diff --git a/src/hooks/moderation/useModerationQueueManager.ts b/src/hooks/moderation/useModerationQueueManager.ts index 2536fa22..ee1915a9 100644 --- a/src/hooks/moderation/useModerationQueueManager.ts +++ b/src/hooks/moderation/useModerationQueueManager.ts @@ -806,21 +806,26 @@ export function useModerationQueueManager( // Visibility change handler useEffect(() => { - console.log('๐Ÿ” [VISIBILITY EFFECT] Running', { - settingsObject: settings, + // HARD CHECK: Explicit boolean comparison to prevent any truthy coercion + const isEnabled = settings.refreshOnTabVisible === true; + + console.log('๐Ÿ” [VISIBILITY EFFECT] Hard check', { refreshOnTabVisible: settings.refreshOnTabVisible, typeOf: typeof settings.refreshOnTabVisible, - rawValue: JSON.stringify(settings.refreshOnTabVisible), - stringValue: String(settings.refreshOnTabVisible), - boolValue: Boolean(settings.refreshOnTabVisible), + isEnabled, + willAttachListener: isEnabled, timestamp: new Date().toISOString() }); // Early return if feature is disabled - if (!settings.refreshOnTabVisible) { - console.log(' โœ… Setting is FALSE - NO listener will be attached'); + if (!isEnabled) { + console.log(' โœ… Feature DISABLED - skipping all visibility logic'); console.log(' โœ… Tab focus will NOT trigger refreshes'); - return; + + // Cleanup: ensure no lingering handlers + return () => { + console.log(' ๐Ÿงน Cleanup: Ensuring no visibility listeners exist'); + }; } console.error(' โŒ Setting is TRUE - listener WILL be attached'); diff --git a/src/hooks/useAdminSettings.ts b/src/hooks/useAdminSettings.ts index 48eaaf6b..9e2b9416 100644 --- a/src/hooks/useAdminSettings.ts +++ b/src/hooks/useAdminSettings.ts @@ -3,7 +3,7 @@ import { supabase } from '@/integrations/supabase/client'; import { useAuth } from './useAuth'; import { useUserRole } from './useUserRole'; import { useToast } from './use-toast'; -import { useCallback } from 'react'; +import { useCallback, useMemo } from 'react'; interface AdminSetting { id: string; @@ -37,6 +37,14 @@ export function useAdminSettings() { enabled: !!user && isSuperuser() }); + // Memoize settings into a stable map to prevent cascading re-renders + const settingsMap = useMemo(() => { + if (!settings) return {}; + return Object.fromEntries( + settings.map(s => [s.setting_key, s.setting_value]) + ); + }, [settings ? JSON.stringify(settings.map(s => [s.setting_key, s.setting_value])) : null]); + const updateSettingMutation = useMutation({ mutationFn: async ({ key, value }: { key: string; value: any }) => { const { error } = await supabase @@ -66,19 +74,9 @@ export function useAdminSettings() { } }); - const getSetting = (key: string) => { - return settings?.find(s => s.setting_key === key); - }; - - const getSettingValue = (key: string, defaultValue: any = null) => { - const setting = getSetting(key); - return setting ? setting.setting_value : defaultValue; - }; - - const getSettingsByCategory = (category: string) => { - return settings?.filter(s => s.category === category) || []; - }; - + const getSettingValue = useCallback((key: string, defaultValue: any = null) => { + return settingsMap[key] ?? defaultValue; + }, [settingsMap]); const updateSetting = async (key: string, value: any) => { return updateSettingMutation.mutateAsync({ key, value }); @@ -87,46 +85,46 @@ export function useAdminSettings() { // Helper functions for common settings (memoized with useCallback for stable references) const getAutoFlagThreshold = useCallback(() => { return parseInt(getSettingValue('moderation.auto_flag_threshold', '3')); - }, [settings]); + }, [getSettingValue]); const getRequireApproval = useCallback(() => { const value = getSettingValue('moderation.require_approval', 'true'); return value === true || value === 'true'; - }, [settings]); + }, [getSettingValue]); const getBanDurations = useCallback(() => { const value = getSettingValue('moderation.ban_durations', ['1d', '7d', '30d', 'permanent']); return Array.isArray(value) ? value : JSON.parse(value || '[]'); - }, [settings]); + }, [getSettingValue]); const getEmailAlertsEnabled = useCallback(() => { const value = getSettingValue('notifications.email_alerts', 'true'); return value === true || value === 'true'; - }, [settings]); + }, [getSettingValue]); const getReportThreshold = useCallback(() => { return parseInt(getSettingValue('notifications.report_threshold', '5')); - }, [settings]); + }, [getSettingValue]); const getAuditRetentionDays = useCallback(() => { return parseInt(getSettingValue('system.audit_retention_days', '365')); - }, [settings]); + }, [getSettingValue]); const getAutoCleanupEnabled = useCallback(() => { const value = getSettingValue('system.auto_cleanup', 'false'); return value === true || value === 'true'; - }, [settings]); + }, [getSettingValue]); const getAdminPanelRefreshMode = useCallback(() => { const value = getSettingValue('system.admin_panel_refresh_mode', 'auto'); // Remove quotes if they exist (JSON string stored in DB) return typeof value === 'string' ? value.replace(/"/g, '') : value; - }, [settings]); + }, [getSettingValue]); const getAdminPanelPollInterval = useCallback(() => { const value = getSettingValue('system.admin_panel_poll_interval', 30); return parseInt(value?.toString() || '30') * 1000; // Convert to milliseconds - }, [settings]); + }, [getSettingValue]); /** * Get auto-refresh strategy setting @@ -136,7 +134,7 @@ export function useAdminSettings() { const value = getSettingValue('auto_refresh_strategy', 'merge'); const cleanValue = typeof value === 'string' ? value.replace(/"/g, '') : value; return cleanValue as 'merge' | 'replace' | 'notify'; - }, [settings]); + }, [getSettingValue]); /** * Get preserve interaction state setting @@ -146,22 +144,30 @@ export function useAdminSettings() { const value = getSettingValue('preserve_interaction_state', 'true'); const cleanValue = typeof value === 'string' ? value.replace(/"/g, '') : value; return cleanValue === 'true' || cleanValue === true; - }, [settings]); + }, [getSettingValue]); const getNotificationRecipients = useCallback(() => { return getSettingValue('notifications.recipients', []); - }, [settings]); + }, [getSettingValue]); const getUseRealtimeQueue = useCallback((): boolean => { const value = getSettingValue('system.use_realtime_queue', 'true'); const cleanValue = typeof value === 'string' ? value.replace(/"/g, '') : value; return cleanValue === 'true' || cleanValue === true; - }, [settings]); + }, [getSettingValue]); const getRefreshOnTabVisible = useCallback((): boolean => { const value = getSettingValue('system.refresh_on_tab_visible', 'false'); const cleanValue = typeof value === 'string' ? value.replace(/"/g, '') : value; return cleanValue === 'true' || cleanValue === true; + }, [getSettingValue]); + + const getSetting = useCallback((key: string) => { + return settings?.find(s => s.setting_key === key); + }, [settings]); + + const getSettingsByCategory = useCallback((category: string) => { + return settings?.filter(s => s.category === category) || []; }, [settings]); return {