diff --git a/src/components/moderation/ModerationQueue.tsx b/src/components/moderation/ModerationQueue.tsx index 2e6ed521..254fd652 100644 --- a/src/components/moderation/ModerationQueue.tsx +++ b/src/components/moderation/ModerationQueue.tsx @@ -1,6 +1,6 @@ import { useState, useImperativeHandle, forwardRef, useMemo, useCallback, useRef, useEffect } from 'react'; import { useVirtualizer } from '@tanstack/react-virtual'; -import { AlertCircle } from 'lucide-react'; +import { AlertCircle, Info } from 'lucide-react'; import { Card, CardContent } from '@/components/ui/card'; import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; import { TooltipProvider } from '@/components/ui/tooltip'; @@ -88,6 +88,8 @@ export const ModerationQueue = forwardRef([]); const [activeLocksCount, setActiveLocksCount] = useState(0); + const [lockRestored, setLockRestored] = useState(false); + const [initialLoadComplete, setInitialLoadComplete] = useState(false); // Confirmation dialog state const [confirmDialog, setConfirmDialog] = useState<{ @@ -153,6 +155,19 @@ export const ModerationQueue = forwardRef clearInterval(interval); }, [isSuperuser, queueManager.queue.queueStats]); + // Track if lock was restored from database + useEffect(() => { + if (!initialLoadComplete) { + setInitialLoadComplete(true); + return; + } + + if (queueManager.queue.currentLock && !lockRestored) { + // If we have a lock after initial load but haven't claimed in this session + setLockRestored(true); + } + }, [queueManager.queue.currentLock, lockRestored, initialLoadComplete]); + // Virtual scrolling setup const parentRef = useRef(null); const virtualizer = useVirtualizer({ @@ -359,6 +374,17 @@ export const ModerationQueue = forwardRef )} + {/* Lock Restored Alert */} + {lockRestored && queueManager.queue.currentLock && ( + + + Active Claim Restored + + Your previous claim was restored. You still have time to review this submission. + + + )} + {/* Filter Bar */} { }; }, []); + // Restore active lock from database on mount + const restoreActiveLock = useCallback(async () => { + if (!user?.id) return; + + try { + // Query for any active lock assigned to current user + const { data, error } = await supabase + .from('content_submissions') + .select('id, locked_until') + .eq('assigned_to', user.id) + .gt('locked_until', new Date().toISOString()) + .in('status', ['pending', 'partially_approved']) + .order('locked_until', { ascending: false }) + .limit(1) + .maybeSingle(); + + if (error) { + throw error; + } + + if (data) { + const expiresAt = new Date(data.locked_until || ''); + + // Only restore if lock hasn't expired (race condition check) + if (data.locked_until && expiresAt > new Date()) { + setCurrentLock({ + submissionId: data.id, + expiresAt, + }); + + // Start countdown timer for restored lock + startLockTimer(expiresAt); + + console.log('Lock state restored from database', { + submissionId: data.id, + expiresAt: expiresAt.toISOString(), + }); + } + } + } catch (error: unknown) { + // Log but don't show user toast (they haven't taken any action yet) + console.debug('Failed to restore lock state', { + error: error instanceof Error ? error.message : String(error), + userId: user.id, + }); + } + }, [user, startLockTimer]); + + // Initialize lock state from database on mount + useEffect(() => { + if (!user) return; + + restoreActiveLock(); + }, [user, restoreActiveLock]); + + // Sync lock state across tabs when user returns to the page + useEffect(() => { + if (!user) return; + + const handleVisibilityChange = () => { + if (document.visibilityState === 'visible') { + // User returned to tab - check if lock state is still valid + if (!currentLock) { + restoreActiveLock(); + } + } + }; + + document.addEventListener('visibilitychange', handleVisibilityChange); + + return () => { + document.removeEventListener('visibilitychange', handleVisibilityChange); + }; + }, [user, currentLock, restoreActiveLock]); + // Claim a specific submission (CRM-style claim any) const extendLock = useCallback(async (submissionId: string): Promise => { if (!user?.id) return false; @@ -323,6 +398,16 @@ export const useModerationQueue = (config?: UseModerationQueueConfig) => { return false; } + // Check if user already has an active lock on a different submission + if (currentLock && currentLock.submissionId !== submissionId) { + toast({ + title: 'Already Have Active Lock', + description: 'Release your current lock before claiming another submission', + variant: 'destructive', + }); + return false; + } + setIsLoading(true); try { // Get submission details FIRST for better toast message