Legal Documents Management System
Overview
The Legal Documents Management System enables administrators to manage Terms of Service and Privacy Policy documents with version control, flexible enforcement options, and comprehensive audit trails. Users must affirmatively accept these documents on first joining and whenever updates are made.
Key Features
Version Management
- Semantic Versioning: Documents use standard versioning (1.0.0, 1.1.0, 2.0.0)
- One Active Version: Only one document version per type can be active at a time
- Immutable History: Published documents cannot be edited (create new version instead)
- Draft Support: Save as draft and publish later
Flexible Enforcement
- Immediate Acceptance: Users blocked from platform until they accept
- Grace Period: Users see dismissible banner; access blocked after X days
- Admin Configurable: Choose enforcement per update
Audit & Compliance
- Complete Audit Trail: Track IP address, user agent, and exact timestamp of every acceptance
- User History: See when each user accepted which version
- CSV Export: Download acceptance records for compliance
- Analytics: View acceptance rates and statistics
User Experience
- Modal Interface: Clean modal with tab interface (Terms | Privacy)
- Grace Period Banner: Smart countdown timer with color escalation
- Settings Integration: Users can review accepted documents anytime
- Fallback Support: Works offline with localStorage backup
Architecture
Data Model
LegalDocument
├── id (CUID)
├── type (terms | privacy)
├── version (string - semantic)
├── title (string)
├── content (text - full HTML/markdown)
├── effectiveDate (datetime)
├── publishedAt (datetime) - null if draft
├── publishedBy (email)
├── isActive (boolean) - only one per type
├── requiresImmediate (boolean)
├── gracePeriodDays (number)
└── acceptances (relationship)
DocumentAcceptance
├── id (CUID)
├── userId (string)
├── documentId (string)
├── version (string - denormalized)
├── acceptedAt (datetime)
├── ipAddress (string - optional)
└── userAgent (string - optional)
API Endpoints
Public Endpoints (no auth required):
GET /legal/current/:type- Get current active documentPOST /legal/accept- User accepts documentGET /legal/status- Check if user needs to accept
Admin Endpoints (AdminClerkAuthGuard):
GET /legal/admin/documents- List all versionsGET /legal/admin/documents/:id- Get single documentPOST /legal/admin/documents- Create new versionPUT /legal/admin/documents/:id- Update draftPOST /legal/admin/documents/:id/publish- Publish & activateDELETE /legal/admin/documents/:id- Delete unpublishedGET /legal/admin/acceptances- View logs with filteringGET /legal/admin/documents/:id/analytics- Get statistics
User Flows
New User Registration
- User signs up via Clerk
- Redirected to platform
AuthenticatedLayoutchecks/legal/statusAPI- No previous acceptance → Blocking
TermsAcceptanceModalappears - User reads terms in tabs
- User checks agreement checkbox
- User clicks "Accept Terms"
- API call to
/legal/acceptrecords acceptance - Database persists: User ID, Document ID, Version, IP, Timestamp
- User granted platform access
Update to Terms (Immediate Enforcement)
- Admin creates new version (e.g., 2.0.0)
- Admin publishes document
- Previous v1.0.0 marked as inactive/archived
- On next user login:
- API checks
/legal/status - Detects userVersion (1.0.0) ≠ currentVersion (2.0.0)
- Shows blocking modal with new terms
- User must accept v2.0.0 before accessing platform
- API checks
- Acceptance recorded in database
Update to Terms (Grace Period)
- Admin creates new version (e.g., 2.0.0)
- Admin publishes with:
requiresImmediate: falsegracePeriodDays: 7
- On next user login:
- API checks
/legal/status - Returns grace period info
- Frontend shows
GracePeriodBannerinstead of blocking modal - Banner shows: "New Terms available. Review by [date]"
- User can dismiss and continue using platform
- API checks
- After 7 days:
- Banner converts to blocking modal
- User must accept to continue
Review Anytime in Settings
- User goes to Settings page
- Scrolls to "Legal Agreements" section
- Sees:
- Terms: "Accepted • Version 2.0.0" with ✓ indicator
- Privacy: "Accepted • Version 1.5.0" with ✓ indicator
- Latest version info below each
- Clicks "Review" button on either
- Modal opens with current documents
- Can read and review anytime
Admin Panel
Document List Page (/legal)
- Features:
- Tab switching between Terms & Privacy
- Three sections: Active Version | Drafts | Archived
- Status badges and acceptance counts
- Dropdown actions: View, Edit, Duplicate, Delete
- Empty states with helpful CTAs
Create/Edit Document (/legal/new or /legal/[id]/edit)
- Form Fields:
- Document type selector (disabled when editing)
- Version number (semantic versioning)
- Title for display
- Content textarea (large, for full legal text)
- Effective date picker
- Enforcement Settings:
- Toggle: "Require Immediate Acceptance"
- Conditional: Grace Period Days input
- Actions:
- Save as Draft (for later editing)
- Save & Publish (goes live immediately)
- Preview modal (shows as users will see it)
Document Details Page (/legal/[id])
- Display:
- Full document content
- Enforcement settings card
- Version and publication info
- Two Tabs:
- Content: Display full text
- Analytics:
- Total acceptances (number)
- Total users (number)
- Acceptance rate (%)
- Recent acceptances table (last 20 + link to all)
- Actions:
- Publish (for drafts only)
- Edit (for drafts only)
- Duplicate (for any version)
Acceptance Logs Page (/legal/acceptances)
- Filters:
- Document type (All/Terms/Privacy)
- Email search
- Real-time filtering
- Statistics Cards:
- Total acceptances
- Showing count
- Last 7 days
- Table Display:
- User email, name
- Document type (badge)
- Version accepted
- Acceptance date & time
- IP address (hidden on mobile)
- Pagination: Previous/Next navigation
- CSV Export: Download all filtered records with headers
Frontend Components
TermsAcceptanceModal
- Props:
isOpen: booleanonAccept: () => voidrequiresImmediate?: boolean
- Features:
- Tab interface (Terms | Privacy)
- Fetches current documents from API
- Large content area (scrollable)
- Agreement checkbox (required)
- Loading states
- Error handling with fallback
- Toast notifications
GracePeriodBanner
- Props:
type: "terms" | "privacy"gracePeriodDays: numberpublishedDate: stringonReviewClick: () => voidonDismiss: () => void
- Features:
- Sticky top banner
- Countdown timer (updates every minute)
- Color escalation: Amber (normal) → Red (3+ days left)
- "Review Now" button
- Dismiss button (X)
- Auto-hides when grace period expires
AuthenticatedLayout Integration
- On Mount:
- Calls
/legal/statusAPI - Determines if acceptance needed
- Shows appropriate UI (modal or banner)
- Calls
- Fallback: Uses localStorage if API fails
- Grace Period Logic:
- If immediate: show blocking modal
- If grace period: show dismissible banner
Settings Page
- New Section: Legal Agreements
- Status cards for Terms & Privacy
- Shows accepted version
- Shows latest version
- Green ✓ if current
- "Review" button for each
- Integrated with TermsAcceptanceModal
Database Schema
-- Legal Documents Table
CREATE TABLE legal_documents (
id TEXT PRIMARY KEY,
type TEXT NOT NULL, -- 'terms' | 'privacy'
version TEXT NOT NULL,
title TEXT NOT NULL,
content TEXT NOT NULL,
effective_date TIMESTAMP NOT NULL,
published_at TIMESTAMP,
published_by TEXT,
is_active BOOLEAN DEFAULT false,
requires_immediate BOOLEAN DEFAULT true,
grace_period_days INTEGER DEFAULT 0,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP,
UNIQUE(type, version),
INDEX(type),
INDEX(is_active),
INDEX(published_at)
);
-- Document Acceptance Records (Audit Trail)
CREATE TABLE document_acceptances (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL,
document_id TEXT NOT NULL,
version TEXT NOT NULL,
accepted_at TIMESTAMP DEFAULT NOW(),
ip_address TEXT,
user_agent TEXT,
UNIQUE(user_id, document_id, version),
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY(document_id) REFERENCES legal_documents(id) ON DELETE CASCADE,
INDEX(user_id),
INDEX(document_id),
INDEX(accepted_at)
);
-- User Table (extended fields for legal documents)
ALTER TABLE users ADD COLUMN (
terms_accepted_at TIMESTAMP,
terms_version TEXT,
privacy_policy_accepted_at TIMESTAMP,
privacy_policy_version TEXT
);
Security & Compliance
Access Control
- ✅ Admin Guard: All admin endpoints protected by
AdminClerkAuthGuard - ✅ User Auth: User endpoints require
ClerkAuthGuard - ✅ Email Whitelist: Only whitelisted emails can access admin panel
Data Protection
- ✅ Immutable Records: Published documents cannot be edited
- ✅ Version Tracking: Each acceptance tied to specific version
- ✅ Audit Trail: IP + user agent logged with each acceptance
- ✅ Atomic Transactions: Publishing deactivates old version atomically
Compliance Features
- ✅ GDPR Ready: Tracks consent with timestamp and details
- ✅ Audit Export: CSV download for legal records
- ✅ User Rights: Users can review accepted documents anytime
- ✅ Change History: Full version history maintained
Best Practices
For Admins
-
Version Numbers: Use semantic versioning
- 1.0.0 = Initial release
- 1.1.0 = Minor updates (formatting, clarification)
- 2.0.0 = Major changes (new rules, terms)
-
Enforcement Strategy:
- Immediate: Material changes that users must know about
- Grace Period: Minor updates, improved UX (3-7 days typical)
-
Publishing Workflow:
- Create version as draft
- Review content (use preview button)
- Choose enforcement type
- Publish when ready
- Old version auto-archives
-
Audit & Tracking:
- Regularly check acceptance logs
- Export records for compliance
- Monitor acceptance rates
- Follow up on non-compliant users
For Users
- First Login: Read and accept both terms and privacy policy
- Updates: Respond promptly to new versions
- Settings: Review accepted documents anytime
- Questions: Contact support if unclear
Example Workflows
Publishing New Terms of Service
Admin Panel → Legal Docs → Create Document
├─ Type: Terms of Service
├─ Version: 2.0.0
├─ Content: [paste new terms]
├─ Effective Date: [select date]
├─ Enforcement: Require Immediate ✓
└─ Click: Save & Publish
Result:
├─ Previous v1.0.0 → Archived
├─ New v2.0.0 → Active
└─ Users see blocking modal on next login
Grace Period Update
Admin Panel → Legal Docs → Create Document
├─ Type: Privacy Policy
├─ Version: 1.5.0
├─ Content: [paste updated policy]
├─ Enforcement: Grace Period
├─ Grace Days: 7
└─ Click: Save & Publish
Result:
├─ Users see dismissible banner
├─ Deadline shown: 7 days from now
├─ After 7 days → Modal becomes blocking
└─ Acceptance recorded with version
Viewing Analytics
Admin Panel → Legal Docs → [Click Document]
├─ View analytics tab
├─ See:
│ ├─ Total acceptances: 245/300 users
│ ├─ Acceptance rate: 81.7%
│ └─ Recent acceptances table
└─ Click "Acceptances" to view all records
Troubleshooting
Users not seeing modal
- ✅ Check
/legal/statusAPI response - ✅ Verify user version ≠ active version
- ✅ Check browser console for errors
- ✅ Clear localStorage and refresh
Modal not fetching content
- ✅ Verify
/legal/current/:typeendpoint - ✅ Check network tab in DevTools
- ✅ Verify API is running
- ✅ Check CORS headers
Acceptance not recorded
- ✅ Verify POST to
/legal/acceptsucceeds - ✅ Check user is authenticated
- ✅ Verify database connection
- ✅ Check for validation errors
Can't edit document
- ✅ Document must be in Draft status
- ✅ Published docs cannot be edited
- ✅ Create new version for changes
- ✅ Click "Duplicate" to copy content