Fix: Stabilize useAdminSettings dependencies

This commit is contained in:
gpt-engineer-app[bot]
2025-10-13 00:00:06 +00:00
parent e7aa74287a
commit 113f880775
2 changed files with 46 additions and 35 deletions

View File

@@ -806,21 +806,26 @@ export function useModerationQueueManager(
// Visibility change handler // Visibility change handler
useEffect(() => { useEffect(() => {
console.log('🔍 [VISIBILITY EFFECT] Running', { // HARD CHECK: Explicit boolean comparison to prevent any truthy coercion
settingsObject: settings, const isEnabled = settings.refreshOnTabVisible === true;
console.log('🔍 [VISIBILITY EFFECT] Hard check', {
refreshOnTabVisible: settings.refreshOnTabVisible, refreshOnTabVisible: settings.refreshOnTabVisible,
typeOf: typeof settings.refreshOnTabVisible, typeOf: typeof settings.refreshOnTabVisible,
rawValue: JSON.stringify(settings.refreshOnTabVisible), isEnabled,
stringValue: String(settings.refreshOnTabVisible), willAttachListener: isEnabled,
boolValue: Boolean(settings.refreshOnTabVisible),
timestamp: new Date().toISOString() timestamp: new Date().toISOString()
}); });
// Early return if feature is disabled // Early return if feature is disabled
if (!settings.refreshOnTabVisible) { if (!isEnabled) {
console.log(' ✅ Setting is FALSE - NO listener will be attached'); console.log(' ✅ Feature DISABLED - skipping all visibility logic');
console.log(' ✅ Tab focus will NOT trigger refreshes'); 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'); console.error(' ❌ Setting is TRUE - listener WILL be attached');

View File

@@ -3,7 +3,7 @@ import { supabase } from '@/integrations/supabase/client';
import { useAuth } from './useAuth'; import { useAuth } from './useAuth';
import { useUserRole } from './useUserRole'; import { useUserRole } from './useUserRole';
import { useToast } from './use-toast'; import { useToast } from './use-toast';
import { useCallback } from 'react'; import { useCallback, useMemo } from 'react';
interface AdminSetting { interface AdminSetting {
id: string; id: string;
@@ -37,6 +37,14 @@ export function useAdminSettings() {
enabled: !!user && isSuperuser() 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({ const updateSettingMutation = useMutation({
mutationFn: async ({ key, value }: { key: string; value: any }) => { mutationFn: async ({ key, value }: { key: string; value: any }) => {
const { error } = await supabase const { error } = await supabase
@@ -66,19 +74,9 @@ export function useAdminSettings() {
} }
}); });
const getSetting = (key: string) => { const getSettingValue = useCallback((key: string, defaultValue: any = null) => {
return settings?.find(s => s.setting_key === key); return settingsMap[key] ?? defaultValue;
}; }, [settingsMap]);
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 updateSetting = async (key: string, value: any) => { const updateSetting = async (key: string, value: any) => {
return updateSettingMutation.mutateAsync({ key, value }); return updateSettingMutation.mutateAsync({ key, value });
@@ -87,46 +85,46 @@ export function useAdminSettings() {
// Helper functions for common settings (memoized with useCallback for stable references) // Helper functions for common settings (memoized with useCallback for stable references)
const getAutoFlagThreshold = useCallback(() => { const getAutoFlagThreshold = useCallback(() => {
return parseInt(getSettingValue('moderation.auto_flag_threshold', '3')); return parseInt(getSettingValue('moderation.auto_flag_threshold', '3'));
}, [settings]); }, [getSettingValue]);
const getRequireApproval = useCallback(() => { const getRequireApproval = useCallback(() => {
const value = getSettingValue('moderation.require_approval', 'true'); const value = getSettingValue('moderation.require_approval', 'true');
return value === true || value === 'true'; return value === true || value === 'true';
}, [settings]); }, [getSettingValue]);
const getBanDurations = useCallback(() => { const getBanDurations = useCallback(() => {
const value = getSettingValue('moderation.ban_durations', ['1d', '7d', '30d', 'permanent']); const value = getSettingValue('moderation.ban_durations', ['1d', '7d', '30d', 'permanent']);
return Array.isArray(value) ? value : JSON.parse(value || '[]'); return Array.isArray(value) ? value : JSON.parse(value || '[]');
}, [settings]); }, [getSettingValue]);
const getEmailAlertsEnabled = useCallback(() => { const getEmailAlertsEnabled = useCallback(() => {
const value = getSettingValue('notifications.email_alerts', 'true'); const value = getSettingValue('notifications.email_alerts', 'true');
return value === true || value === 'true'; return value === true || value === 'true';
}, [settings]); }, [getSettingValue]);
const getReportThreshold = useCallback(() => { const getReportThreshold = useCallback(() => {
return parseInt(getSettingValue('notifications.report_threshold', '5')); return parseInt(getSettingValue('notifications.report_threshold', '5'));
}, [settings]); }, [getSettingValue]);
const getAuditRetentionDays = useCallback(() => { const getAuditRetentionDays = useCallback(() => {
return parseInt(getSettingValue('system.audit_retention_days', '365')); return parseInt(getSettingValue('system.audit_retention_days', '365'));
}, [settings]); }, [getSettingValue]);
const getAutoCleanupEnabled = useCallback(() => { const getAutoCleanupEnabled = useCallback(() => {
const value = getSettingValue('system.auto_cleanup', 'false'); const value = getSettingValue('system.auto_cleanup', 'false');
return value === true || value === 'true'; return value === true || value === 'true';
}, [settings]); }, [getSettingValue]);
const getAdminPanelRefreshMode = useCallback(() => { const getAdminPanelRefreshMode = useCallback(() => {
const value = getSettingValue('system.admin_panel_refresh_mode', 'auto'); const value = getSettingValue('system.admin_panel_refresh_mode', 'auto');
// Remove quotes if they exist (JSON string stored in DB) // Remove quotes if they exist (JSON string stored in DB)
return typeof value === 'string' ? value.replace(/"/g, '') : value; return typeof value === 'string' ? value.replace(/"/g, '') : value;
}, [settings]); }, [getSettingValue]);
const getAdminPanelPollInterval = useCallback(() => { const getAdminPanelPollInterval = useCallback(() => {
const value = getSettingValue('system.admin_panel_poll_interval', 30); const value = getSettingValue('system.admin_panel_poll_interval', 30);
return parseInt(value?.toString() || '30') * 1000; // Convert to milliseconds return parseInt(value?.toString() || '30') * 1000; // Convert to milliseconds
}, [settings]); }, [getSettingValue]);
/** /**
* Get auto-refresh strategy setting * Get auto-refresh strategy setting
@@ -136,7 +134,7 @@ export function useAdminSettings() {
const value = getSettingValue('auto_refresh_strategy', 'merge'); const value = getSettingValue('auto_refresh_strategy', 'merge');
const cleanValue = typeof value === 'string' ? value.replace(/"/g, '') : value; const cleanValue = typeof value === 'string' ? value.replace(/"/g, '') : value;
return cleanValue as 'merge' | 'replace' | 'notify'; return cleanValue as 'merge' | 'replace' | 'notify';
}, [settings]); }, [getSettingValue]);
/** /**
* Get preserve interaction state setting * Get preserve interaction state setting
@@ -146,22 +144,30 @@ export function useAdminSettings() {
const value = getSettingValue('preserve_interaction_state', 'true'); const value = getSettingValue('preserve_interaction_state', 'true');
const cleanValue = typeof value === 'string' ? value.replace(/"/g, '') : value; const cleanValue = typeof value === 'string' ? value.replace(/"/g, '') : value;
return cleanValue === 'true' || cleanValue === true; return cleanValue === 'true' || cleanValue === true;
}, [settings]); }, [getSettingValue]);
const getNotificationRecipients = useCallback(() => { const getNotificationRecipients = useCallback(() => {
return getSettingValue('notifications.recipients', []); return getSettingValue('notifications.recipients', []);
}, [settings]); }, [getSettingValue]);
const getUseRealtimeQueue = useCallback((): boolean => { const getUseRealtimeQueue = useCallback((): boolean => {
const value = getSettingValue('system.use_realtime_queue', 'true'); const value = getSettingValue('system.use_realtime_queue', 'true');
const cleanValue = typeof value === 'string' ? value.replace(/"/g, '') : value; const cleanValue = typeof value === 'string' ? value.replace(/"/g, '') : value;
return cleanValue === 'true' || cleanValue === true; return cleanValue === 'true' || cleanValue === true;
}, [settings]); }, [getSettingValue]);
const getRefreshOnTabVisible = useCallback((): boolean => { const getRefreshOnTabVisible = useCallback((): boolean => {
const value = getSettingValue('system.refresh_on_tab_visible', 'false'); const value = getSettingValue('system.refresh_on_tab_visible', 'false');
const cleanValue = typeof value === 'string' ? value.replace(/"/g, '') : value; const cleanValue = typeof value === 'string' ? value.replace(/"/g, '') : value;
return cleanValue === 'true' || cleanValue === true; 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]); }, [settings]);
return { return {