diff --git a/src/components/moderation/ModerationQueue.tsx b/src/components/moderation/ModerationQueue.tsx index 1c45969d..23c18012 100644 --- a/src/components/moderation/ModerationQueue.tsx +++ b/src/components/moderation/ModerationQueue.tsx @@ -121,14 +121,22 @@ export const ModerationQueue = forwardRef((props, ref) => { const preserveInteraction = getPreserveInteractionState(); const useRealtimeQueue = getUseRealtimeQueue(); - // Store admin settings in refs to avoid triggering fetchItems recreation + // Store admin settings and stable refs to avoid triggering fetchItems recreation const refreshStrategyRef = useRef(refreshStrategy); const preserveInteractionRef = useRef(preserveInteraction); + const userRef = useRef(user); + const toastRef = useRef(toast); + const isAdminRef = useRef(isAdmin); + const isSuperuserRef = useRef(isSuperuser); useEffect(() => { refreshStrategyRef.current = refreshStrategy; preserveInteractionRef.current = preserveInteraction; - }, [refreshStrategy, preserveInteraction]); + userRef.current = user; + toastRef.current = toast; + isAdminRef.current = isAdmin; + isSuperuserRef.current = isSuperuser; + }, [refreshStrategy, preserveInteraction, user, toast, isAdmin, isSuperuser]); // Only sync itemsRef (not loadedIdsRef) to avoid breaking silent polling logic useEffect(() => { @@ -136,7 +144,7 @@ export const ModerationQueue = forwardRef((props, ref) => { }, [items]); const fetchItems = useCallback(async (entityFilter: EntityFilter = 'all', statusFilter: StatusFilter = 'pending', silent = false, tab: QueueTab = 'mainQueue') => { - if (!user) { + if (!userRef.current) { return; } @@ -525,12 +533,7 @@ export const ModerationQueue = forwardRef((props, ref) => { setIsRefreshing(false); setIsInitialLoad(false); } - }, [user, toast, isAdmin, isSuperuser]); - - // Log when fetchItems is recreated to help debug refresh issues - useEffect(() => { - console.log('🔄 fetchItems callback recreated'); - }, [fetchItems]); + }, []); // Empty deps - use refs instead // Debounced filters to prevent rapid-fire calls const debouncedEntityFilter = useDebounce(activeEntityFilter, 500); @@ -768,7 +771,10 @@ export const ModerationQueue = forwardRef((props, ref) => { description: "Submission and all items have been reset to pending status", }); - fetchItems(activeEntityFilter, activeStatusFilter); + // Silent cleanup after delay + setTimeout(() => { + fetchItems(activeEntityFilter, activeStatusFilter, true); + }, 2000); } catch (error: any) { console.error('Error resetting submission:', error); toast({ @@ -819,7 +825,10 @@ export const ModerationQueue = forwardRef((props, ref) => { description: `Processed ${failedItems.length} failed item(s)`, }); - fetchItems(activeEntityFilter, activeStatusFilter); + // Silent cleanup after delay + setTimeout(() => { + fetchItems(activeEntityFilter, activeStatusFilter, true); + }, 2000); } catch (error: any) { console.error('Error retrying failed items:', error); toast({ @@ -844,6 +853,19 @@ export const ModerationQueue = forwardRef((props, ref) => { setActionLoading(item.id); + // Determine if item should be removed from current view after action + const shouldRemove = (activeStatusFilter === 'pending' || activeStatusFilter === 'flagged') && + (action === 'approved' || action === 'rejected'); + + // Optimistic update + if (shouldRemove) { + setItems(prev => prev.filter(i => i.id !== item.id)); + } else { + setItems(prev => prev.map(i => + i.id === item.id ? { ...i, status: action } : i + )); + } + // Release lock if this submission is claimed by current user if (queue.currentLock?.submissionId === item.id) { await queue.releaseLock(item.id); @@ -952,8 +974,10 @@ export const ModerationQueue = forwardRef((props, ref) => { description: "All entities created successfully", }); - // Refresh the queue - fetchItems(activeEntityFilter, activeStatusFilter); + // Silent cleanup after delay + setTimeout(() => { + fetchItems(activeEntityFilter, activeStatusFilter, true); + }, 2000); return; } @@ -1049,8 +1073,10 @@ export const ModerationQueue = forwardRef((props, ref) => { description: `Successfully approved and published ${photoSubmission.items.length} photo(s)`, }); - // Refresh the queue - fetchItems(activeEntityFilter, activeStatusFilter); + // Silent cleanup after delay + setTimeout(() => { + fetchItems(activeEntityFilter, activeStatusFilter, true); + }, 2000); return; } catch (error: any) { @@ -1089,8 +1115,10 @@ export const ModerationQueue = forwardRef((props, ref) => { description: `Successfully processed ${submissionItems.length} item(s)`, }); - // Refresh and return early since edge function already updated parent - fetchItems(activeEntityFilter, activeStatusFilter); + // Silent cleanup after delay + setTimeout(() => { + fetchItems(activeEntityFilter, activeStatusFilter, true); + }, 2000); return; } else if (action === 'rejected') { // Cascade rejection to all pending items @@ -1156,35 +1184,30 @@ export const ModerationQueue = forwardRef((props, ref) => { description: `The ${item.type} has been ${action}`, }); - // Only update local state if the database update was successful - setItems(prev => prev.map(i => - i.id === item.id - ? { ...i, status: action } - : i - )); - - // Clear notes only after successful update + // Clear notes after successful update setNotes(prev => { const newNotes = { ...prev }; delete newNotes[item.id]; return newNotes; }); - // Refresh if needed based on filter - if ((activeStatusFilter === 'pending' && (action === 'approved' || action === 'rejected')) || - (activeStatusFilter === 'flagged' && (action === 'approved' || action === 'rejected'))) { - // Item no longer matches filter - } + // Silent cleanup after delay + setTimeout(() => { + fetchItems(activeEntityFilter, activeStatusFilter, true); + }, 2000); } catch (error: any) { console.error('Error moderating content:', error); - // Revert any optimistic updates - setItems(prev => prev.map(i => - i.id === item.id - ? { ...i, status: item.status } // Revert to original status - : i - )); + // Revert optimistic update - restore item to list + setItems(prev => { + const exists = prev.find(i => i.id === item.id); + if (exists) { + return prev.map(i => i.id === item.id ? item : i); + } else { + return [...prev, item]; + } + }); toast({ title: "Error", @@ -1385,7 +1408,7 @@ export const ModerationQueue = forwardRef((props, ref) => { }; const QueueContent = () => { - if (loading) { + if (isInitialLoad && loading) { return (
@@ -1890,11 +1913,8 @@ export const ModerationQueue = forwardRef((props, ref) => { Claim this submission to lock it for 15 minutes while you review