- globals.css: add --cyan-ai CSS variable (rgb(12,96,96) light / #00F0FF dark) + opacity variants --cyan-ai-10/20/30/40/50/80 - Bulk replace all #00F0FF hardcoded Tailwind arbitrary values with var(--cyan-ai) / var(--cyan-ai-10) / etc across 55+ TSX/TS files - Shadow glow colours also updated (suppressed by box-shadow:none in light mode) - TypeScript: 0 errors
170 lines
8.5 KiB
TypeScript
170 lines
8.5 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect } from "react";
|
|
import { Button } from "@/components/ui/button"
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
|
|
|
export default function SettingsPage() {
|
|
const [settings, setSettings] = useState({
|
|
maintenance_mode: false,
|
|
allow_new_registrations: true,
|
|
enable_free_trial: true,
|
|
trial_duration_days: 5,
|
|
trial_tier: "Al_Coach"
|
|
});
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
fetch("/api/admin/settings")
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
if(data && !data.detail) {
|
|
setSettings(prev => ({ ...prev, ...data }));
|
|
}
|
|
setLoading(false);
|
|
})
|
|
.catch(err => {
|
|
console.error("Failed to load settings:", err);
|
|
setLoading(false);
|
|
});
|
|
}, []);
|
|
|
|
const saveSettings = async (section: string, overrides: Partial<typeof settings> = {}) => {
|
|
try {
|
|
const payload = { ...settings, ...overrides };
|
|
const res = await fetch("/api/admin/settings", {
|
|
method: "PUT",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(payload)
|
|
});
|
|
if (res.ok) {
|
|
alert(`${section} saved successfully!`);
|
|
setSettings(payload);
|
|
} else {
|
|
alert(`Failed to save ${section}.`);
|
|
}
|
|
} catch (err) {
|
|
alert("Error saving settings.");
|
|
}
|
|
};
|
|
|
|
const handleToggle = (key: keyof typeof settings, sectionName: string) => {
|
|
const newValue = !settings[key];
|
|
setSettings(prev => ({ ...prev, [key]: newValue }));
|
|
saveSettings(sectionName, { [key]: newValue });
|
|
};
|
|
|
|
if (loading) return <div className="p-8 animate-pulse text-muted-foreground font-bold">Loading System Settings...</div>;
|
|
|
|
return (
|
|
<div className="p-8 space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-500 max-w-4xl">
|
|
<div>
|
|
<h1 className="text-3xl font-bold tracking-tight">System Settings</h1>
|
|
<p className="text-muted-foreground mt-1">Configure global application variables and security protocols.</p>
|
|
</div>
|
|
|
|
<div className="space-y-6">
|
|
<Card className="border-border bg-secondary/10">
|
|
<CardHeader>
|
|
<CardTitle>AI Inference Configuration</CardTitle>
|
|
<CardDescription>Manage the underlying LLM models and API keys for the AI agents.</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div className="space-y-2">
|
|
<label className="text-sm font-bold">Primary Model Name</label>
|
|
<input type="text" defaultValue="gemini-1.5-pro-experimental" className="w-full bg-background border border-border rounded-lg px-4 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-[var(--cyan-ai)]" />
|
|
</div>
|
|
<div className="space-y-2">
|
|
<label className="text-sm font-bold">OpenAI API Key (Fallback)</label>
|
|
<input type="password" defaultValue="sk-........................" className="w-full bg-background border border-border rounded-lg px-4 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-[var(--cyan-ai)]" />
|
|
</div>
|
|
<Button onClick={() => alert("Configuration Saved via Admin Panel")} className="bg-[var(--cyan-ai)] text-background hover:bg-[var(--cyan-ai-80)] font-bold">Save Configuration</Button>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card className="border-border bg-secondary/10">
|
|
<CardHeader>
|
|
<CardTitle>Trial Management</CardTitle>
|
|
<CardDescription>Configure the trial length and tier access for new signups.</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-6">
|
|
<div className="flex items-center justify-between p-4 bg-background border border-border rounded-xl">
|
|
<div>
|
|
<div className="font-bold">Enable Free Trial</div>
|
|
<div className="text-sm text-muted-foreground">Offer a trial to newly registered users.</div>
|
|
</div>
|
|
<div
|
|
onClick={() => handleToggle('enable_free_trial', 'Free Trial')}
|
|
className={`h-6 w-11 rounded-full relative cursor-pointer transition-colors ${settings.enable_free_trial ? 'bg-primary border border-primary shadow-[0_0_10px_var(--color-primary)]' : 'bg-secondary border border-border'}`}>
|
|
<div className={`absolute top-1 size-4 rounded-full transition-all ${settings.enable_free_trial ? 'right-1 bg-primary-foreground' : 'left-1 bg-muted-foreground'}`}></div>
|
|
</div>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div className="space-y-2">
|
|
<label className="text-sm font-bold">Trial Duration (Days)</label>
|
|
<input
|
|
type="number"
|
|
value={settings.trial_duration_days}
|
|
onChange={(e) => setSettings(prev => ({ ...prev, trial_duration_days: parseInt(e.target.value) || 0 }))}
|
|
className="w-full bg-background border border-border rounded-lg px-4 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-[var(--cyan-ai)]"
|
|
/>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<label className="text-sm font-bold">Trial Access Tier</label>
|
|
<select
|
|
value={settings.trial_tier}
|
|
onChange={(e) => setSettings(prev => ({ ...prev, trial_tier: e.target.value }))}
|
|
className="w-full bg-background border border-border rounded-lg px-4 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-[var(--cyan-ai)]">
|
|
<option value="Pro">Pro Tier</option>
|
|
<option value="Al_Coach">AI Coach (All Features)</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<Button onClick={() => saveSettings("Trial Settings")} className="bg-[var(--cyan-ai)] text-background hover:bg-[var(--cyan-ai-80)] font-bold">Update Trial Settings</Button>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card className="border-border bg-secondary/10">
|
|
<CardHeader>
|
|
<CardTitle>Platform Maintenance</CardTitle>
|
|
<CardDescription>Global toggles for site availability.</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-6">
|
|
<div className="flex items-center justify-between p-4 bg-background border border-border rounded-xl">
|
|
<div>
|
|
<div className="font-bold">Maintenance Mode</div>
|
|
<div className="text-sm text-muted-foreground">Disable all student access and show maintenance screen.</div>
|
|
</div>
|
|
<div
|
|
onClick={() => handleToggle('maintenance_mode', 'Maintenance Mode')}
|
|
className={`h-6 w-11 rounded-full relative cursor-pointer transition-colors ${settings.maintenance_mode ? 'bg-red-500 border border-red-500 shadow-[0_0_10px_rgba(239,68,68,0.8)]' : 'bg-secondary border border-border'}`}>
|
|
<div className={`absolute top-1 size-4 rounded-full transition-all ${settings.maintenance_mode ? 'right-1 bg-white' : 'left-1 bg-muted-foreground'}`}></div>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center justify-between p-4 bg-background border border-border rounded-xl">
|
|
<div>
|
|
<div className="font-bold">Allow New Registrations</div>
|
|
<div className="text-sm text-muted-foreground">Open the `/signup` route for new users.</div>
|
|
</div>
|
|
<div
|
|
onClick={() => handleToggle('allow_new_registrations', 'Registration Setting')}
|
|
className={`h-6 w-11 rounded-full relative cursor-pointer transition-colors ${settings.allow_new_registrations ? 'bg-primary border border-primary shadow-[0_0_10px_var(--color-primary)]' : 'bg-secondary border border-border'}`}>
|
|
<div className={`absolute top-1 size-4 rounded-full transition-all ${settings.allow_new_registrations ? 'right-1 bg-primary-foreground' : 'left-1 bg-muted-foreground'}`}></div>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card className="border-red-500/20 bg-red-500/5">
|
|
<CardHeader>
|
|
<CardTitle className="text-red-500">Danger Zone</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Button onClick={() => alert("Cache Purged.")} className="bg-red-500 text-white hover:bg-red-600 font-bold">Purge Generated Cache</Button>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|