Bankbot / frontend /src /app /settings /page.tsx
mohsin-devs's picture
feat: implement Goals, Loans, and Settings pages with real APIs
6a71f57
Raw
History Blame Contribute Delete
7.66 kB
"use client";
import { useEffect, useState } from "react";
import { motion } from "framer-motion";
import { User, Bell, Sparkles, Shield, Save, Loader2, CheckCircle2 } from "lucide-react";
import { authApi } from "@/lib/api";
import { useAuthStore } from "@/lib/stores/authStore";
const PERSONALITIES = [
"Balanced Investor",
"Conservative Saver",
"Growth Focused",
"Aggressive Trader",
"Debt Reducer",
];
export default function SettingsPage() {
const { user, restoreSession } = useAuthStore();
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [saved, setSaved] = useState(false);
const [form, setForm] = useState({
name: "",
email: "",
financial_personality: "Balanced Investor",
notifications_enabled: true,
ai_coaching_enabled: true,
fraud_alerts_enabled: true,
});
useEffect(() => {
(async () => {
try {
const me = await authApi.me();
setForm({
name: me.name,
email: me.email,
financial_personality: me.financial_personality || "Balanced Investor",
notifications_enabled: me.notifications_enabled ?? true,
ai_coaching_enabled: me.ai_coaching_enabled ?? true,
fraud_alerts_enabled: me.fraud_alerts_enabled ?? true,
});
} catch {
if (user) {
setForm((f) => ({
...f,
name: user.name,
email: user.email,
financial_personality: user.financial_personality || "Balanced Investor",
}));
}
} finally {
setLoading(false);
}
})();
}, [user]);
const handleSave = async (e: React.FormEvent) => {
e.preventDefault();
setSaving(true);
setSaved(false);
try {
await authApi.updateSettings({
name: form.name,
financial_personality: form.financial_personality,
notifications_enabled: form.notifications_enabled,
ai_coaching_enabled: form.ai_coaching_enabled,
fraud_alerts_enabled: form.fraud_alerts_enabled,
});
await restoreSession();
setSaved(true);
setTimeout(() => setSaved(false), 3000);
} catch {
/* ignore */
} finally {
setSaving(false);
}
};
const Toggle = ({
label,
desc,
checked,
onChange,
}: {
label: string;
desc: string;
checked: boolean;
onChange: (v: boolean) => void;
}) => (
<div className="flex items-center justify-between gap-4 py-3 border-b border-white/5 last:border-0">
<div>
<p className="text-sm font-medium text-white">{label}</p>
<p className="text-xs text-zinc-500 mt-0.5">{desc}</p>
</div>
<button
type="button"
role="switch"
aria-checked={checked}
onClick={() => onChange(!checked)}
className={`relative h-6 w-11 rounded-full transition-colors ${checked ? "bg-emerald-500" : "bg-zinc-700"}`}
>
<span
className={`absolute top-0.5 h-5 w-5 rounded-full bg-white transition-transform ${checked ? "left-5" : "left-0.5"}`}
/>
</button>
</div>
);
if (loading) {
return (
<div className="flex justify-center py-20 text-zinc-500 text-sm">Loading settings...</div>
);
}
return (
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} className="flex flex-col gap-6 max-w-2xl">
<div>
<h1 className="text-3xl font-bold tracking-tight text-white">Settings</h1>
<p className="text-zinc-400 mt-1 text-sm">Account preferences and AI personalization</p>
</div>
<form onSubmit={handleSave} className="space-y-6">
<section className="glass-card p-6">
<h2 className="text-base font-semibold text-white flex items-center gap-2 mb-4">
<User className="h-4 w-4 text-blue-400" />
Profile
</h2>
<div className="space-y-4">
<div>
<label className="text-xs text-zinc-400">Display name</label>
<input
value={form.name}
onChange={(e) => setForm({ ...form, name: e.target.value })}
className="mt-1 w-full rounded-xl border border-white/10 bg-white/5 px-3 py-2 text-sm text-white focus:border-emerald-500/40 focus:outline-none"
/>
</div>
<div>
<label className="text-xs text-zinc-400">Email</label>
<input
value={form.email}
disabled
className="mt-1 w-full rounded-xl border border-white/10 bg-white/[0.02] px-3 py-2 text-sm text-zinc-500 cursor-not-allowed"
/>
</div>
<div>
<label className="text-xs text-zinc-400">Financial personality</label>
<select
value={form.financial_personality}
onChange={(e) => setForm({ ...form, financial_personality: e.target.value })}
className="mt-1 w-full rounded-xl border border-white/10 bg-white/5 px-3 py-2 text-sm text-white focus:border-emerald-500/40 focus:outline-none"
>
{PERSONALITIES.map((p) => (
<option key={p} value={p} className="bg-zinc-900">
{p}
</option>
))}
</select>
</div>
</div>
</section>
<section className="glass-card p-6">
<h2 className="text-base font-semibold text-white flex items-center gap-2 mb-2">
<Bell className="h-4 w-4 text-amber-400" />
Notifications & AI
</h2>
<Toggle
label="Push notifications"
desc="Fraud alerts, insights, and budget warnings"
checked={form.notifications_enabled}
onChange={(v) => setForm({ ...form, notifications_enabled: v })}
/>
<Toggle
label="AI coaching"
desc="Weekly coach reports and dashboard briefings"
checked={form.ai_coaching_enabled}
onChange={(v) => setForm({ ...form, ai_coaching_enabled: v })}
/>
<Toggle
label="Fraud alerts"
desc="Real-time suspicious activity notifications"
checked={form.fraud_alerts_enabled}
onChange={(v) => setForm({ ...form, fraud_alerts_enabled: v })}
/>
</section>
<section className="glass-card p-6 flex items-start gap-3">
<Shield className="h-5 w-5 text-emerald-400 flex-shrink-0 mt-0.5" />
<div className="text-sm text-zinc-400">
<p className="text-white font-medium">Security</p>
<p className="mt-1">Sessions use JWT encryption. Password changes require backend support in this demo.</p>
</div>
</section>
<button
type="submit"
disabled={saving}
className="flex items-center justify-center gap-2 rounded-xl bg-gradient-to-r from-emerald-500 to-cyan-500 px-6 py-3 text-sm font-semibold text-white disabled:opacity-50 w-full sm:w-auto"
>
{saving ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : saved ? (
<CheckCircle2 className="h-4 w-4" />
) : (
<Save className="h-4 w-4" />
)}
{saved ? "Saved" : "Save changes"}
</button>
</form>
<div className="glass-card p-4 flex items-center gap-2 text-xs text-zinc-500">
<Sparkles className="h-4 w-4 text-cyan-400" />
User ID: {user?.user_id?.slice(0, 8) ?? "—"}…
</div>
</motion.div>
);
}