Fix moderation queue flashing

This commit is contained in:
gpt-engineer-app[bot]
2025-10-09 15:35:30 +00:00
parent 1557def354
commit f01c58a056

View File

@@ -121,14 +121,22 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((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<ModerationQueueRef>((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<ModerationQueueRef>((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<ModerationQueueRef>((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<ModerationQueueRef>((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<ModerationQueueRef>((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<ModerationQueueRef>((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<ModerationQueueRef>((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<ModerationQueueRef>((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<ModerationQueueRef>((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<ModerationQueueRef>((props, ref) => {
};
const QueueContent = () => {
if (loading) {
if (isInitialLoad && loading) {
return (
<div className="flex items-center justify-center p-8">
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-primary"></div>
@@ -1890,11 +1913,8 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
<span className="text-sm">Claim this submission to lock it for 15 minutes while you review</span>
<Button
onClick={async () => {
const success = await queue.claimSubmission(item.id);
if (success) {
// Refresh to update UI
fetchItems(activeEntityFilter, activeStatusFilter, false);
}
await queue.claimSubmission(item.id);
// No refresh needed - realtime handles it
}}
disabled={queue.isLoading}
size="sm"
@@ -2154,11 +2174,8 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
// Handle claim next action
const handleClaimNext = async () => {
const claimedId = await queue.claimNext();
if (claimedId) {
// Scroll to claimed submission or fetch to show it
fetchItems(activeEntityFilter, activeStatusFilter);
}
await queue.claimNext();
// No refresh needed - realtime subscription handles updates
};
return (
@@ -2403,7 +2420,10 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
open={reviewManagerOpen}
onOpenChange={setReviewManagerOpen}
onComplete={() => {
fetchItems(activeEntityFilter, activeStatusFilter);
// Silent cleanup after delay
setTimeout(() => {
fetchItems(activeEntityFilter, activeStatusFilter, true);
}, 2000);
}}
/>
)}