diff --git a/src/App.tsx b/src/App.tsx index 81d82b3f..66e2efa2 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -44,12 +44,20 @@ import BlogIndex from "./pages/BlogIndex"; import BlogPost from "./pages/BlogPost"; import AdminBlog from "./pages/AdminBlog"; -const queryClient = new QueryClient(); +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + refetchOnWindowFocus: false, // Don't refetch when switching back to tab + refetchOnMount: true, // Still refetch on component mount + refetchOnReconnect: true, // Refetch when internet reconnects + staleTime: 60000, // Consider data fresh for 60 seconds + }, + }, +}); function AppContent() { return ( - ( - + + + ); diff --git a/src/components/moderation/ModerationQueue.tsx b/src/components/moderation/ModerationQueue.tsx index 86472fcb..15df2714 100644 --- a/src/components/moderation/ModerationQueue.tsx +++ b/src/components/moderation/ModerationQueue.tsx @@ -130,6 +130,7 @@ export const ModerationQueue = forwardRef((props, ref) => { const isMountingRef = useRef(true); const initialFetchCompleteRef = useRef(false); const FETCH_COOLDOWN_MS = 1000; + const isPageVisible = useRef(true); // Pagination state const [currentPage, setCurrentPage] = useState(1); @@ -212,8 +213,34 @@ export const ModerationQueue = forwardRef((props, ref) => { } }, [loadingState, items.length, hasRenderedOnce]); + // Track page visibility to prevent tab-switch refreshes + useEffect(() => { + const handleVisibilityChange = () => { + const wasHidden = !isPageVisible.current; + isPageVisible.current = !document.hidden; + + if (wasHidden && isPageVisible.current) { + console.log('📄 Page became visible - NOT auto-refreshing queue'); + } + }; + + document.addEventListener('visibilitychange', handleVisibilityChange); + return () => document.removeEventListener('visibilitychange', handleVisibilityChange); + }, []); + const fetchItems = useCallback(async (entityFilter: EntityFilter = 'all', statusFilter: StatusFilter = 'pending', silent = false, tab: QueueTab = 'mainQueue') => { + console.log('🔍 fetchItems called:', { + hasUser: !!userRef.current, + entityFilter, + statusFilter, + silent, + tab, + loadingState, + timestamp: new Date().toISOString() + }); + if (!userRef.current) { + console.warn('⚠️ fetchItems: No user available yet, cannot fetch'); return; } @@ -223,6 +250,12 @@ export const ModerationQueue = forwardRef((props, ref) => { return; } + // Skip fetch if triggered during tab visibility change (unless manual refresh) + if (!silent && !isPageVisible.current) { + console.log('👁️ Skipping fetch while page is hidden'); + return; + } + // Cooldown check - prevent rapid-fire calls const now = Date.now(); const timeSinceLastFetch = now - lastFetchTimeRef.current; @@ -740,15 +773,28 @@ export const ModerationQueue = forwardRef((props, ref) => { // Initial fetch on mount and filter changes useEffect(() => { - if (!user) return; + console.log('🎯 Initial fetch effect:', { + hasUser: !!user, + loadingState, + hasInitialFetchRef: hasInitialFetchRef.current, + initialFetchComplete: initialFetchCompleteRef.current, + isMounting: isMountingRef.current + }); + + if (!user) { + console.log('⏳ Waiting for user to be available...'); + return; + } // Phase 1: Initial fetch (run once) if (!hasInitialFetchRef.current) { + console.log('✅ Triggering initial fetch'); hasInitialFetchRef.current = true; isMountingRef.current = true; fetchItems(debouncedEntityFilter, debouncedStatusFilter, false) .then(() => { + console.log('✅ Initial fetch complete'); initialFetchCompleteRef.current = true; // Wait for DOM to paint before allowing subsequent fetches requestAnimationFrame(() => { @@ -760,6 +806,7 @@ export const ModerationQueue = forwardRef((props, ref) => { // Phase 2: Filter changes (only after initial fetch completes) if (!isMountingRef.current && initialFetchCompleteRef.current) { + console.log('🔄 Filter changed, fetching with debounce'); debouncedFetchItems(debouncedEntityFilter, debouncedStatusFilter, true, activeTab); } }, [debouncedEntityFilter, debouncedStatusFilter, user, activeTab, fetchItems, debouncedFetchItems]); diff --git a/src/components/providers/LocationAutoDetectProvider.tsx b/src/components/providers/LocationAutoDetectProvider.tsx index 68213bc0..f03a8b35 100644 --- a/src/components/providers/LocationAutoDetectProvider.tsx +++ b/src/components/providers/LocationAutoDetectProvider.tsx @@ -1,7 +1,11 @@ -import { useEffect } from 'react'; +import { ReactNode } from 'react'; import { useLocationAutoDetect } from '@/hooks/useLocationAutoDetect'; -export function LocationAutoDetectProvider() { +interface LocationAutoDetectProviderProps { + children?: ReactNode; +} + +export function LocationAutoDetectProvider({ children }: LocationAutoDetectProviderProps) { useLocationAutoDetect(); - return null; // This component doesn't render anything, just runs the hook + return <>{children}; } \ No newline at end of file diff --git a/src/hooks/useAuth.tsx b/src/hooks/useAuth.tsx index a3da20bc..e12cb13b 100644 --- a/src/hooks/useAuth.tsx +++ b/src/hooks/useAuth.tsx @@ -109,6 +109,7 @@ function AuthProviderComponent({ children }: { children: React.ReactNode }) { } else { setSession(session); setUser(session?.user ?? null); + setLoading(false); } // Track pending email changes