Files
thrilltrack-explorer/src/pages/AdminSettings.tsx
gpt-engineer-app[bot] ad31be1622 Combine Testing UIs
Merge Testing and Integration Tests into a single Testing control center in AdminSettings. Remove the separate integration-tests tab, and update the Testing tab to render both Test Data Generator and Integration Test Runner together, with appropriate headers and icons (Database and TestTube). Add Database to imports.
2025-11-10 16:44:31 +00:00

1000 lines
42 KiB
TypeScript

import { useState } from 'react';
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 { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Badge } from '@/components/ui/badge';
import { AdminLayout } from '@/components/layout/AdminLayout';
import { useAuth } from '@/hooks/useAuth';
import { useUserRole } from '@/hooks/useUserRole';
import { useAdminSettings } from '@/hooks/useAdminSettings';
import { NovuMigrationUtility } from '@/components/admin/NovuMigrationUtility';
import { TestDataGenerator } from '@/components/admin/TestDataGenerator';
import { IntegrationTestRunner } from '@/components/admin/IntegrationTestRunner';
import { Loader2, Save, Clock, Users, Bell, Shield, Settings, Trash2, Plug, AlertTriangle, Lock, TestTube, RefreshCw, Info, AlertCircle, Database } from 'lucide-react';
import { useDocumentTitle } from '@/hooks/useDocumentTitle';
export default function AdminSettings() {
useDocumentTitle('Settings - Admin');
const { user } = useAuth();
const { isSuperuser, loading: roleLoading } = useUserRole();
const {
settings,
isLoading,
error,
updateSetting,
isUpdating,
getSettingsByCategory
} = useAdminSettings();
if (roleLoading || isLoading) {
return (
<AdminLayout>
<div className="flex items-center justify-center min-h-[400px]">
<Loader2 className="w-8 h-8 animate-spin" />
</div>
</AdminLayout>
);
}
if (!user || !isSuperuser()) {
return (
<AdminLayout>
<div className="text-center space-y-4">
<h1 className="text-2xl font-bold mb-4">Access Denied</h1>
<p className="text-muted-foreground">You don't have permission to access admin settings.</p>
{error && (
<div className="text-sm text-red-500 p-4 bg-red-50 rounded-md">
Error details: {error.message}
</div>
)}
</div>
</AdminLayout>
);
}
if (!settings || settings.length === 0) {
return (
<AdminLayout>
<div className="text-center space-y-4">
<h1 className="text-2xl font-bold mb-4">No Settings Found</h1>
<p className="text-muted-foreground">
No admin settings have been configured yet. Please contact your system administrator.
</p>
{error && (
<div className="text-sm text-red-500 p-4 bg-red-50 rounded-md">
Error details: {error.message}
</div>
)}
</div>
</AdminLayout>
);
}
const SettingInput = ({ setting }: { setting: any }) => {
const [localValue, setLocalValue] = useState(setting.setting_value);
const handleSubmit = async () => {
await updateSetting(setting.setting_key, localValue);
};
// Auto-flagging threshold settings
if (setting.setting_key === 'moderation.auto_flag_threshold') {
return (
<Card className="p-4">
<div className="space-y-4">
<div className="flex items-center gap-2">
<Shield className="w-4 h-4 text-orange-500" />
<Label className="text-base font-medium">Auto-Flag Content Threshold</Label>
</div>
<p className="text-sm text-muted-foreground">
Content will be automatically flagged for review after receiving this many reports
</p>
<div className="flex items-center gap-4">
<Select value={localValue?.toString()} onValueChange={(value) => {
const numValue = parseInt(value);
setLocalValue(numValue);
updateSetting(setting.setting_key, numValue);
}}>
<SelectTrigger className="w-32">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="1">1 report</SelectItem>
<SelectItem value="2">2 reports</SelectItem>
<SelectItem value="3">3 reports</SelectItem>
<SelectItem value="5">5 reports</SelectItem>
<SelectItem value="10">10 reports</SelectItem>
</SelectContent>
</Select>
<Badge variant="outline">Current: {localValue} reports</Badge>
</div>
</div>
</Card>
);
}
// Review retention settings
if (setting.setting_key === 'moderation.review_retention_days') {
return (
<Card className="p-4">
<div className="space-y-4">
<div className="flex items-center gap-2">
<Clock className="w-4 h-4 text-blue-500" />
<Label className="text-base font-medium">Review Data Retention</Label>
</div>
<p className="text-sm text-muted-foreground">
How long to keep moderated content data before automatic cleanup
</p>
<div className="flex items-center gap-4">
<Select value={localValue?.toString()} onValueChange={(value) => {
const numValue = parseInt(value);
setLocalValue(numValue);
updateSetting(setting.setting_key, numValue);
}}>
<SelectTrigger className="w-40">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="30">30 days</SelectItem>
<SelectItem value="60">60 days</SelectItem>
<SelectItem value="90">90 days</SelectItem>
<SelectItem value="180">6 months</SelectItem>
<SelectItem value="365">1 year</SelectItem>
<SelectItem value="730">2 years</SelectItem>
</SelectContent>
</Select>
<Badge variant="outline">Current: {localValue} days</Badge>
</div>
</div>
</Card>
);
}
// Ban duration settings
if (setting.setting_key === 'moderation.ban_durations') {
const [banDurations, setBanDurations] = useState<{[key: string]: number}>(
typeof localValue === 'object' ? localValue : { temporary: 7, extended: 30, permanent: 0 }
);
const updateBanDuration = (type: string, days: number) => {
const updated = { ...banDurations, [type]: days };
setBanDurations(updated);
setLocalValue(updated);
};
const [savingBanDurations, setSavingBanDurations] = useState(false);
const saveBanDurations = async () => {
setSavingBanDurations(true);
try {
await updateSetting(setting.setting_key, banDurations);
} finally {
setSavingBanDurations(false);
}
};
return (
<Card className="p-4">
<div className="space-y-4">
<div className="flex items-center gap-2">
<Users className="w-4 h-4 text-red-500" />
<Label className="text-base font-medium">User Ban Duration Options</Label>
</div>
<p className="text-sm text-muted-foreground">
Configure the available ban duration options for moderators
</p>
<div className="grid gap-4">
<div className="flex items-center justify-between p-3 border rounded-lg">
<div>
<Label className="font-medium">Temporary Ban</Label>
<p className="text-sm text-muted-foreground">Short-term restriction for minor violations</p>
</div>
<div className="flex items-center gap-2">
<Select
value={banDurations.temporary?.toString()}
onValueChange={(value) => updateBanDuration('temporary', parseInt(value))}
>
<SelectTrigger className="w-32">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="1">1 day</SelectItem>
<SelectItem value="3">3 days</SelectItem>
<SelectItem value="7">7 days</SelectItem>
<SelectItem value="14">14 days</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="flex items-center justify-between p-3 border rounded-lg">
<div>
<Label className="font-medium">Extended Ban</Label>
<p className="text-sm text-muted-foreground">Longer restriction for serious violations</p>
</div>
<div className="flex items-center gap-2">
<Select
value={banDurations.extended?.toString()}
onValueChange={(value) => updateBanDuration('extended', parseInt(value))}
>
<SelectTrigger className="w-32">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="30">30 days</SelectItem>
<SelectItem value="60">60 days</SelectItem>
<SelectItem value="90">90 days</SelectItem>
<SelectItem value="180">6 months</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="flex items-center justify-between p-3 border rounded-lg">
<div>
<Label className="font-medium">Permanent Ban</Label>
<p className="text-sm text-muted-foreground">Indefinite restriction for severe violations</p>
</div>
<div className="flex items-center gap-2">
<Badge variant="destructive">Permanent</Badge>
</div>
</div>
</div>
<Button
onClick={saveBanDurations}
loading={savingBanDurations}
loadingText="Saving..."
className="w-full"
trackingLabel="save-ban-duration-settings"
>
<Save className="w-4 h-4 mr-2" />
Save Ban Duration Settings
</Button>
</div>
</Card>
);
}
// Admin panel refresh mode setting
if (setting.setting_key === 'system.admin_panel_refresh_mode') {
return (
<Card className="p-4">
<div className="space-y-4">
<div className="flex items-center gap-2">
<Settings className="w-4 h-4 text-blue-500" />
<Label className="text-base font-medium">Admin Panel Refresh Mode</Label>
</div>
<p className="text-sm text-muted-foreground">
Choose how the admin panel statistics refresh
</p>
<div className="flex items-center gap-4">
<Select
value={typeof localValue === 'string' ? localValue.replace(/"/g, '') : localValue}
onValueChange={(value) => {
setLocalValue(value);
updateSetting(setting.setting_key, JSON.stringify(value));
}}
>
<SelectTrigger className="w-48">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="manual">Manual Only</SelectItem>
<SelectItem value="auto">Auto-refresh</SelectItem>
</SelectContent>
</Select>
<Badge variant="outline">
Current: {(typeof localValue === 'string' ? localValue.replace(/"/g, '') : localValue) === 'auto' ? 'Auto-refresh' : 'Manual'}
</Badge>
</div>
</div>
</Card>
);
}
// Admin panel poll interval setting
if (setting.setting_key === 'system.admin_panel_poll_interval') {
return (
<Card className="p-4">
<div className="space-y-4">
<div className="flex items-center gap-2">
<Clock className="w-4 h-4 text-green-500" />
<Label className="text-base font-medium">Auto-refresh Interval</Label>
</div>
<p className="text-sm text-muted-foreground">
How often to automatically refresh admin panel statistics (when auto-refresh is enabled)
</p>
<div className="flex items-center gap-4">
<Select value={localValue?.toString()} onValueChange={(value) => {
const numValue = parseInt(value);
setLocalValue(numValue);
updateSetting(setting.setting_key, numValue);
}}>
<SelectTrigger className="w-40">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="10">10 seconds</SelectItem>
<SelectItem value="30">30 seconds</SelectItem>
<SelectItem value="60">1 minute</SelectItem>
<SelectItem value="120">2 minutes</SelectItem>
<SelectItem value="300">5 minutes</SelectItem>
</SelectContent>
</Select>
<Badge variant="outline">Current: {localValue}s</Badge>
</div>
</div>
</Card>
);
}
// Auto-refresh strategy setting
if (setting.setting_key === 'auto_refresh_strategy') {
return (
<Card className="p-4">
<div className="space-y-4">
<div className="flex items-center gap-2">
<Settings className="w-4 h-4 text-purple-500" />
<Label className="text-base font-medium">Auto-Refresh Strategy</Label>
</div>
<p className="text-sm text-muted-foreground">
How the moderation queue handles new items when they arrive
</p>
<div className="flex items-center gap-4">
<Select
value={typeof localValue === 'string' ? localValue.replace(/"/g, '') : localValue}
onValueChange={(value) => {
setLocalValue(value);
updateSetting(setting.setting_key, JSON.stringify(value));
}}
>
<SelectTrigger className="w-48">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="merge">Merge silently</SelectItem>
<SelectItem value="replace">Replace all</SelectItem>
<SelectItem value="notify">Show notification</SelectItem>
</SelectContent>
</Select>
<Badge variant="outline">
Current: {typeof localValue === 'string' ? localValue.replace(/"/g, '') : localValue}
</Badge>
</div>
</div>
</Card>
);
}
// Retry settings
if (setting.setting_key === 'retry.max_attempts') {
return (
<Card className="p-4">
<div className="space-y-4">
<div className="flex items-center gap-2">
<RefreshCw className="w-4 h-4 text-blue-500" />
<Label className="text-base font-medium">Maximum Retry Attempts</Label>
</div>
<p className="text-sm text-muted-foreground">
How many times to retry failed operations (entity/photo submissions)
</p>
<div className="flex items-center gap-4">
<Select value={localValue?.toString()} onValueChange={(value) => {
const numValue = parseInt(value);
setLocalValue(numValue);
updateSetting(setting.setting_key, numValue);
}}>
<SelectTrigger className="w-32">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="1">1 attempt</SelectItem>
<SelectItem value="2">2 attempts</SelectItem>
<SelectItem value="3">3 attempts</SelectItem>
<SelectItem value="5">5 attempts</SelectItem>
<SelectItem value="10">10 attempts</SelectItem>
</SelectContent>
</Select>
<Badge variant="outline">Current: {localValue}</Badge>
</div>
</div>
</Card>
);
}
if (setting.setting_key === 'retry.base_delay') {
return (
<Card className="p-4">
<div className="space-y-4">
<div className="flex items-center gap-2">
<Clock className="w-4 h-4 text-green-500" />
<Label className="text-base font-medium">Initial Retry Delay</Label>
</div>
<p className="text-sm text-muted-foreground">
Milliseconds to wait before first retry attempt
</p>
<div className="flex items-center gap-4">
<Select value={localValue?.toString()} onValueChange={(value) => {
const numValue = parseInt(value);
setLocalValue(numValue);
updateSetting(setting.setting_key, numValue);
}}>
<SelectTrigger className="w-40">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="100">100ms (very fast)</SelectItem>
<SelectItem value="500">500ms</SelectItem>
<SelectItem value="1000">1 second</SelectItem>
<SelectItem value="2000">2 seconds</SelectItem>
<SelectItem value="5000">5 seconds</SelectItem>
</SelectContent>
</Select>
<Badge variant="outline">Current: {localValue}ms</Badge>
</div>
</div>
</Card>
);
}
if (setting.setting_key === 'retry.max_delay') {
return (
<Card className="p-4">
<div className="space-y-4">
<div className="flex items-center gap-2">
<Clock className="w-4 h-4 text-orange-500" />
<Label className="text-base font-medium">Maximum Retry Delay</Label>
</div>
<p className="text-sm text-muted-foreground">
Maximum delay between retry attempts (exponential backoff cap)
</p>
<div className="flex items-center gap-4">
<Select value={localValue?.toString()} onValueChange={(value) => {
const numValue = parseInt(value);
setLocalValue(numValue);
updateSetting(setting.setting_key, numValue);
}}>
<SelectTrigger className="w-40">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="5000">5 seconds</SelectItem>
<SelectItem value="10000">10 seconds</SelectItem>
<SelectItem value="20000">20 seconds</SelectItem>
<SelectItem value="30000">30 seconds</SelectItem>
</SelectContent>
</Select>
<Badge variant="outline">Current: {localValue}ms</Badge>
</div>
</div>
</Card>
);
}
if (setting.setting_key === 'retry.backoff_multiplier') {
return (
<Card className="p-4">
<div className="space-y-4">
<div className="flex items-center gap-2">
<RefreshCw className="w-4 h-4 text-purple-500" />
<Label className="text-base font-medium">Backoff Multiplier</Label>
</div>
<p className="text-sm text-muted-foreground">
Growth rate for exponential backoff between retries
</p>
<div className="flex items-center gap-4">
<Select value={localValue?.toString()} onValueChange={(value) => {
const numValue = parseFloat(value);
setLocalValue(numValue);
updateSetting(setting.setting_key, numValue);
}}>
<SelectTrigger className="w-32">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="1.5">1.5x</SelectItem>
<SelectItem value="2">2x</SelectItem>
<SelectItem value="2.5">2.5x</SelectItem>
<SelectItem value="3">3x</SelectItem>
</SelectContent>
</Select>
<Badge variant="outline">Current: {localValue}x</Badge>
</div>
</div>
</Card>
);
}
if (setting.setting_key === 'circuit_breaker.failure_threshold') {
return (
<Card className="p-4">
<div className="space-y-4">
<div className="flex items-center gap-2">
<AlertTriangle className="w-4 h-4 text-orange-500" />
<Label className="text-base font-medium">Circuit Breaker Threshold</Label>
</div>
<p className="text-sm text-muted-foreground">
Number of consecutive failures before blocking all requests temporarily
</p>
<div className="flex items-center gap-4">
<Select value={localValue?.toString()} onValueChange={(value) => {
const numValue = parseInt(value);
setLocalValue(numValue);
updateSetting(setting.setting_key, numValue);
}}>
<SelectTrigger className="w-40">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="3">3 failures</SelectItem>
<SelectItem value="5">5 failures</SelectItem>
<SelectItem value="10">10 failures</SelectItem>
<SelectItem value="15">15 failures</SelectItem>
<SelectItem value="20">20 failures</SelectItem>
</SelectContent>
</Select>
<Badge variant="outline">Current: {localValue}</Badge>
</div>
</div>
</Card>
);
}
if (setting.setting_key === 'circuit_breaker.reset_timeout') {
return (
<Card className="p-4">
<div className="space-y-4">
<div className="flex items-center gap-2">
<Clock className="w-4 h-4 text-red-500" />
<Label className="text-base font-medium">Circuit Reset Timeout</Label>
</div>
<p className="text-sm text-muted-foreground">
How long to wait before testing if service has recovered
</p>
<div className="flex items-center gap-4">
<Select value={localValue?.toString()} onValueChange={(value) => {
const numValue = parseInt(value);
setLocalValue(numValue);
updateSetting(setting.setting_key, numValue);
}}>
<SelectTrigger className="w-40">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="30000">30 seconds</SelectItem>
<SelectItem value="60000">1 minute</SelectItem>
<SelectItem value="120000">2 minutes</SelectItem>
<SelectItem value="300000">5 minutes</SelectItem>
</SelectContent>
</Select>
<Badge variant="outline">Current: {Math.floor(Number(localValue) / 1000)}s</Badge>
</div>
</div>
</Card>
);
}
if (setting.setting_key === 'circuit_breaker.monitoring_window') {
return (
<Card className="p-4">
<div className="space-y-4">
<div className="flex items-center gap-2">
<Clock className="w-4 h-4 text-yellow-500" />
<Label className="text-base font-medium">Monitoring Window</Label>
</div>
<p className="text-sm text-muted-foreground">
Time window to track failures for circuit breaker
</p>
<div className="flex items-center gap-4">
<Select value={localValue?.toString()} onValueChange={(value) => {
const numValue = parseInt(value);
setLocalValue(numValue);
updateSetting(setting.setting_key, numValue);
}}>
<SelectTrigger className="w-40">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="60000">1 minute</SelectItem>
<SelectItem value="120000">2 minutes</SelectItem>
<SelectItem value="180000">3 minutes</SelectItem>
<SelectItem value="300000">5 minutes</SelectItem>
<SelectItem value="600000">10 minutes</SelectItem>
</SelectContent>
</Select>
<Badge variant="outline">Current: {Math.floor(Number(localValue) / 60000)}min</Badge>
</div>
</div>
</Card>
);
}
// Helper to check if value is boolean
const isBooleanSetting = (value: any) => {
return value === true || value === false ||
value === 'true' || value === 'false';
};
// Boolean/switch settings - check by value type instead of key pattern
if (isBooleanSetting(setting.setting_value)) {
const getSettingIcon = () => {
if (setting.setting_key.includes('email_alerts')) return <Bell className="w-4 h-4 text-blue-500" />;
if (setting.setting_key.includes('require_approval')) return <Shield className="w-4 h-4 text-orange-500" />;
if (setting.setting_key.includes('auto_cleanup')) return <Trash2 className="w-4 h-4 text-green-500" />;
return <Settings className="w-4 h-4 text-gray-500" />;
};
return (
<Card className="p-4">
<div className="flex items-center justify-between">
<div className="space-y-1">
<div className="flex items-center gap-2">
{getSettingIcon()}
<Label className="text-base font-medium">{setting.description}</Label>
</div>
<p className="text-sm text-muted-foreground">
{setting.setting_key.includes('email_alerts') && "Receive email notifications for this event"}
{setting.setting_key.includes('require_approval') && "Items must be manually approved before being published"}
{setting.setting_key.includes('auto_cleanup') && "Automatically remove old or rejected content"}
{(!setting.setting_key.includes('email_alerts') && !setting.setting_key.includes('require_approval') && !setting.setting_key.includes('auto_cleanup')) && "Toggle this feature on or off"}
</p>
</div>
<div className="flex items-center gap-3">
<Badge variant={localValue ? "default" : "secondary"}>
{localValue ? "Enabled" : "Disabled"}
</Badge>
<Switch
checked={localValue === true || localValue === 'true'}
onCheckedChange={(checked) => {
setLocalValue(checked);
updateSetting(setting.setting_key, checked);
}}
/>
</div>
</div>
</Card>
);
}
// Numeric threshold settings
if (setting.setting_key.includes('threshold') || setting.setting_key.includes('limit') || setting.setting_key.includes('max_')) {
return (
<Card className="p-4">
<div className="space-y-4">
<div className="flex items-center gap-2">
<Settings className="w-4 h-4 text-purple-500" />
<Label className="text-base font-medium">{setting.description}</Label>
</div>
<p className="text-sm text-muted-foreground">
Set the numeric limit for this system parameter
</p>
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<Input
type="number"
value={localValue}
onChange={(e) => {
const value = parseInt(e.target.value) || 0;
setLocalValue(value);
}}
className="w-24"
min="0"
/>
<Button
onClick={handleSubmit}
loading={isUpdating}
loadingText=""
size="sm"
trackingLabel="save-threshold-setting"
>
<Save className="w-4 h-4" />
</Button>
</div>
<Badge variant="outline">Current: {localValue}</Badge>
</div>
</div>
</Card>
);
}
// Default string input
return (
<Card className="p-4">
<div className="space-y-4">
<div className="flex items-center gap-2">
<Settings className="w-4 h-4 text-gray-500" />
<Label className="text-base font-medium">{setting.description}</Label>
</div>
<div className="flex items-center gap-2">
<Input
value={localValue}
onChange={(e) => {
setLocalValue(e.target.value);
}}
className="flex-1"
/>
<Button
onClick={handleSubmit}
loading={isUpdating}
loadingText=""
size="sm"
trackingLabel="save-setting"
>
<Save className="w-4 h-4" />
</Button>
</div>
</div>
</Card>
);
};
return (
<AdminLayout>
<div className="space-y-6 max-w-4xl">
<div>
<h1 className="text-2xl font-bold tracking-tight">Admin Settings</h1>
<p className="text-muted-foreground mt-1">
Configure system-wide settings and preferences
</p>
</div>
<Tabs defaultValue="moderation" className="space-y-6">
<TabsList className="grid w-full grid-cols-7">
<TabsTrigger value="moderation" className="flex items-center gap-2">
<Shield className="w-4 h-4" />
<span className="hidden sm:inline">Moderation</span>
</TabsTrigger>
<TabsTrigger value="user_management" className="flex items-center gap-2">
<Users className="w-4 h-4" />
<span className="hidden sm:inline">Users</span>
</TabsTrigger>
<TabsTrigger value="notifications" className="flex items-center gap-2">
<Bell className="w-4 h-4" />
<span className="hidden sm:inline">Notifications</span>
</TabsTrigger>
<TabsTrigger value="resilience" className="flex items-center gap-2">
<RefreshCw className="w-4 h-4" />
<span className="hidden sm:inline">Resilience</span>
</TabsTrigger>
<TabsTrigger value="system" className="flex items-center gap-2">
<Settings className="w-4 h-4" />
<span className="hidden sm:inline">System</span>
</TabsTrigger>
<TabsTrigger value="integrations" className="flex items-center gap-2">
<Plug className="w-4 h-4" />
<span className="hidden sm:inline">Integrations</span>
</TabsTrigger>
<TabsTrigger value="testing" className="flex items-center gap-2">
<TestTube className="w-4 h-4" />
<span className="hidden sm:inline">Testing</span>
</TabsTrigger>
</TabsList>
<TabsContent value="moderation">
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Shield className="w-5 h-5" />
Moderation Settings
</CardTitle>
<CardDescription>
Configure content moderation rules, thresholds, and automated actions
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{getSettingsByCategory('moderation').length > 0 ? (
getSettingsByCategory('moderation').map((setting) => (
<SettingInput key={setting.id} setting={setting} />
))
) : (
<div className="text-center py-8 text-muted-foreground">
<Shield className="w-12 h-12 mx-auto mb-4 opacity-50" />
<p>No moderation settings configured yet.</p>
</div>
)}
</CardContent>
</Card>
</TabsContent>
<TabsContent value="user_management">
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Users className="w-5 h-5" />
User Management
</CardTitle>
<CardDescription>
Configure user registration, profile settings, and account policies
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{getSettingsByCategory('user_management').length > 0 ? (
getSettingsByCategory('user_management').map((setting) => (
<SettingInput key={setting.id} setting={setting} />
))
) : (
<div className="text-center py-8 text-muted-foreground">
<Users className="w-12 h-12 mx-auto mb-4 opacity-50" />
<p>No user management settings configured yet.</p>
</div>
)}
</CardContent>
</Card>
</TabsContent>
<TabsContent value="notifications">
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Bell className="w-5 h-5" />
Notification Settings
</CardTitle>
<CardDescription>
Configure email alerts, push notifications, and communication preferences
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{getSettingsByCategory('notifications').length > 0 ? (
getSettingsByCategory('notifications').map((setting) => (
<SettingInput key={setting.id} setting={setting} />
))
) : (
<div className="text-center py-8 text-muted-foreground">
<Bell className="w-12 h-12 mx-auto mb-4 opacity-50" />
<p>No notification settings configured yet.</p>
</div>
)}
</CardContent>
</Card>
</TabsContent>
<TabsContent value="resilience">
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<RefreshCw className="w-5 h-5" />
Resilience & Retry Configuration
</CardTitle>
<CardDescription>
Configure automatic retry behavior and circuit breaker settings for handling transient failures
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<div className="bg-blue-50 dark:bg-blue-950 p-4 rounded-lg border border-blue-200 dark:border-blue-800">
<div className="flex items-start gap-3">
<Info className="w-5 h-5 text-blue-600 dark:text-blue-400 mt-0.5 flex-shrink-0" />
<div className="text-sm text-blue-900 dark:text-blue-100">
<p className="font-medium mb-2">About Retry & Circuit Breaker</p>
<ul className="space-y-1 text-blue-800 dark:text-blue-200">
<li>• <strong>Retry Logic:</strong> Automatically retries failed operations (network issues, timeouts)</li>
<li>• <strong>Circuit Breaker:</strong> Prevents system overload by blocking requests during outages</li>
<li>• <strong>When to adjust:</strong> Increase retries for unstable networks, decrease for fast-fail scenarios</li>
</ul>
</div>
</div>
</div>
<div className="space-y-4">
<h3 className="text-lg font-semibold">Retry Settings</h3>
{getSettingsByCategory('system')
.filter(s => s.setting_key.startsWith('retry.'))
.map(setting => <SettingInput key={setting.id} setting={setting} />)}
</div>
<div className="border-t pt-6 space-y-4">
<h3 className="text-lg font-semibold">Circuit Breaker Settings</h3>
{getSettingsByCategory('system')
.filter(s => s.setting_key.startsWith('circuit_breaker.'))
.map(setting => <SettingInput key={setting.id} setting={setting} />)}
</div>
<div className="bg-yellow-50 dark:bg-yellow-950 p-4 rounded-lg border border-yellow-200 dark:border-yellow-800">
<div className="flex items-start gap-3">
<AlertCircle className="w-5 h-5 text-yellow-600 dark:text-yellow-400 mt-0.5 flex-shrink-0" />
<div className="text-sm text-yellow-900 dark:text-yellow-100">
<p className="font-medium mb-1">Configuration Changes</p>
<p className="text-yellow-800 dark:text-yellow-200">
Settings take effect immediately but may be cached for up to 5 minutes in active sessions. Consider monitoring error logs after changes to verify behavior.
</p>
</div>
</div>
</div>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="system">
<div className="space-y-4">
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Settings className="w-5 h-5" />
System Configuration
</CardTitle>
<CardDescription>
Configure system-wide settings, maintenance options, and technical parameters
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{getSettingsByCategory('system').filter(s => !s.setting_key.startsWith('retry.') && !s.setting_key.startsWith('circuit_breaker.')).length > 0 ? (
getSettingsByCategory('system').filter(s => !s.setting_key.startsWith('retry.') && !s.setting_key.startsWith('circuit_breaker.')).map((setting) => (
<SettingInput key={setting.id} setting={setting} />
))
) : (
<div className="text-center py-8 text-muted-foreground">
<Settings className="w-12 h-12 mx-auto mb-4 opacity-50" />
<p>No system settings configured yet.</p>
</div>
)}
</CardContent>
</Card>
</div>
</TabsContent>
<TabsContent value="integrations">
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Plug className="w-5 h-5" />
Integration Settings
</CardTitle>
<CardDescription>
Configure third-party integrations and external services
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{getSettingsByCategory('integrations').length > 0 ? (
getSettingsByCategory('integrations').map((setting) => (
<SettingInput key={setting.id} setting={setting} />
))
) : (
<div className="text-center py-8 text-muted-foreground">
<Plug className="w-12 h-12 mx-auto mb-4 opacity-50" />
<p>No integration settings configured yet.</p>
</div>
)}
<NovuMigrationUtility />
</CardContent>
</Card>
</TabsContent>
<TabsContent value="testing">
<div className="space-y-6">
{/* Test Data Generator Section */}
<div>
<h2 className="text-2xl font-bold mb-4 flex items-center gap-2">
<Database className="w-6 h-6" />
Test Data Generator
</h2>
<p className="text-muted-foreground mb-4">
Generate realistic test data for parks, rides, companies, and submissions.
</p>
<TestDataGenerator />
</div>
{/* Integration Test Runner Section */}
<div>
<h2 className="text-2xl font-bold mb-4 flex items-center gap-2">
<TestTube className="w-6 h-6" />
Integration Test Runner
</h2>
<p className="text-muted-foreground mb-4">
Run automated integration tests against your approval pipeline, moderation system, and data integrity checks.
</p>
<IntegrationTestRunner />
</div>
</div>
</TabsContent>
</Tabs>
</div>
</AdminLayout>
);
}