184 lines
6.6 KiB
TypeScript
184 lines
6.6 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useTransition } from "react";
|
|
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Label } from "@/components/ui/label";
|
|
import { Separator } from "@/components/ui/separator";
|
|
import { User, Lock, Shield } from "lucide-react";
|
|
import { toast } from "sonner";
|
|
import { authClient, useSession } from "@/lib/auth/client";
|
|
|
|
export default function SettingsPage() {
|
|
const { data: session, isPending } = useSession();
|
|
|
|
const [name, setName] = useState("");
|
|
const [nameLoading, startNameTransition] = useTransition();
|
|
|
|
const [currentPassword, setCurrentPassword] = useState("");
|
|
const [newPassword, setNewPassword] = useState("");
|
|
const [confirmPassword, setConfirmPassword] = useState("");
|
|
const [passwordLoading, startPasswordTransition] = useTransition();
|
|
|
|
// Populate name field once session loads
|
|
const displayName = name || session?.user?.name || "";
|
|
|
|
function handleNameSave() {
|
|
if (!displayName.trim()) {
|
|
toast.error("Name cannot be empty");
|
|
return;
|
|
}
|
|
startNameTransition(async () => {
|
|
const { error } = await authClient.updateUser({ name: displayName.trim() });
|
|
if (error) {
|
|
toast.error(error.message ?? "Failed to update name");
|
|
} else {
|
|
toast.success("Display name updated");
|
|
}
|
|
});
|
|
}
|
|
|
|
function handlePasswordChange() {
|
|
if (!currentPassword) {
|
|
toast.error("Current password is required");
|
|
return;
|
|
}
|
|
if (newPassword.length < 8) {
|
|
toast.error("New password must be at least 8 characters");
|
|
return;
|
|
}
|
|
if (newPassword !== confirmPassword) {
|
|
toast.error("Passwords do not match");
|
|
return;
|
|
}
|
|
startPasswordTransition(async () => {
|
|
const { error } = await authClient.changePassword({
|
|
currentPassword,
|
|
newPassword,
|
|
revokeOtherSessions: false,
|
|
});
|
|
if (error) {
|
|
toast.error(error.message ?? "Failed to change password");
|
|
} else {
|
|
toast.success("Password changed successfully");
|
|
setCurrentPassword("");
|
|
setNewPassword("");
|
|
setConfirmPassword("");
|
|
}
|
|
});
|
|
}
|
|
|
|
return (
|
|
<div className="p-6 max-w-2xl space-y-6">
|
|
<div>
|
|
<h1 className="text-2xl font-bold text-white">Account Settings</h1>
|
|
<p className="text-zinc-400 text-sm mt-1">Manage your profile and security preferences</p>
|
|
</div>
|
|
|
|
{/* Profile */}
|
|
<Card className="bg-zinc-900 border-zinc-800">
|
|
<CardHeader>
|
|
<CardTitle className="text-base font-medium text-zinc-300 flex items-center gap-2">
|
|
<User className="w-4 h-4 text-emerald-500" />
|
|
Profile
|
|
</CardTitle>
|
|
<CardDescription className="text-zinc-500">
|
|
Update your display name and view account info
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div className="space-y-1.5">
|
|
<Label className="text-zinc-300">Email</Label>
|
|
<Input
|
|
value={session?.user?.email ?? ""}
|
|
disabled
|
|
className="bg-zinc-800 border-zinc-700 text-zinc-400 cursor-not-allowed"
|
|
/>
|
|
<p className="text-xs text-zinc-500">Email address cannot be changed</p>
|
|
</div>
|
|
<div className="space-y-1.5">
|
|
<Label className="text-zinc-300">Display Name</Label>
|
|
<Input
|
|
value={name || session?.user?.name || ""}
|
|
onChange={(e) => setName(e.target.value)}
|
|
placeholder="Your name"
|
|
className="bg-zinc-800 border-zinc-700 text-white placeholder:text-zinc-500"
|
|
disabled={isPending}
|
|
/>
|
|
</div>
|
|
{session?.user && (
|
|
<div className="flex items-center gap-2 text-xs text-zinc-500">
|
|
<Shield className="w-3 h-3" />
|
|
Role: <span className="text-zinc-300 capitalize">{(session.user as { role?: string }).role ?? "moderator"}</span>
|
|
</div>
|
|
)}
|
|
<Button
|
|
onClick={handleNameSave}
|
|
disabled={nameLoading || isPending}
|
|
className="bg-emerald-600 hover:bg-emerald-500 text-white"
|
|
size="sm"
|
|
>
|
|
{nameLoading ? "Saving…" : "Save Name"}
|
|
</Button>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Separator className="bg-zinc-800" />
|
|
|
|
{/* Password */}
|
|
<Card className="bg-zinc-900 border-zinc-800">
|
|
<CardHeader>
|
|
<CardTitle className="text-base font-medium text-zinc-300 flex items-center gap-2">
|
|
<Lock className="w-4 h-4 text-emerald-500" />
|
|
Change Password
|
|
</CardTitle>
|
|
<CardDescription className="text-zinc-500">
|
|
Choose a strong password with at least 8 characters
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div className="space-y-1.5">
|
|
<Label className="text-zinc-300">Current Password</Label>
|
|
<Input
|
|
type="password"
|
|
value={currentPassword}
|
|
onChange={(e) => setCurrentPassword(e.target.value)}
|
|
placeholder="••••••••"
|
|
className="bg-zinc-800 border-zinc-700 text-white placeholder:text-zinc-500"
|
|
/>
|
|
</div>
|
|
<div className="space-y-1.5">
|
|
<Label className="text-zinc-300">New Password</Label>
|
|
<Input
|
|
type="password"
|
|
value={newPassword}
|
|
onChange={(e) => setNewPassword(e.target.value)}
|
|
placeholder="••••••••"
|
|
className="bg-zinc-800 border-zinc-700 text-white placeholder:text-zinc-500"
|
|
/>
|
|
</div>
|
|
<div className="space-y-1.5">
|
|
<Label className="text-zinc-300">Confirm New Password</Label>
|
|
<Input
|
|
type="password"
|
|
value={confirmPassword}
|
|
onChange={(e) => setConfirmPassword(e.target.value)}
|
|
placeholder="••••••••"
|
|
className="bg-zinc-800 border-zinc-700 text-white placeholder:text-zinc-500"
|
|
/>
|
|
</div>
|
|
<Button
|
|
onClick={handlePasswordChange}
|
|
disabled={passwordLoading}
|
|
className="bg-emerald-600 hover:bg-emerald-500 text-white"
|
|
size="sm"
|
|
>
|
|
{passwordLoading ? "Changing…" : "Change Password"}
|
|
</Button>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|