diff --git a/src/App.tsx b/src/App.tsx index e024f7d0..7ad40b5d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -19,6 +19,7 @@ import Terms from "./pages/Terms"; import Privacy from "./pages/Privacy"; import SubmissionGuidelines from "./pages/SubmissionGuidelines"; import Admin from "./pages/Admin"; +import AdminSettings from "./pages/AdminSettings"; const queryClient = new QueryClient(); @@ -42,6 +43,7 @@ const App = () => ( } /> } /> } /> + } /> } /> } /> } /> diff --git a/src/components/layout/AdminHeader.tsx b/src/components/layout/AdminHeader.tsx index 3f10ff27..42a92a27 100644 --- a/src/components/layout/AdminHeader.tsx +++ b/src/components/layout/AdminHeader.tsx @@ -27,9 +27,11 @@ export function AdminHeader() { {/* Right Section - Admin actions */}
- diff --git a/src/hooks/useAdminSettings.ts b/src/hooks/useAdminSettings.ts new file mode 100644 index 00000000..2d82d811 --- /dev/null +++ b/src/hooks/useAdminSettings.ts @@ -0,0 +1,134 @@ +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { supabase } from '@/integrations/supabase/client'; +import { useAuth } from './useAuth'; +import { useToast } from './use-toast'; + +interface AdminSetting { + id: string; + setting_key: string; + setting_value: any; + category: string; + description: string; +} + +export function useAdminSettings() { + const { user } = useAuth(); + 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 + }); + + const updateSettingMutation = useMutation({ + mutationFn: async ({ key, value }: { key: string; value: any }) => { + const { error } = await supabase + .from('admin_settings') + .update({ + setting_value: value, + updated_by: user?.id, + updated_at: new Date().toISOString() + }) + .eq('setting_key', key); + + if (error) throw error; + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['admin-settings'] }); + toast({ + title: "Setting Updated", + description: "The setting has been saved successfully.", + }); + }, + onError: (error: any) => { + toast({ + title: "Error", + description: error.message || "Failed to update setting", + variant: "destructive", + }); + } + }); + + 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 updateSetting = async (key: string, value: any) => { + return updateSettingMutation.mutateAsync({ key, value }); + }; + + // Helper functions for common settings + const getAutoFlagThreshold = () => { + return parseInt(getSettingValue('moderation.auto_flag_threshold', '3')); + }; + + const getRequireApproval = () => { + const value = getSettingValue('moderation.require_approval', 'true'); + return value === true || value === 'true'; + }; + + const getBanDurations = () => { + const value = getSettingValue('moderation.ban_durations', ['1d', '7d', '30d', 'permanent']); + return Array.isArray(value) ? value : JSON.parse(value || '[]'); + }; + + const getEmailAlertsEnabled = () => { + const value = getSettingValue('notifications.email_alerts', 'true'); + return value === true || value === 'true'; + }; + + const getReportThreshold = () => { + return parseInt(getSettingValue('notifications.report_threshold', '5')); + }; + + const getAuditRetentionDays = () => { + return parseInt(getSettingValue('system.audit_retention_days', '365')); + }; + + const getAutoCleanupEnabled = () => { + const value = getSettingValue('system.auto_cleanup', 'false'); + return value === true || value === 'true'; + }; + + return { + settings, + isLoading, + error, + updateSetting, + isUpdating: updateSettingMutation.isPending, + getSetting, + getSettingValue, + getSettingsByCategory, + // Helper functions + getAutoFlagThreshold, + getRequireApproval, + getBanDurations, + getEmailAlertsEnabled, + getReportThreshold, + getAuditRetentionDays, + getAutoCleanupEnabled, + }; +} \ No newline at end of file diff --git a/src/integrations/supabase/types.ts b/src/integrations/supabase/types.ts index 5d9ed527..59212a3d 100644 --- a/src/integrations/supabase/types.ts +++ b/src/integrations/supabase/types.ts @@ -41,6 +41,39 @@ export type Database = { } Relationships: [] } + admin_settings: { + Row: { + category: string + created_at: string + description: string | null + id: string + setting_key: string + setting_value: Json + updated_at: string + updated_by: string | null + } + Insert: { + category: string + created_at?: string + description?: string | null + id?: string + setting_key: string + setting_value: Json + updated_at?: string + updated_by?: string | null + } + Update: { + category?: string + created_at?: string + description?: string | null + id?: string + setting_key?: string + setting_value?: Json + updated_at?: string + updated_by?: string | null + } + Relationships: [] + } companies: { Row: { average_rating: number | null diff --git a/src/pages/AdminSettings.tsx b/src/pages/AdminSettings.tsx new file mode 100644 index 00000000..240f7b30 --- /dev/null +++ b/src/pages/AdminSettings.tsx @@ -0,0 +1,296 @@ +import { useState } from 'react'; +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Switch } from '@/components/ui/switch'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { Textarea } from '@/components/ui/textarea'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { AdminHeader } from '@/components/layout/AdminHeader'; +import { useAuth } from '@/hooks/useAuth'; +import { useUserRole } from '@/hooks/useUserRole'; +import { supabase } from '@/integrations/supabase/client'; +import { useToast } from '@/hooks/use-toast'; +import { Loader2, Save } from 'lucide-react'; + +interface AdminSetting { + id: string; + setting_key: string; + setting_value: any; + category: string; + description: string; +} + +export default function AdminSettings() { + const { user } = useAuth(); + const { permissions, loading: roleLoading } = useUserRole(); + const { toast } = useToast(); + const queryClient = useQueryClient(); + const [hasChanges, setHasChanges] = useState(false); + + // Fetch admin settings + const { data: settings, isLoading } = 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 && permissions?.role_level !== 'user' + }); + + // Update settings mutation + const updateSettingMutation = useMutation({ + mutationFn: async ({ key, value }: { key: string; value: any }) => { + const { error } = await supabase + .from('admin_settings') + .update({ + setting_value: value, + updated_by: user?.id, + updated_at: new Date().toISOString() + }) + .eq('setting_key', key); + + if (error) throw error; + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['admin-settings'] }); + setHasChanges(false); + toast({ + title: "Settings Updated", + description: "Admin settings have been saved successfully.", + }); + }, + onError: (error: any) => { + toast({ + title: "Error", + description: error.message || "Failed to update settings", + variant: "destructive", + }); + } + }); + + if (roleLoading || isLoading) { + return ( + <> + +
+ +
+ + ); + } + + if (!user || permissions?.role_level === 'user') { + return ( + <> + +
+
+

Access Denied

+

You don't have permission to access admin settings.

+
+
+ + ); + } + + const getSettingsByCategory = (category: string) => { + return settings?.filter(s => s.category === category) || []; + }; + + const handleSettingChange = (key: string, value: any) => { + setHasChanges(true); + // You could implement local state management here for real-time updates + }; + + const handleSave = async (key: string, value: any) => { + await updateSettingMutation.mutateAsync({ key, value }); + }; + + const SettingInput = ({ setting }: { setting: AdminSetting }) => { + const [localValue, setLocalValue] = useState(setting.setting_value); + + const handleSubmit = async () => { + await handleSave(setting.setting_key, localValue); + }; + + if (setting.setting_key.includes('threshold') || setting.setting_key.includes('retention_days')) { + return ( +
+ +
+ { + setLocalValue(parseInt(e.target.value)); + handleSettingChange(setting.setting_key, parseInt(e.target.value)); + }} + /> + +
+
+ ); + } + + if (setting.setting_key.includes('email_alerts') || setting.setting_key.includes('require_approval') || setting.setting_key.includes('auto_cleanup')) { + return ( +
+
+ +
+ { + setLocalValue(checked); + handleSave(setting.setting_key, checked); + }} + /> +
+ ); + } + + if (setting.setting_key === 'moderation.ban_durations') { + return ( +
+ +
+