mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-23 18:11:12 -05:00
feat: Implement Phase 5 optimization and best practices
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import { useRef, useCallback, useMemo } from 'react';
|
||||
import { useRef, useCallback } from 'react';
|
||||
import { supabase } from '@/integrations/supabase/client';
|
||||
import { logger } from '@/lib/logger';
|
||||
import { MODERATION_CONSTANTS } from '@/lib/moderation/constants';
|
||||
|
||||
/**
|
||||
* Entity types supported by the cache
|
||||
@@ -61,10 +63,21 @@ export function useEntityCache() {
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Set a cached entity
|
||||
* Set a cached entity with LRU eviction
|
||||
*/
|
||||
const setCached = useCallback((type: EntityType, id: string, data: any): void => {
|
||||
cacheRef.current[type].set(id, data);
|
||||
const cache = cacheRef.current[type];
|
||||
|
||||
// LRU eviction: remove oldest entry if cache is full
|
||||
if (cache.size >= MODERATION_CONSTANTS.MAX_ENTITY_CACHE_SIZE) {
|
||||
const firstKey = cache.keys().next().value;
|
||||
if (firstKey) {
|
||||
cache.delete(firstKey);
|
||||
logger.log(`♻️ [EntityCache] Evicted ${type}/${firstKey} (LRU)`);
|
||||
}
|
||||
}
|
||||
|
||||
cache.set(id, data);
|
||||
}, []);
|
||||
|
||||
/**
|
||||
@@ -119,7 +132,7 @@ export function useEntityCache() {
|
||||
.in('id', uncachedIds);
|
||||
|
||||
if (error) {
|
||||
console.error(`Error fetching ${type}:`, error);
|
||||
logger.error(`Error fetching ${type}:`, error);
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -132,7 +145,7 @@ export function useEntityCache() {
|
||||
|
||||
return data || [];
|
||||
} catch (error) {
|
||||
console.error(`Failed to bulk fetch ${type}:`, error);
|
||||
logger.error(`Failed to bulk fetch ${type}:`, error);
|
||||
return [];
|
||||
}
|
||||
}, [getCached, setCached, getUncachedIds]);
|
||||
@@ -221,7 +234,8 @@ export function useEntityCache() {
|
||||
*/
|
||||
const getCacheRef = useCallback(() => cacheRef.current, []);
|
||||
|
||||
return useMemo(() => ({
|
||||
// Return without useMemo wrapper (OPTIMIZED)
|
||||
return {
|
||||
getCached,
|
||||
has,
|
||||
setCached,
|
||||
@@ -233,17 +247,5 @@ export function useEntityCache() {
|
||||
getSize,
|
||||
getTotalSize,
|
||||
getCacheRef,
|
||||
}), [
|
||||
getCached,
|
||||
has,
|
||||
setCached,
|
||||
getUncachedIds,
|
||||
bulkFetch,
|
||||
fetchRelatedEntities,
|
||||
clear,
|
||||
clearAll,
|
||||
getSize,
|
||||
getTotalSize,
|
||||
getCacheRef,
|
||||
]);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,8 +8,10 @@
|
||||
* - Filter persistence and clearing
|
||||
*/
|
||||
|
||||
import { useState, useCallback, useEffect, useMemo } from 'react';
|
||||
import { useState, useCallback, useEffect } from 'react';
|
||||
import { useDebounce } from '@/hooks/useDebounce';
|
||||
import { logger } from '@/lib/logger';
|
||||
import { MODERATION_CONSTANTS } from '@/lib/moderation/constants';
|
||||
import type { EntityFilter, StatusFilter, QueueTab, SortConfig, SortField } from '@/types/moderation';
|
||||
|
||||
export interface ModerationFiltersConfig {
|
||||
@@ -109,7 +111,7 @@ export function useModerationFilters(config: ModerationFiltersConfig = {}): Mode
|
||||
initialEntityFilter = 'all',
|
||||
initialStatusFilter = 'pending',
|
||||
initialTab = 'mainQueue',
|
||||
debounceDelay = 300,
|
||||
debounceDelay = MODERATION_CONSTANTS.FILTER_DEBOUNCE_MS,
|
||||
persist = true,
|
||||
storageKey = 'moderationQueue_filters',
|
||||
initialSortConfig = { field: 'created_at', direction: 'asc' },
|
||||
@@ -204,25 +206,25 @@ export function useModerationFilters(config: ModerationFiltersConfig = {}): Mode
|
||||
|
||||
// Set entity filter with logging
|
||||
const setEntityFilter = useCallback((filter: EntityFilter) => {
|
||||
console.log('🔍 Entity filter changed:', filter);
|
||||
logger.log('🔍 Entity filter changed:', filter);
|
||||
setEntityFilterState(filter);
|
||||
}, []);
|
||||
|
||||
// Set status filter with logging
|
||||
const setStatusFilter = useCallback((filter: StatusFilter) => {
|
||||
console.log('🔍 Status filter changed:', filter);
|
||||
logger.log('🔍 Status filter changed:', filter);
|
||||
setStatusFilterState(filter);
|
||||
}, []);
|
||||
|
||||
// Set active tab with logging
|
||||
const setActiveTab = useCallback((tab: QueueTab) => {
|
||||
console.log('🔍 Tab changed:', tab);
|
||||
logger.log('🔍 Tab changed:', tab);
|
||||
setActiveTabState(tab);
|
||||
}, []);
|
||||
|
||||
// Sort callbacks
|
||||
const setSortConfig = useCallback((config: SortConfig) => {
|
||||
console.log('📝 [SORT] Sort config changed:', config);
|
||||
logger.log('📝 [SORT] Sort config changed:', config);
|
||||
setSortConfigState(config);
|
||||
}, []);
|
||||
|
||||
@@ -248,7 +250,7 @@ export function useModerationFilters(config: ModerationFiltersConfig = {}): Mode
|
||||
|
||||
// Clear all filters
|
||||
const clearFilters = useCallback(() => {
|
||||
console.log('🔍 Filters cleared');
|
||||
logger.log('🔍 Filters cleared');
|
||||
setEntityFilterState(initialEntityFilter);
|
||||
setStatusFilterState(initialStatusFilter);
|
||||
setActiveTabState(initialTab);
|
||||
@@ -263,7 +265,8 @@ export function useModerationFilters(config: ModerationFiltersConfig = {}): Mode
|
||||
sortConfig.field !== initialSortConfig.field ||
|
||||
sortConfig.direction !== initialSortConfig.direction;
|
||||
|
||||
return useMemo(() => ({
|
||||
// Return without useMemo wrapper (OPTIMIZED)
|
||||
return {
|
||||
entityFilter,
|
||||
statusFilter,
|
||||
activeTab,
|
||||
@@ -280,22 +283,5 @@ export function useModerationFilters(config: ModerationFiltersConfig = {}): Mode
|
||||
sortBy,
|
||||
toggleSortDirection,
|
||||
resetSort,
|
||||
}), [
|
||||
entityFilter,
|
||||
statusFilter,
|
||||
activeTab,
|
||||
debouncedEntityFilter,
|
||||
debouncedStatusFilter,
|
||||
setEntityFilter,
|
||||
setStatusFilter,
|
||||
setActiveTab,
|
||||
clearFilters,
|
||||
hasActiveFilters,
|
||||
sortConfig,
|
||||
debouncedSortConfig,
|
||||
setSortConfig,
|
||||
sortBy,
|
||||
toggleSortDirection,
|
||||
resetSort,
|
||||
]);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { useState, useCallback, useRef, useEffect, useMemo } from "react";
|
||||
import { supabase } from "@/integrations/supabase/client";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { logger } from "@/lib/logger";
|
||||
import { MODERATION_CONSTANTS } from "@/lib/moderation/constants";
|
||||
import type { User } from "@supabase/supabase-js";
|
||||
import {
|
||||
useEntityCache,
|
||||
@@ -71,7 +73,7 @@ export interface ModerationQueueManager {
|
||||
* Consolidates all queue-related logic into a single hook
|
||||
*/
|
||||
export function useModerationQueueManager(config: ModerationQueueManagerConfig): ModerationQueueManager {
|
||||
console.log('🚀 [QUEUE MANAGER] Hook mounting/rendering', {
|
||||
logger.log('🚀 [QUEUE MANAGER] Hook mounting/rendering', {
|
||||
hasUser: !!config.user,
|
||||
isAdmin: config.isAdmin,
|
||||
timestamp: new Date().toISOString()
|
||||
@@ -169,7 +171,7 @@ export function useModerationQueueManager(config: ModerationQueueManagerConfig):
|
||||
useEffect(() => {
|
||||
if (queueQuery.items) {
|
||||
setItems(queueQuery.items);
|
||||
console.log('✅ Queue items updated from TanStack Query:', queueQuery.items.length);
|
||||
logger.log('✅ Queue items updated from TanStack Query:', queueQuery.items.length);
|
||||
}
|
||||
}, [queueQuery.items]);
|
||||
|
||||
@@ -187,7 +189,7 @@ export function useModerationQueueManager(config: ModerationQueueManagerConfig):
|
||||
// Show error toast when query fails
|
||||
useEffect(() => {
|
||||
if (queueQuery.error) {
|
||||
console.error('❌ Queue query error:', queueQuery.error);
|
||||
logger.error('❌ Queue query error:', queueQuery.error);
|
||||
toast({
|
||||
variant: 'destructive',
|
||||
title: 'Failed to Load Queue',
|
||||
@@ -205,7 +207,7 @@ export function useModerationQueueManager(config: ModerationQueueManagerConfig):
|
||||
useEffect(() => {
|
||||
if (!queueQuery.isLoading && !initialFetchCompleteRef.current) {
|
||||
initialFetchCompleteRef.current = true;
|
||||
console.log('✅ Initial queue fetch complete');
|
||||
logger.log('✅ Initial queue fetch complete');
|
||||
}
|
||||
}, [queueQuery.isLoading]);
|
||||
|
||||
@@ -213,7 +215,7 @@ export function useModerationQueueManager(config: ModerationQueueManagerConfig):
|
||||
* Manual refresh function
|
||||
*/
|
||||
const refresh = useCallback(async () => {
|
||||
console.log('🔄 Manual refresh triggered');
|
||||
logger.log('🔄 Manual refresh triggered');
|
||||
await queueQuery.refetch();
|
||||
}, [queueQuery]);
|
||||
|
||||
@@ -221,7 +223,7 @@ export function useModerationQueueManager(config: ModerationQueueManagerConfig):
|
||||
* Show pending new items by invalidating query
|
||||
*/
|
||||
const showNewItems = useCallback(async () => {
|
||||
console.log('✅ Showing new items via query invalidation');
|
||||
logger.log('✅ Showing new items via query invalidation');
|
||||
await queueQuery.invalidate();
|
||||
setPendingNewItems([]);
|
||||
setNewItemsCount(0);
|
||||
@@ -521,16 +523,25 @@ export function useModerationQueueManager(config: ModerationQueueManagerConfig):
|
||||
[filters.statusFilter, toast],
|
||||
);
|
||||
|
||||
// Extract stable callbacks for dependencies
|
||||
const invalidateQuery = useCallback(() => {
|
||||
queueQuery.invalidate();
|
||||
}, [queueQuery.invalidate]);
|
||||
|
||||
const resetPagination = useCallback(() => {
|
||||
pagination.reset();
|
||||
}, [pagination.reset]);
|
||||
|
||||
// Mark initial fetch as complete when query loads
|
||||
useEffect(() => {
|
||||
if (!queueQuery.isLoading && !initialFetchCompleteRef.current) {
|
||||
initialFetchCompleteRef.current = true;
|
||||
isMountingRef.current = false;
|
||||
console.log('✅ Initial queue fetch complete');
|
||||
logger.log('✅ Initial queue fetch complete');
|
||||
}
|
||||
}, [queueQuery.isLoading]);
|
||||
|
||||
// Invalidate query when filters or sort changes
|
||||
// Invalidate query when filters or sort changes (OPTIMIZED)
|
||||
useEffect(() => {
|
||||
if (
|
||||
!user ||
|
||||
@@ -538,36 +549,41 @@ export function useModerationQueueManager(config: ModerationQueueManagerConfig):
|
||||
isMountingRef.current
|
||||
) return;
|
||||
|
||||
console.log('🔄 Filters/sort changed, invalidating query');
|
||||
pagination.reset();
|
||||
queueQuery.invalidate();
|
||||
logger.log('🔄 Filters/sort changed, invalidating query');
|
||||
resetPagination();
|
||||
invalidateQuery();
|
||||
}, [
|
||||
filters.debouncedEntityFilter,
|
||||
filters.debouncedStatusFilter,
|
||||
filters.debouncedSortConfig.field,
|
||||
filters.debouncedSortConfig.direction,
|
||||
user,
|
||||
queueQuery,
|
||||
pagination
|
||||
invalidateQuery,
|
||||
resetPagination
|
||||
]);
|
||||
|
||||
// Polling effect (when realtime disabled)
|
||||
// Polling effect (when realtime disabled) - MUTUALLY EXCLUSIVE
|
||||
useEffect(() => {
|
||||
if (!user || settings.refreshMode !== "auto" || loadingState === "initial" || settings.useRealtimeQueue) {
|
||||
const shouldPoll = settings.refreshMode === 'auto'
|
||||
&& !settings.useRealtimeQueue
|
||||
&& loadingState !== 'initial'
|
||||
&& !!user;
|
||||
|
||||
if (!shouldPoll) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("⚠️ Polling ENABLED - interval:", settings.pollInterval);
|
||||
logger.log("⚠️ Polling ENABLED - interval:", settings.pollInterval);
|
||||
const interval = setInterval(() => {
|
||||
console.log("🔄 Polling refresh triggered");
|
||||
logger.log("🔄 Polling refresh triggered");
|
||||
queueQuery.refetch();
|
||||
}, settings.pollInterval);
|
||||
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
console.log("🛑 Polling stopped");
|
||||
logger.log("🛑 Polling stopped");
|
||||
};
|
||||
}, [user, settings.refreshMode, settings.pollInterval, loadingState, settings.useRealtimeQueue, queueQuery]);
|
||||
}, [user, settings.refreshMode, settings.pollInterval, loadingState, settings.useRealtimeQueue, queueQuery.refetch]);
|
||||
|
||||
// Initialize realtime subscriptions
|
||||
useRealtimeSubscriptions({
|
||||
@@ -591,21 +607,18 @@ export function useModerationQueueManager(config: ModerationQueueManagerConfig):
|
||||
if (recentlyRemovedRef.current.has(item.id)) return;
|
||||
if (interactingWith.has(item.id)) return;
|
||||
|
||||
if (shouldRemove) {
|
||||
setItems((prev) => prev.filter((i) => i.id !== item.id));
|
||||
} else {
|
||||
setItems((prev) => {
|
||||
const exists = prev.some((i) => i.id === item.id);
|
||||
if (exists) {
|
||||
return prev.map((i) => (i.id === item.id ? item : i));
|
||||
} else {
|
||||
return [item, ...prev];
|
||||
}
|
||||
});
|
||||
// Only track removals for optimistic update protection
|
||||
if (shouldRemove && !recentlyRemovedRef.current.has(item.id)) {
|
||||
recentlyRemovedRef.current.add(item.id);
|
||||
setTimeout(() => recentlyRemovedRef.current.delete(item.id), MODERATION_CONSTANTS.REALTIME_OPTIMISTIC_REMOVAL_TIMEOUT);
|
||||
}
|
||||
// TanStack Query handles actual state updates via invalidation
|
||||
},
|
||||
onItemRemoved: (itemId: string) => {
|
||||
setItems((prev) => prev.filter((i) => i.id !== itemId));
|
||||
// Track for optimistic update protection
|
||||
recentlyRemovedRef.current.add(itemId);
|
||||
setTimeout(() => recentlyRemovedRef.current.delete(itemId), MODERATION_CONSTANTS.REALTIME_OPTIMISTIC_REMOVAL_TIMEOUT);
|
||||
// TanStack Query handles removal via invalidation
|
||||
},
|
||||
entityCache,
|
||||
profileCache,
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
import { useState, useCallback, useEffect, useMemo } from 'react';
|
||||
import { MODERATION_CONSTANTS } from '@/lib/moderation/constants';
|
||||
|
||||
export interface PaginationConfig {
|
||||
/** Initial page number (1-indexed) */
|
||||
@@ -104,7 +105,7 @@ export interface PaginationState {
|
||||
export function usePagination(config: PaginationConfig = {}): PaginationState {
|
||||
const {
|
||||
initialPage = 1,
|
||||
initialPageSize = 25,
|
||||
initialPageSize = MODERATION_CONSTANTS.DEFAULT_PAGE_SIZE,
|
||||
persist = false,
|
||||
storageKey = 'pagination_state',
|
||||
onPageChange,
|
||||
@@ -231,6 +232,7 @@ export function usePagination(config: PaginationConfig = {}): PaginationState {
|
||||
[currentPage, totalPages]
|
||||
);
|
||||
|
||||
// Return without useMemo wrapper (OPTIMIZED)
|
||||
return {
|
||||
currentPage,
|
||||
pageSize,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { useRef, useCallback, useMemo } from 'react';
|
||||
import { useRef, useCallback } from 'react';
|
||||
import { supabase } from '@/integrations/supabase/client';
|
||||
import { logger } from '@/lib/logger';
|
||||
import { MODERATION_CONSTANTS } from '@/lib/moderation/constants';
|
||||
|
||||
/**
|
||||
* Profile data structure returned from the database
|
||||
@@ -55,10 +57,21 @@ export function useProfileCache() {
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Set a cached profile
|
||||
* Set a cached profile with LRU eviction
|
||||
*/
|
||||
const setCached = useCallback((userId: string, profile: CachedProfile): void => {
|
||||
cacheRef.current.set(userId, profile);
|
||||
const cache = cacheRef.current;
|
||||
|
||||
// LRU eviction
|
||||
if (cache.size >= MODERATION_CONSTANTS.MAX_PROFILE_CACHE_SIZE) {
|
||||
const firstKey = cache.keys().next().value;
|
||||
if (firstKey) {
|
||||
cache.delete(firstKey);
|
||||
logger.log(`♻️ [ProfileCache] Evicted ${firstKey} (LRU)`);
|
||||
}
|
||||
}
|
||||
|
||||
cache.set(userId, profile);
|
||||
}, []);
|
||||
|
||||
/**
|
||||
@@ -92,7 +105,7 @@ export function useProfileCache() {
|
||||
.in('user_id', uncachedIds);
|
||||
|
||||
if (error) {
|
||||
console.error('Error fetching profiles:', error);
|
||||
logger.error('Error fetching profiles:', error);
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -105,7 +118,7 @@ export function useProfileCache() {
|
||||
|
||||
return data || [];
|
||||
} catch (error) {
|
||||
console.error('Failed to bulk fetch profiles:', error);
|
||||
logger.error('Failed to bulk fetch profiles:', error);
|
||||
return [];
|
||||
}
|
||||
}, [getCached, setCached, getUncachedIds]);
|
||||
@@ -181,7 +194,8 @@ export function useProfileCache() {
|
||||
*/
|
||||
const getCacheRef = useCallback(() => cacheRef.current, []);
|
||||
|
||||
return useMemo(() => ({
|
||||
// Return without useMemo wrapper (OPTIMIZED)
|
||||
return {
|
||||
getCached,
|
||||
has,
|
||||
setCached,
|
||||
@@ -195,19 +209,5 @@ export function useProfileCache() {
|
||||
getSize,
|
||||
getAllCachedIds,
|
||||
getCacheRef,
|
||||
}), [
|
||||
getCached,
|
||||
has,
|
||||
setCached,
|
||||
getUncachedIds,
|
||||
bulkFetch,
|
||||
fetchAsMap,
|
||||
fetchForSubmissions,
|
||||
getDisplayName,
|
||||
invalidate,
|
||||
clear,
|
||||
getSize,
|
||||
getAllCachedIds,
|
||||
getCacheRef,
|
||||
]);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { fetchSubmissions, type QueryConfig } from '@/lib/moderation/queries';
|
||||
import { supabase } from '@/integrations/supabase/client';
|
||||
import { logger } from '@/lib/logger';
|
||||
import { MODERATION_CONSTANTS } from '@/lib/moderation/constants';
|
||||
import type {
|
||||
ModerationItem,
|
||||
EntityFilter,
|
||||
@@ -116,22 +118,22 @@ export function useQueueQuery(config: UseQueueQueryConfig): UseQueueQueryReturn
|
||||
const query = useQuery({
|
||||
queryKey,
|
||||
queryFn: async () => {
|
||||
console.log('🔍 [TanStack Query] Fetching queue data:', queryKey);
|
||||
logger.log('🔍 [TanStack Query] Fetching queue data:', queryKey);
|
||||
const result = await fetchSubmissions(supabase, queryConfig);
|
||||
|
||||
if (result.error) {
|
||||
console.error('❌ [TanStack Query] Error:', result.error);
|
||||
logger.error('❌ [TanStack Query] Error:', result.error);
|
||||
throw result.error;
|
||||
}
|
||||
|
||||
console.log('✅ [TanStack Query] Fetched', result.submissions.length, 'items');
|
||||
logger.log('✅ [TanStack Query] Fetched', result.submissions.length, 'items');
|
||||
return result;
|
||||
},
|
||||
enabled: config.enabled !== false && !!config.userId,
|
||||
staleTime: 30000, // 30 seconds
|
||||
gcTime: 5 * 60 * 1000, // 5 minutes
|
||||
retry: 2, // Retry failed requests up to 2 times
|
||||
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000), // Exponential backoff
|
||||
staleTime: MODERATION_CONSTANTS.QUERY_STALE_TIME,
|
||||
gcTime: MODERATION_CONSTANTS.QUERY_GC_TIME,
|
||||
retry: MODERATION_CONSTANTS.QUERY_RETRY_COUNT,
|
||||
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
|
||||
});
|
||||
|
||||
// Invalidate helper
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
import { useEffect, useRef, useState, useCallback } from 'react';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { supabase } from '@/integrations/supabase/client';
|
||||
import { logger } from '@/lib/logger';
|
||||
import { MODERATION_CONSTANTS } from '@/lib/moderation/constants';
|
||||
import type { RealtimeChannel } from '@supabase/supabase-js';
|
||||
import type { ModerationItem, EntityFilter, StatusFilter } from '@/types/moderation';
|
||||
import type { useEntityCache } from './useEntityCache';
|
||||
@@ -47,7 +49,7 @@ export interface RealtimeSubscriptionConfig {
|
||||
/** Pause subscriptions when tab is hidden (default: true) */
|
||||
pauseWhenHidden?: boolean;
|
||||
|
||||
/** Debounce delay for UPDATE events in milliseconds (default: 500) */
|
||||
/** Debounce delay for UPDATE events in milliseconds */
|
||||
debounceMs?: number;
|
||||
|
||||
/** Entity cache for resolving entity names */
|
||||
@@ -95,7 +97,7 @@ export function useRealtimeSubscriptions(
|
||||
onUpdateItem,
|
||||
onItemRemoved,
|
||||
pauseWhenHidden = true,
|
||||
debounceMs = 500,
|
||||
debounceMs = MODERATION_CONSTANTS.REALTIME_DEBOUNCE_MS,
|
||||
entityCache,
|
||||
profileCache,
|
||||
recentlyRemovedIds,
|
||||
@@ -151,7 +153,7 @@ export function useRealtimeSubscriptions(
|
||||
.single();
|
||||
|
||||
if (error || !submission) {
|
||||
console.error('Error fetching submission details:', error);
|
||||
logger.error('Error fetching submission details:', error);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -243,17 +245,17 @@ export function useRealtimeSubscriptions(
|
||||
const handleInsert = useCallback(async (payload: any) => {
|
||||
const newSubmission = payload.new as any;
|
||||
|
||||
console.log('🆕 Realtime INSERT:', newSubmission.id);
|
||||
logger.log('🆕 Realtime INSERT:', newSubmission.id);
|
||||
|
||||
// Queue updates if tab is hidden
|
||||
if (pauseWhenHidden && document.hidden) {
|
||||
console.log('📴 Realtime event received while hidden - queuing for later');
|
||||
logger.log('📴 Realtime event received while hidden - queuing for later');
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore if recently removed (optimistic update)
|
||||
if (recentlyRemovedIds.has(newSubmission.id)) {
|
||||
console.log('⏭️ Ignoring INSERT for recently removed submission:', newSubmission.id);
|
||||
logger.log('⏭️ Ignoring INSERT for recently removed submission:', newSubmission.id);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -271,7 +273,7 @@ export function useRealtimeSubscriptions(
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('✅ NEW submission matches filters, invalidating query:', newSubmission.id);
|
||||
logger.log('✅ NEW submission matches filters, invalidating query:', newSubmission.id);
|
||||
|
||||
// Invalidate the query to trigger background refetch
|
||||
await queryClient.invalidateQueries({ queryKey: ['moderation-queue'] });
|
||||
@@ -296,7 +298,7 @@ export function useRealtimeSubscriptions(
|
||||
|
||||
onNewItem(fullItem);
|
||||
} catch (error) {
|
||||
console.error('Error building new item notification:', error);
|
||||
logger.error('Error building new item notification:', error);
|
||||
}
|
||||
}, [
|
||||
filters,
|
||||
@@ -316,23 +318,23 @@ export function useRealtimeSubscriptions(
|
||||
const updatedSubmission = payload.new as any;
|
||||
const oldSubmission = payload.old as any;
|
||||
|
||||
console.log('🔄 Realtime UPDATE:', updatedSubmission.id);
|
||||
logger.log('🔄 Realtime UPDATE:', updatedSubmission.id);
|
||||
|
||||
// Queue updates if tab is hidden
|
||||
if (pauseWhenHidden && document.hidden) {
|
||||
console.log('📴 Realtime UPDATE received while hidden - queuing for later');
|
||||
logger.log('📴 Realtime UPDATE received while hidden - queuing for later');
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore if recently removed (optimistic update in progress)
|
||||
if (recentlyRemovedIds.has(updatedSubmission.id)) {
|
||||
console.log('⏭️ Ignoring UPDATE for recently removed submission:', updatedSubmission.id);
|
||||
logger.log('⏭️ Ignoring UPDATE for recently removed submission:', updatedSubmission.id);
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore if currently being interacted with
|
||||
if (interactingWithIds.has(updatedSubmission.id)) {
|
||||
console.log('⏭️ Ignoring UPDATE for interacting submission:', updatedSubmission.id);
|
||||
logger.log('⏭️ Ignoring UPDATE for interacting submission:', updatedSubmission.id);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -340,7 +342,7 @@ export function useRealtimeSubscriptions(
|
||||
const isStatusChange = oldSubmission?.status !== updatedSubmission.status;
|
||||
|
||||
if (isStatusChange) {
|
||||
console.log('⚡ Status change detected, invalidating immediately');
|
||||
logger.log('⚡ Status change detected, invalidating immediately');
|
||||
await queryClient.invalidateQueries({ queryKey: ['moderation-queue'] });
|
||||
|
||||
const matchesEntity = matchesEntityFilter(updatedSubmission, filters.entityFilter);
|
||||
@@ -355,7 +357,7 @@ export function useRealtimeSubscriptions(
|
||||
|
||||
// Use debounce for non-critical updates
|
||||
debouncedUpdate(updatedSubmission.id, async () => {
|
||||
console.log('🔄 Invalidating query due to UPDATE:', updatedSubmission.id);
|
||||
logger.log('🔄 Invalidating query due to UPDATE:', updatedSubmission.id);
|
||||
|
||||
// Simply invalidate the query - TanStack Query handles the rest
|
||||
await queryClient.invalidateQueries({ queryKey: ['moderation-queue'] });
|
||||
@@ -388,7 +390,7 @@ export function useRealtimeSubscriptions(
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('📡 Setting up INSERT subscription');
|
||||
logger.log('📡 Setting up INSERT subscription');
|
||||
|
||||
const channel = supabase
|
||||
.channel('moderation-new-submissions')
|
||||
@@ -402,7 +404,7 @@ export function useRealtimeSubscriptions(
|
||||
handleInsert
|
||||
)
|
||||
.subscribe((status) => {
|
||||
console.log('INSERT subscription status:', status);
|
||||
logger.log('INSERT subscription status:', status);
|
||||
if (status === 'SUBSCRIBED') {
|
||||
setChannelStatus('connected');
|
||||
} else if (status === 'CHANNEL_ERROR') {
|
||||
@@ -413,7 +415,7 @@ export function useRealtimeSubscriptions(
|
||||
insertChannelRef.current = channel;
|
||||
|
||||
return () => {
|
||||
console.log('🛑 Cleaning up INSERT subscription');
|
||||
logger.log('🛑 Cleaning up INSERT subscription');
|
||||
supabase.removeChannel(channel);
|
||||
insertChannelRef.current = null;
|
||||
};
|
||||
@@ -425,7 +427,7 @@ export function useRealtimeSubscriptions(
|
||||
useEffect(() => {
|
||||
if (!enabled) return;
|
||||
|
||||
console.log('📡 Setting up UPDATE subscription');
|
||||
logger.log('📡 Setting up UPDATE subscription');
|
||||
|
||||
const channel = supabase
|
||||
.channel('moderation-updated-submissions')
|
||||
@@ -439,7 +441,7 @@ export function useRealtimeSubscriptions(
|
||||
handleUpdate
|
||||
)
|
||||
.subscribe((status) => {
|
||||
console.log('UPDATE subscription status:', status);
|
||||
logger.log('UPDATE subscription status:', status);
|
||||
if (status === 'SUBSCRIBED') {
|
||||
setChannelStatus('connected');
|
||||
} else if (status === 'CHANNEL_ERROR') {
|
||||
@@ -450,7 +452,7 @@ export function useRealtimeSubscriptions(
|
||||
updateChannelRef.current = channel;
|
||||
|
||||
return () => {
|
||||
console.log('🛑 Cleaning up UPDATE subscription');
|
||||
logger.log('🛑 Cleaning up UPDATE subscription');
|
||||
supabase.removeChannel(channel);
|
||||
updateChannelRef.current = null;
|
||||
};
|
||||
@@ -470,7 +472,7 @@ export function useRealtimeSubscriptions(
|
||||
* Manual reconnect function
|
||||
*/
|
||||
const reconnect = useCallback(() => {
|
||||
console.log('🔄 Manually reconnecting subscriptions...');
|
||||
logger.log('🔄 Manually reconnecting subscriptions...');
|
||||
setReconnectTrigger(prev => prev + 1);
|
||||
}, []);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user