diff --git a/docs/moderation/ARCHITECTURE.md b/docs/moderation/ARCHITECTURE.md new file mode 100644 index 00000000..09d5f72a --- /dev/null +++ b/docs/moderation/ARCHITECTURE.md @@ -0,0 +1,34 @@ +# Moderation Queue Architecture + +## Overview + +The moderation queue system is a comprehensive content review platform that enables moderators to review, approve, and reject user-submitted content including park/ride submissions, photo uploads, and user reviews. + +## System Architecture + +### Core Components + +``` +┌─────────────────────────────────────────────────────────────┐ +│ ModerationQueue (Root) │ +│ - Entry point for moderation interface │ +│ - Manages UI state (modals, dialogs) │ +│ - Delegates business logic to hooks │ +└────────────┬────────────────────────────────────────────────┘ + │ + ├─► useModerationQueueManager (Orchestrator) + │ └─► Combines multiple sub-hooks + │ ├─► useModerationFilters (Filtering) + │ ├─► usePagination (Page management) + │ ├─► useModerationQueue (Lock management) + │ ├─► useModerationActions (Action handlers) + │ ├─► useEntityCache (Entity name resolution) + │ └─► useProfileCache (User profile caching) + │ + ├─► QueueFilters (Filter controls) + ├─► QueueStats (Statistics display) + ├─► LockStatusDisplay (Current lock info) + │ + └─► QueueItem (Individual submission renderer) + └─► Wrapped in ModerationErrorBoundary + └─► Prevents individual failures from crashing queue diff --git a/docs/moderation/COMPONENTS.md b/docs/moderation/COMPONENTS.md new file mode 100644 index 00000000..fdef26ad --- /dev/null +++ b/docs/moderation/COMPONENTS.md @@ -0,0 +1,524 @@ +# Moderation Queue Components + +## Component Reference + +### ModerationQueue (Root Component) + +**Location:** `src/components/moderation/ModerationQueue.tsx` + +**Purpose:** Root component for moderation interface. Orchestrates all sub-components and manages UI state. + +**Props:** +```typescript +interface ModerationQueueProps { + optimisticallyUpdateStats?: (delta: Partial<{ + pendingSubmissions: number; + openReports: number; + flaggedContent: number; + }>) => void; +} +``` + +**Ref API:** +```typescript +interface ModerationQueueRef { + refresh: () => void; +} +``` + +**Usage:** +```tsx +import { useRef } from 'react'; +import { ModerationQueue } from '@/components/moderation/ModerationQueue'; + +function AdminPanel() { + const queueRef = useRef(null); + + return ( +
+ + +
+ ); +} +``` + +--- + +### ModerationErrorBoundary + +**Location:** `src/components/error/ModerationErrorBoundary.tsx` + +**Purpose:** Catches React render errors in queue items, preventing full queue crashes. + +**Props:** +```typescript +interface ModerationErrorBoundaryProps { + children: ReactNode; + submissionId?: string; + fallback?: ReactNode; + onError?: (error: Error, errorInfo: ErrorInfo) => void; +} +``` + +**Features:** +- Automatic error logging +- User-friendly error UI +- Retry functionality +- Copy error details button +- Development-mode stack traces + +**Usage:** +```tsx + + + +``` + +**Custom Fallback:** +```tsx +Custom error message} + onError={(error, info) => { + // Send to monitoring service + trackError(error, info); + }} +> + + +``` + +--- + +### QueueItem + +**Location:** `src/components/moderation/QueueItem.tsx` + +**Purpose:** Renders individual submission in queue with all interaction controls. + +**Props:** +```typescript +interface QueueItemProps { + item: ModerationItem; + isMobile: boolean; + actionLoading: string | null; + isLockedByMe: boolean; + isLockedByOther: boolean; + lockStatus: LockStatus; + currentLockSubmissionId?: string; + notes: Record; + isAdmin: boolean; + isSuperuser: boolean; + queueIsLoading: boolean; + onNoteChange: (id: string, value: string) => void; + onApprove: (item: ModerationItem, action: 'approved' | 'rejected', notes?: string) => void; + onResetToPending: (item: ModerationItem) => void; + onRetryFailed: (item: ModerationItem) => void; + onOpenPhotos: (photos: PhotoForDisplay[], index: number) => void; + onOpenReviewManager: (submissionId: string) => void; + onOpenItemEditor: (submissionId: string) => void; + onClaimSubmission: (submissionId: string) => void; + onDeleteSubmission: (item: ModerationItem) => void; + onInteractionFocus: (id: string) => void; + onInteractionBlur: (id: string) => void; +} +``` + +**Key Features:** +- Displays submission type, status, timestamps +- User profile with avatar +- Validation summary (errors, warnings) +- Lock status indicators +- Moderator edit badges +- Action buttons (approve, reject, claim) +- Responsive mobile/desktop layouts + +**Accessibility:** +- Keyboard navigation support +- ARIA labels on interactive elements +- Focus management +- Screen reader compatible + +--- + +### QueueFilters + +**Location:** `src/components/moderation/QueueFilters.tsx` + +**Purpose:** Filter and sort controls for moderation queue. + +**Props:** +```typescript +interface QueueFiltersProps { + activeEntityFilter: EntityFilter; + activeStatusFilter: StatusFilter; + sortConfig: SortConfig; + isMobile: boolean; + isLoading?: boolean; + onEntityFilterChange: (filter: EntityFilter) => void; + onStatusFilterChange: (filter: StatusFilter) => void; + onSortChange: (config: SortConfig) => void; + onClearFilters: () => void; + showClearButton: boolean; +} +``` + +**Features:** +- Entity type filter (all, reviews, submissions, photos) +- Status filter (pending, approved, rejected, etc.) +- Sort controls (date, type, status) +- Clear filters button +- Fully accessible (ARIA labels, keyboard navigation) + +**Usage:** +```tsx + +``` + +--- + +### ValidationSummary + +**Location:** `src/components/moderation/ValidationSummary.tsx` + +**Purpose:** Displays validation results for submission items. + +**Props:** +```typescript +interface ValidationSummaryProps { + item: { + item_type: string; + item_data: SubmissionItemData; + id?: string; + }; + onValidationChange?: (result: ValidationResult) => void; + compact?: boolean; + validationKey?: number; +} +``` + +**View Modes:** + +**Compact (for queue items):** +- Status badges (Valid, Errors, Warnings) +- Always-visible error details (no hover needed) +- Minimal space usage + +**Detailed (for review manager):** +- Expandable validation details +- Full error, warning, and suggestion lists +- Re-validate button + +**Usage:** +```tsx +{/* Compact view in queue */} + { + if (result.blockingErrors.length > 0) { + setCanApprove(false); + } + }} +/> + +{/* Detailed view in editor */} + +``` + +--- + +## Hooks Reference + +### useModerationQueueManager + +**Location:** `src/hooks/moderation/useModerationQueueManager.ts` + +**Purpose:** Orchestrator hook combining all moderation queue logic. + +**Usage:** +```typescript +const queueManager = useModerationQueueManager({ + user, + isAdmin: isAdmin(), + isSuperuser: isSuperuser(), + toast, + settings: { + refreshMode: 'auto', + pollInterval: 30000, + refreshStrategy: 'merge', + preserveInteraction: true, + useRealtimeQueue: true, + }, +}); + +// Access sub-hooks +queueManager.filters.setEntityFilter('reviews'); +queueManager.pagination.setCurrentPage(2); +queueManager.queue.claimSubmission(itemId); + +// Perform actions +await queueManager.performAction(item, 'approved', 'Looks good!'); +await queueManager.deleteSubmission(item); +``` + +--- + +### useModerationQueue + +**Location:** `src/hooks/useModerationQueue.ts` + +**Purpose:** Lock management and queue statistics. + +**Features:** +- Claim/release submission locks +- Lock expiry countdown +- Lock status checking +- Queue statistics + +**Usage:** +```typescript +const queue = useModerationQueue({ + onLockStateChange: () => { + console.log('Lock state changed'); + } +}); + +// Claim submission +await queue.claimSubmission('submission-123'); + +// Extend lock (adds 15 minutes) +await queue.extendLock(); + +// Release lock +await queue.releaseLock('submission-123'); + +// Check lock status +const timeRemaining = queue.getTimeRemaining(); +const progress = queue.getLockProgress(); +``` + +--- + +## Testing Components + +### Unit Testing Example + +```typescript +import { render, screen } from '@testing-library/react'; +import { ModerationErrorBoundary } from '@/components/error/ModerationErrorBoundary'; + +describe('ModerationErrorBoundary', () => { + it('catches errors and shows fallback UI', () => { + const ThrowError = () => { + throw new Error('Test error'); + }; + + render( + + + + ); + + expect(screen.getByText(/queue item error/i)).toBeInTheDocument(); + expect(screen.getByText(/test error/i)).toBeInTheDocument(); + }); + + it('shows retry button', () => { + const ThrowError = () => { + throw new Error('Test error'); + }; + + render( + + + + ); + + expect(screen.getByRole('button', { name: /retry/i })).toBeInTheDocument(); + }); +}); +``` + +### Integration Testing Example + +```typescript +import { renderHook, act } from '@testing-library/react'; +import { useModerationQueueManager } from '@/hooks/moderation/useModerationQueueManager'; + +describe('useModerationQueueManager', () => { + it('filters items correctly', async () => { + const { result } = renderHook(() => useModerationQueueManager(config)); + + act(() => { + result.current.filters.setEntityFilter('reviews'); + }); + + await waitFor(() => { + expect(result.current.items.every(item => item.type === 'review')).toBe(true); + }); + }); +}); +``` + +--- + +## Accessibility Guidelines + +### Keyboard Navigation + +**Queue Filters:** +- `Tab`: Navigate between filter controls +- `Enter`/`Space`: Open dropdown +- `Arrow keys`: Navigate dropdown options +- `Escape`: Close dropdown + +**Queue Items:** +- `Tab`: Navigate between interactive elements +- `Enter`/`Space`: Activate buttons +- `Escape`: Close expanded sections + +### Screen Reader Support + +All components include: +- Semantic HTML (`