feat: setup admin JSON generation and pdf viewer for free plan DPPs
This commit is contained in:
@@ -1,7 +1,61 @@
|
||||
"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>
|
||||
@@ -24,7 +78,49 @@ export default function SettingsPage() {
|
||||
<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-[#00F0FF]" />
|
||||
</div>
|
||||
<Button className="bg-[#00F0FF] text-background hover:bg-[#00F0FF]/90 font-bold">Save Configuration</Button>
|
||||
<Button onClick={() => alert("Configuration Saved via Admin Panel")} className="bg-[#00F0FF] text-background hover:bg-[#00F0FF]/90 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-[#00F0FF]"
|
||||
/>
|
||||
</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-[#00F0FF]">
|
||||
<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-[#00F0FF] text-background hover:bg-[#00F0FF]/90 font-bold">Update Trial Settings</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -39,8 +135,10 @@ export default function SettingsPage() {
|
||||
<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 className="h-6 w-11 rounded-full bg-secondary border border-border relative cursor-pointer">
|
||||
<div className="absolute left-1 top-1 size-4 bg-muted-foreground rounded-full"></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">
|
||||
@@ -48,8 +146,10 @@ export default function SettingsPage() {
|
||||
<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 className="h-6 w-11 rounded-full bg-primary border border-primary relative cursor-pointer shadow-[0_0_10px_var(--color-primary)]">
|
||||
<div className="absolute right-1 top-1 size-4 bg-primary-foreground rounded-full"></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>
|
||||
@@ -60,7 +160,7 @@ export default function SettingsPage() {
|
||||
<CardTitle className="text-red-500">Danger Zone</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Button className="bg-red-500 text-white hover:bg-red-600 font-bold">Purge Generated Cache</Button>
|
||||
<Button onClick={() => alert("Cache Purged.")} className="bg-red-500 text-white hover:bg-red-600 font-bold">Purge Generated Cache</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user