Account
The account module owns the signed-in user's profile, avatar, notification preferences, security form, and deletion flow.
On this page
Where to look
| Area | Paths |
|---|---|
| Route | src/app/(dashboard)/account/page.tsx |
| Actions | src/features/account/server/actions/profile.ts, security.ts, notifications.ts, delete.ts |
| Queries | src/features/account/server/queries/account.ts |
| Persistence | src/features/account/server/persistence/profile.ts, notification-preferences.ts |
| UI | src/features/account/ui/profile-settings-form.tsx, avatar-uploader.tsx, notification-preferences-form.tsx, security-settings-form.tsx, delete-account-form.tsx |
| Shared | src/features/account/shared/form.ts |
| Related infrastructure | src/lib/auth/session, src/lib/auth/guards, src/lib/auth/rbac |
Code-backed account map
Account page and forms
The route renders account settings while feature UI owns profile, avatar, notifications, security, and deletion forms.
src/app/(dashboard)/account/page.tsxsrc/features/account/ui/profile-settings-form.tsxsrc/features/account/ui/security-settings-form.tsx
Server actions
Profile, notification preference, password, session, and deletion mutations stay behind authenticated server actions.
src/features/account/server/actions/profile.tssrc/features/account/server/actions/notifications.tssrc/features/account/server/actions/security.tssrc/features/account/server/actions/delete.ts
Reads and persistence
Account reads and writes go through query and persistence helpers instead of trusting client-supplied identity.
src/features/account/server/queries/account.tssrc/features/account/server/persistence/profile.tssrc/features/account/server/persistence/notification-preferences.ts
Account update flow
- 1
Resolve the signed-in profile
The page and actions use server-side identity helpers, not browser-provided profile ids.
src/app/(dashboard)/account/page.tsxsrc/lib/auth/session/profile.ts
- 2
Validate form input
Shared form helpers and action utilities normalize fields before profile, security, or notification mutations run.
src/features/account/shared/form.tssrc/features/account/server/actions/utils.ts
- 3
Write through persistence helpers
Profile and preference writes stay behind account-owned persistence modules.
src/features/account/server/persistence/profile.tssrc/features/account/server/persistence/notification-preferences.ts
- 4
Treat deletion as high risk
Account deletion is isolated in its own action and should be tested directly before customization.
src/features/account/server/actions/delete.tssrc/features/account/server/actions/__tests__/delete.test.ts
Profile update boundary
Source: src/features/account/server/actions/profile.ts
The signed-in server profile owns the update target; the browser never supplies the profile id.
export async function updateMyProfile(prevState: ActionResult, formData: FormData): Promise<ActionResult> {
const profile = await getActiveProfile();
if (!profile) {
return { success: false, message: "Not authenticated" };
}
if (hasReadOnlyAccess(profile)) {
return { success: false, message: "This account has read-only access and cannot make changes." };
}
const raw = {
name: formData.get("name")?.toString() ?? "",
phone: formData.get("phone")?.toString() ?? "",
locale: formData.get("locale")?.toString() ?? "",
timezone: formData.get("timezone")?.toString() ?? "",
avatar_url: formData.get("avatar_url")?.toString() ?? "",
job_title: formData.get("job_title")?.toString() ?? "",
bio: formData.get("bio")?.toString() ?? "",
github_url: formData.get("github_url")?.toString() ?? "",
linkedin_url: formData.get("linkedin_url")?.toString() ?? "",
x_url: formData.get("x_url")?.toString() ?? "",
};
const parsed = ProfileUpdateSchema.safeParse(raw);
if (!parsed.success) {
return { success: false, message: "Validation failed", errors: zodErrors(parsed.error) };
}Change rules
- Use the server session as the source of identity.
- Keep profile and preference writes behind server actions.
- Treat account deletion as a high-risk path and test it directly.
- Do not trust browser-supplied profile ids when the server already knows the signed-in profile.