From 80d823a1b91f1300b2e0832ab24d6e58c84852ab Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Wed, 5 Nov 2025 16:37:54 +0000 Subject: [PATCH] Fix moderation queue claim logic --- src/components/moderation/ModerationQueue.tsx | 57 ++++++++++++++++--- src/hooks/useModerationQueue.ts | 30 ++++++++++ 2 files changed, 78 insertions(+), 9 deletions(-) diff --git a/src/components/moderation/ModerationQueue.tsx b/src/components/moderation/ModerationQueue.tsx index 458e9714..a3576f16 100644 --- a/src/components/moderation/ModerationQueue.tsx +++ b/src/components/moderation/ModerationQueue.tsx @@ -134,6 +134,17 @@ export const ModerationQueue = forwardRef { + if (lockRestored && queueManager.queue.currentLock) { + const timer = setTimeout(() => { + setLockRestored(false); + }, 10000); // Auto-dismiss after 10 seconds + + return () => clearTimeout(timer); + } + }, [lockRestored, queueManager.queue.currentLock]); + // Fetch active locks count for superusers const isSuperuserValue = isSuperuser(); @@ -377,15 +388,43 @@ export const ModerationQueue = forwardRef - - Active Claim Restored - - Your previous claim was restored. You still have time to review this submission. - - - )} + {lockRestored && queueManager.queue.currentLock && (() => { + // Check if restored submission is in current queue + const restoredSubmissionInQueue = queueManager.items.some( + item => item.id === queueManager.queue.currentLock?.submissionId + ); + + if (!restoredSubmissionInQueue) return null; + + // Calculate time remaining + const timeRemainingMs = queueManager.queue.currentLock.expiresAt.getTime() - Date.now(); + const timeRemainingSec = Math.max(0, Math.floor(timeRemainingMs / 1000)); + const isExpiringSoon = timeRemainingSec < 300; // Less than 5 minutes + + return ( + + + + {isExpiringSoon + ? `Lock Expiring Soon (${Math.floor(timeRemainingSec / 60)}m ${timeRemainingSec % 60}s)` + : "Active Claim Restored" + } + + + {isExpiringSoon + ? "Your lock is about to expire. Complete your review or extend the lock." + : "Your previous claim was restored. You still have time to review this submission." + } + + + ); + })()} {/* Filter Bar */} { // Only restore if lock hasn't expired (race condition check) if (data.locked_until && expiresAt > new Date()) { + const timeRemaining = expiresAt.getTime() - new Date().getTime(); + const minTimeMs = 60 * 1000; // 60 seconds minimum + + if (timeRemaining < minTimeMs) { + // Lock expires too soon - auto-release it + logger.info('Lock expired or expiring soon, auto-releasing', { + submissionId: data.id, + timeRemainingSeconds: Math.floor(timeRemaining / 1000), + }); + + // Release the stale lock + await supabase.rpc('release_submission_lock', { + submission_id: data.id, + moderator_id: user.id, + }); + + return; // Don't restore + } + + // Lock has sufficient time - restore it setCurrentLock({ submissionId: data.id, expiresAt, @@ -198,6 +218,7 @@ export const useModerationQueue = (config?: UseModerationQueueConfig) => { logger.info('Lock state restored from database', { submissionId: data.id, expiresAt: expiresAt.toISOString(), + timeRemainingSeconds: Math.floor(timeRemaining / 1000), }); } } @@ -399,6 +420,15 @@ export const useModerationQueue = (config?: UseModerationQueueConfig) => { return false; } + // Check if trying to claim same submission user already has locked + if (currentLock && currentLock.submissionId === submissionId) { + toast({ + title: 'Already Claimed', + description: 'You already have this submission claimed. Review it below.', + }); + return true; // Return success, don't re-claim + } + // Check if user already has an active lock on a different submission if (currentLock && currentLock.submissionId !== submissionId) { toast({