mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-21 17:51:14 -05:00
Fix race condition in moderation queue
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect, useImperativeHandle, forwardRef } from 'react';
|
import { useState, useEffect, useImperativeHandle, forwardRef, useCallback, useRef } from 'react';
|
||||||
import { CheckCircle, XCircle, Eye, Calendar, User, Filter, MessageSquare, FileText, Image, X, Trash2, ListTree, RefreshCw, AlertCircle, Clock, Lock, Unlock, AlertTriangle, UserCog, Zap } from 'lucide-react';
|
import { CheckCircle, XCircle, Eye, Calendar, User, Filter, MessageSquare, FileText, Image, X, Trash2, ListTree, RefreshCw, AlertCircle, Clock, Lock, Unlock, AlertTriangle, UserCog, Zap } from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
@@ -26,6 +26,7 @@ import { QueueStatsDashboard } from './QueueStatsDashboard';
|
|||||||
import { EscalationDialog } from './EscalationDialog';
|
import { EscalationDialog } from './EscalationDialog';
|
||||||
import { ReassignDialog } from './ReassignDialog';
|
import { ReassignDialog } from './ReassignDialog';
|
||||||
import { smartMergeArray } from '@/lib/smartStateUpdate';
|
import { smartMergeArray } from '@/lib/smartStateUpdate';
|
||||||
|
import { useDebounce } from '@/hooks/useDebounce';
|
||||||
|
|
||||||
interface ModerationItem {
|
interface ModerationItem {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -95,6 +96,7 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
const { isAdmin, isSuperuser } = useUserRole();
|
const { isAdmin, isSuperuser } = useUserRole();
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
const queue = useModerationQueue();
|
const queue = useModerationQueue();
|
||||||
|
const fetchInProgressRef = useRef(false);
|
||||||
|
|
||||||
// Get admin settings for polling configuration
|
// Get admin settings for polling configuration
|
||||||
const {
|
const {
|
||||||
@@ -108,11 +110,19 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
const refreshStrategy = getAutoRefreshStrategy();
|
const refreshStrategy = getAutoRefreshStrategy();
|
||||||
const preserveInteraction = getPreserveInteractionState();
|
const preserveInteraction = getPreserveInteractionState();
|
||||||
|
|
||||||
const fetchItems = async (entityFilter: EntityFilter = 'all', statusFilter: StatusFilter = 'pending', silent = false) => {
|
const fetchItems = useCallback(async (entityFilter: EntityFilter = 'all', statusFilter: StatusFilter = 'pending', silent = false) => {
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prevent concurrent calls - race condition guard
|
||||||
|
if (fetchInProgressRef.current) {
|
||||||
|
console.log('⚠️ Fetch already in progress, skipping duplicate call');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchInProgressRef.current = true;
|
||||||
|
|
||||||
console.log('🔍 fetchItems called:', {
|
console.log('🔍 fetchItems called:', {
|
||||||
entityFilter,
|
entityFilter,
|
||||||
statusFilter,
|
statusFilter,
|
||||||
@@ -394,11 +404,26 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
variant: 'destructive',
|
variant: 'destructive',
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
|
fetchInProgressRef.current = false;
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
setIsRefreshing(false);
|
setIsRefreshing(false);
|
||||||
setIsInitialLoad(false);
|
setIsInitialLoad(false);
|
||||||
}
|
}
|
||||||
};
|
}, [
|
||||||
|
user,
|
||||||
|
entityCache,
|
||||||
|
profileCache,
|
||||||
|
submissionMemo,
|
||||||
|
items,
|
||||||
|
refreshStrategy,
|
||||||
|
preserveInteraction,
|
||||||
|
interactingWith,
|
||||||
|
toast
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Debounced filters to prevent rapid-fire calls
|
||||||
|
const debouncedEntityFilter = useDebounce(activeEntityFilter, 300);
|
||||||
|
const debouncedStatusFilter = useDebounce(activeStatusFilter, 300);
|
||||||
|
|
||||||
// Expose refresh method via ref
|
// Expose refresh method via ref
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
@@ -410,22 +435,22 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
// Initial fetch on mount and filter changes
|
// Initial fetch on mount and filter changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (user) {
|
if (user) {
|
||||||
fetchItems(activeEntityFilter, activeStatusFilter, false); // Show loading
|
fetchItems(debouncedEntityFilter, debouncedStatusFilter, false); // Show loading
|
||||||
}
|
}
|
||||||
}, [activeEntityFilter, activeStatusFilter, user, fetchItems]);
|
}, [debouncedEntityFilter, debouncedStatusFilter, user, fetchItems]);
|
||||||
|
|
||||||
// Polling for auto-refresh
|
// Polling for auto-refresh
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!user || refreshMode !== 'auto' || isInitialLoad) return;
|
if (!user || refreshMode !== 'auto' || isInitialLoad) return;
|
||||||
|
|
||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
fetchItems(activeEntityFilter, activeStatusFilter, true); // Silent refresh
|
fetchItems(debouncedEntityFilter, debouncedStatusFilter, true); // Silent refresh
|
||||||
}, pollInterval);
|
}, pollInterval);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
};
|
};
|
||||||
}, [user, refreshMode, pollInterval, activeEntityFilter, activeStatusFilter, isInitialLoad, fetchItems]);
|
}, [user, refreshMode, pollInterval, debouncedEntityFilter, debouncedStatusFilter, isInitialLoad, fetchItems]);
|
||||||
|
|
||||||
// Real-time subscription for lock status
|
// Real-time subscription for lock status
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user