diff --git a/openspec/changes/user-accounts/tasks.md b/openspec/changes/user-accounts/tasks.md index 7162cf9..661c7e8 100644 --- a/openspec/changes/user-accounts/tasks.md +++ b/openspec/changes/user-accounts/tasks.md @@ -68,7 +68,7 @@ ## 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.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` diff --git a/src/app/app/settings/page.tsx b/src/app/app/settings/page.tsx new file mode 100644 index 0000000..61590d5 --- /dev/null +++ b/src/app/app/settings/page.tsx @@ -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(null); + const [errorMessage, setErrorMessage] = useState(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 ( +
+

Settings

+ + {/* Profile section */} + + + Profile + + Update your display name. Your email cannot be changed here. + + + +
+ {/* Feedback messages */} + {successMessage && ( +
+ +

{successMessage}

+
+ )} + {errorMessage && ( +
+ +

{errorMessage}

+
+ )} + + {/* Display name */} +
+ +
+ setDisplayName(e.target.value)} + className="font-mono text-sm flex-1" + required + /> + +
+
+ + {/* Read-only email */} +
+ + +

+ Email address cannot be changed. +

+
+
+
+
+
+ ); +}