Add settings page profile section (task 12.1)
Create src/app/app/settings/page.tsx as a client component with a Profile section: display name input pre-filled from session with a Save button (PUT /api/auth/profile), read-only email field, and success/error feedback. Mark task 12.1 done in tasks.md. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
3f3cb6eebf
commit
9514a987e3
2 changed files with 132 additions and 1 deletions
|
|
@ -68,7 +68,7 @@
|
||||||
|
|
||||||
## 12. Settings Page
|
## 12. Settings Page
|
||||||
|
|
||||||
- [ ] 12.1 `[sonnet]` Create `src/app/app/settings/page.tsx` — Profile section: display name input with save, read-only email
|
- [x] 12.1 `[sonnet]` Create `src/app/app/settings/page.tsx` — Profile section: display name input with save, read-only email
|
||||||
- [ ] 12.2 `[sonnet]` Add Security section: change password form (current/new/confirm) for credentials users, "Signed in via Google" for OAuth users
|
- [ ] 12.2 `[sonnet]` Add Security section: change password form (current/new/confirm) for credentials users, "Signed in via Google" for OAuth users
|
||||||
- [ ] 12.3 `[sonnet]` Add Danger Zone section: delete account button with confirmation dialog (type "DELETE" to confirm)
|
- [ ] 12.3 `[sonnet]` Add Danger Zone section: delete account button with confirmation dialog (type "DELETE" to confirm)
|
||||||
- [ ] 12.4 `[haiku]` Add back navigation link to `/app`
|
- [ ] 12.4 `[haiku]` Add back navigation link to `/app`
|
||||||
|
|
|
||||||
131
src/app/app/settings/page.tsx
Normal file
131
src/app/app/settings/page.tsx
Normal file
|
|
@ -0,0 +1,131 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import { useSession } from "next-auth/react";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
|
||||||
|
import { CheckCircle, AlertCircle } from "lucide-react";
|
||||||
|
|
||||||
|
export default function SettingsPage() {
|
||||||
|
const { data: session, update } = useSession();
|
||||||
|
const [displayName, setDisplayName] = useState("");
|
||||||
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
|
const [successMessage, setSuccessMessage] = useState<string | null>(null);
|
||||||
|
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
||||||
|
|
||||||
|
// Pre-fill display name from session
|
||||||
|
useEffect(() => {
|
||||||
|
if (session?.user?.name) {
|
||||||
|
setDisplayName(session.user.name);
|
||||||
|
}
|
||||||
|
}, [session?.user?.name]);
|
||||||
|
|
||||||
|
async function handleSaveProfile(e: React.FormEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
setSuccessMessage(null);
|
||||||
|
setErrorMessage(null);
|
||||||
|
setIsSaving(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/auth/profile", {
|
||||||
|
method: "PUT",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ name: displayName }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const data = await response.json();
|
||||||
|
setErrorMessage(data.error ?? "Failed to save profile.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh session so the new name propagates to the nav bar
|
||||||
|
await update();
|
||||||
|
setSuccessMessage("Profile updated successfully.");
|
||||||
|
} catch {
|
||||||
|
setErrorMessage("An unexpected error occurred. Please try again.");
|
||||||
|
} finally {
|
||||||
|
setIsSaving(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const email = session?.user?.email ?? "";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="max-w-xl mx-auto py-10 px-4 space-y-6">
|
||||||
|
<h1 className="text-2xl font-bold font-mono tracking-tight">Settings</h1>
|
||||||
|
|
||||||
|
{/* Profile section */}
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-base font-mono">Profile</CardTitle>
|
||||||
|
<CardDescription className="text-xs">
|
||||||
|
Update your display name. Your email cannot be changed here.
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<form onSubmit={handleSaveProfile} className="space-y-4">
|
||||||
|
{/* Feedback messages */}
|
||||||
|
{successMessage && (
|
||||||
|
<div className="flex items-center gap-2 rounded-md bg-green-500/10 border border-green-500/20 p-3">
|
||||||
|
<CheckCircle className="h-4 w-4 text-green-500 flex-shrink-0" />
|
||||||
|
<p className="text-sm text-green-600 dark:text-green-400 font-mono">{successMessage}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{errorMessage && (
|
||||||
|
<div className="flex items-center gap-2 rounded-md bg-destructive/10 border border-destructive/20 p-3">
|
||||||
|
<AlertCircle className="h-4 w-4 text-destructive flex-shrink-0" />
|
||||||
|
<p className="text-sm text-destructive font-mono">{errorMessage}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Display name */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="displayName" className="text-xs font-mono">
|
||||||
|
Display Name
|
||||||
|
</Label>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Input
|
||||||
|
id="displayName"
|
||||||
|
type="text"
|
||||||
|
placeholder="Your name"
|
||||||
|
value={displayName}
|
||||||
|
onChange={(e) => setDisplayName(e.target.value)}
|
||||||
|
className="font-mono text-sm flex-1"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
disabled={isSaving}
|
||||||
|
className="font-mono text-sm shrink-0"
|
||||||
|
>
|
||||||
|
{isSaving ? "Saving…" : "Save"}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Read-only email */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="email" className="text-xs font-mono">
|
||||||
|
Email
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="email"
|
||||||
|
type="email"
|
||||||
|
value={email}
|
||||||
|
readOnly
|
||||||
|
disabled
|
||||||
|
className="font-mono text-sm bg-muted/50 text-muted-foreground cursor-not-allowed"
|
||||||
|
/>
|
||||||
|
<p className="text-xs text-muted-foreground font-mono">
|
||||||
|
Email address cannot be changed.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue