mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 14:11:13 -05:00
228 lines
7.5 KiB
TypeScript
228 lines
7.5 KiB
TypeScript
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
import { supabase } from '@/lib/supabaseClient';
|
|
import { useAuth } from './useAuth';
|
|
import { useUserRole } from './useUserRole';
|
|
import { useToast } from './use-toast';
|
|
import { useCallback, useMemo } from 'react';
|
|
import type { Json } from '@/integrations/supabase/types';
|
|
import { handleError } from '@/lib/errorHandler';
|
|
|
|
interface AdminSetting {
|
|
id: string;
|
|
setting_key: string;
|
|
setting_value: unknown;
|
|
category: string;
|
|
description: string;
|
|
}
|
|
|
|
export function useAdminSettings() {
|
|
const { user } = useAuth();
|
|
const { isSuperuser } = useUserRole();
|
|
const { toast } = useToast();
|
|
const queryClient = useQueryClient();
|
|
|
|
const {
|
|
data: settings,
|
|
isLoading,
|
|
error
|
|
} = useQuery({
|
|
queryKey: ['admin-settings'],
|
|
queryFn: async () => {
|
|
const { data, error } = await supabase
|
|
.from('admin_settings')
|
|
.select('*')
|
|
.order('category', { ascending: true });
|
|
|
|
if (error) throw error;
|
|
return data as AdminSetting[];
|
|
},
|
|
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]);
|
|
|
|
const updateSettingMutation = useMutation({
|
|
mutationFn: async ({ key, value }: { key: string; value: unknown }) => {
|
|
const { error } = await supabase
|
|
.from('admin_settings')
|
|
.update({
|
|
setting_value: value as Json,
|
|
updated_by: user?.id,
|
|
updated_at: new Date().toISOString()
|
|
})
|
|
.eq('setting_key', key);
|
|
|
|
if (error) throw error;
|
|
return { key, value };
|
|
},
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['admin-settings'] });
|
|
toast({
|
|
title: "Setting Updated",
|
|
description: "The setting has been saved successfully.",
|
|
});
|
|
},
|
|
onError: (error: Error, variables) => {
|
|
handleError(error, {
|
|
action: 'Update Admin Setting',
|
|
userId: user?.id,
|
|
metadata: {
|
|
settingKey: variables.key,
|
|
attemptedValue: variables.value
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
const getSettingValue = useCallback((key: string, defaultValue: unknown = null) => {
|
|
return settingsMap[key] ?? defaultValue;
|
|
}, [settingsMap]);
|
|
|
|
const updateSetting = async (key: string, value: unknown) => {
|
|
return updateSettingMutation.mutateAsync({ key, value });
|
|
};
|
|
|
|
// Helper functions for common settings (memoized with useCallback for stable references)
|
|
const getAutoFlagThreshold = useCallback(() => {
|
|
return parseInt(String(getSettingValue('moderation.auto_flag_threshold', '3')));
|
|
}, [getSettingValue]);
|
|
|
|
const getRequireApproval = useCallback(() => {
|
|
const value = getSettingValue('moderation.require_approval', 'true');
|
|
return value === true || value === 'true';
|
|
}, [getSettingValue]);
|
|
|
|
const getBanDurations = useCallback(() => {
|
|
const value = getSettingValue('moderation.ban_durations', ['1d', '7d', '30d', 'permanent']);
|
|
return Array.isArray(value) ? value : JSON.parse(String(value || '[]'));
|
|
}, [getSettingValue]);
|
|
|
|
const getEmailAlertsEnabled = useCallback(() => {
|
|
const value = getSettingValue('notifications.email_alerts', 'true');
|
|
return value === true || value === 'true';
|
|
}, [getSettingValue]);
|
|
|
|
const getReportThreshold = useCallback(() => {
|
|
return parseInt(String(getSettingValue('notifications.report_threshold', '5')));
|
|
}, [getSettingValue]);
|
|
|
|
const getAuditRetentionDays = useCallback(() => {
|
|
return parseInt(String(getSettingValue('system.audit_retention_days', '365')));
|
|
}, [getSettingValue]);
|
|
|
|
const getAutoCleanupEnabled = useCallback(() => {
|
|
const value = getSettingValue('system.auto_cleanup', 'false');
|
|
return value === true || value === 'true';
|
|
}, [getSettingValue]);
|
|
|
|
const getAdminPanelRefreshMode = useCallback((): 'auto' | 'manual' => {
|
|
const value = getSettingValue('system.admin_panel_refresh_mode', 'auto');
|
|
// Remove quotes if they exist (JSON string stored in DB)
|
|
const cleanValue = typeof value === 'string' ? value.replace(/"/g, '') : String(value);
|
|
return (cleanValue === 'manual' ? 'manual' : 'auto') as 'auto' | 'manual';
|
|
}, [getSettingValue]);
|
|
|
|
const getAdminPanelPollInterval = useCallback(() => {
|
|
const value = getSettingValue('system.admin_panel_poll_interval', 30);
|
|
return parseInt(value?.toString() || '30') * 1000; // Convert to milliseconds
|
|
}, [getSettingValue]);
|
|
|
|
/**
|
|
* Get auto-refresh strategy setting
|
|
* Returns: 'merge' | 'replace' | 'notify'
|
|
*/
|
|
const getAutoRefreshStrategy = useCallback((): 'merge' | 'replace' | 'notify' => {
|
|
const value = getSettingValue('auto_refresh_strategy', 'merge');
|
|
const cleanValue = typeof value === 'string' ? value.replace(/"/g, '') : value;
|
|
return cleanValue as 'merge' | 'replace' | 'notify';
|
|
}, [getSettingValue]);
|
|
|
|
/**
|
|
* Get preserve interaction state setting
|
|
* Returns: boolean
|
|
*/
|
|
const getPreserveInteractionState = useCallback((): boolean => {
|
|
const value = getSettingValue('preserve_interaction_state', 'true');
|
|
const cleanValue = typeof value === 'string' ? value.replace(/"/g, '') : value;
|
|
return cleanValue === 'true' || cleanValue === true;
|
|
}, [getSettingValue]);
|
|
|
|
const getNotificationRecipients = useCallback(() => {
|
|
return getSettingValue('notifications.recipients', []);
|
|
}, [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;
|
|
}, [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 useMemo(() => ({
|
|
settings,
|
|
isLoading,
|
|
error,
|
|
updateSetting,
|
|
isUpdating: updateSettingMutation.isPending,
|
|
getSetting,
|
|
getSettingValue,
|
|
getSettingsByCategory,
|
|
// Helper functions
|
|
getAutoFlagThreshold,
|
|
getRequireApproval,
|
|
getBanDurations,
|
|
getEmailAlertsEnabled,
|
|
getNotificationRecipients,
|
|
getReportThreshold,
|
|
getAuditRetentionDays,
|
|
getAutoCleanupEnabled,
|
|
getAdminPanelRefreshMode,
|
|
getAdminPanelPollInterval,
|
|
getAutoRefreshStrategy,
|
|
getPreserveInteractionState,
|
|
getUseRealtimeQueue,
|
|
getRefreshOnTabVisible,
|
|
}), [
|
|
settings,
|
|
isLoading,
|
|
error,
|
|
updateSetting,
|
|
updateSettingMutation.isPending,
|
|
getSetting,
|
|
getSettingValue,
|
|
getSettingsByCategory,
|
|
getAutoFlagThreshold,
|
|
getRequireApproval,
|
|
getBanDurations,
|
|
getEmailAlertsEnabled,
|
|
getNotificationRecipients,
|
|
getReportThreshold,
|
|
getAuditRetentionDays,
|
|
getAutoCleanupEnabled,
|
|
getAdminPanelRefreshMode,
|
|
getAdminPanelPollInterval,
|
|
getAutoRefreshStrategy,
|
|
getPreserveInteractionState,
|
|
getUseRealtimeQueue,
|
|
getRefreshOnTabVisible,
|
|
]);
|
|
} |