Account Management Page Design
Summary
Add a user-facing account management page at /account where logged-in users can change their email, change their password, and delete their account. Single page with sectioned forms.
Routes & Controller
Single AccountController with these routes:
| Method | Route | Name | Purpose |
|---|---|---|---|
| GET | /account |
app_account |
Render page with all 3 forms |
| POST | /account/change-email |
app_account_change_email |
Process email change |
| GET | /account/verify-email/{token} |
app_account_verify_email |
Confirm new email |
| POST | /account/cancel-email-change |
app_account_cancel_email_change |
Cancel pending email change |
| POST | /account/change-password |
app_account_change_password |
Process password change |
| POST | /account/delete |
app_account_delete |
Process account deletion |
All routes require IS_AUTHENTICATED_FULLY (no remember-me for sensitive operations). Each POST redirects to app_account with a flash message on success, or re-renders the page with form errors on failure.
Forms
-
ChangeEmailFormType- Field:email(EmailType, pre-filled with current email). Validation: NotBlank, Email. -
AccountChangePasswordFormType- Fields:currentPassword(PasswordType) +plainPassword(RepeatedType with same validation as existing registration/reset forms: NotBlank, Length min 8, PasswordStrength WEAK, NotCompromisedPassword). Separate from the existingChangePasswordFormTypeused in the reset flow (which has no current password field). -
DeleteAccountFormType- Field:password(PasswordType). Validation: NotBlank. Controller verifies the password matches.
Email Change Flow
- User submits new email via
ChangeEmailFormType. - Controller validates form and checks new email isn't already taken.
- Store new email in
pendingEmailfield on User entity. Current email stays active. - Generate verification token (reuse
EmailVerificationService.generateToken()) and store on user. - Send verification email to the new address with confirmation link.
- Flash: "A verification link has been sent to {newEmail}. Your email won't change until you confirm."
- When user clicks the link (
/account/verify-email/{token}): - Validate token (not expired, matches user)
- Update
emailtopendingEmail, clearpendingEmail - Clear verification token
- Flash: "Your email has been updated."
- Redirect to
/account
Cancel Email Change
- While a
pendingEmailexists, the email change form is hidden. - An info banner shows: "Pending email change to {pendingEmail}. Check your inbox." with a "Cancel" button.
- The cancel route clears
pendingEmail,verificationToken, andverificationTokenExpiresAt. - Flash: "Email change cancelled."
The existing verificationToken and verificationTokenExpiresAt fields are reused since a user won't be doing initial registration verification and email change simultaneously.
Password Change Flow
- User submits
AccountChangePasswordFormTypewith current password + new password (repeated). - Controller verifies current password using
UserPasswordHasherInterface::isPasswordValid(). - If current password is wrong, add form error to
currentPasswordfield. - If valid, hash new password and persist.
- Flash: "Your password has been updated."
- Redirect to
/account.
User stays logged in after password change.
Account Deletion Flow
- User submits
DeleteAccountFormTypewith their password. - Controller verifies password.
- If wrong, add form error.
- If valid:
- Remove user from database (hard delete, cascade removes related entities)
- Invalidate session (log user out)
- Flash: "Your account has been deleted."
- Redirect to homepage
Entity Changes
Add to User entity:
- pendingEmail (string, 180 chars, nullable) - stores new email awaiting verification
New Doctrine migration to add pending_email column.
Template & UI
Single template templates/account/index.html.twig:
- Extends
base.html.twig - Navigation bar: app name left, "Account" link + user email + logout right (visible on all pages when logged in)
- Page title: "Account Settings"
- Three stacked sections, each with heading, form, and submit button:
- "Email" section (with pending email banner when applicable)
- "Password" section
- "Delete Account" section (red/danger button, warning text)
- Flash messages at top (reuse existing styles)
- Uses
.auth-containerstyle widened to max-width 500px
Testing
Functional tests in AccountControllerTest:
- Renders account page for authenticated users
- Redirects to login for unauthenticated users
- Email change: validates form, stores pending email, sends verification email
- Cancel email change: clears pending email and token
- Email verification: confirms token, updates email
- Password change: validates current password, updates password
- Account deletion: validates password, removes user, logs out
Red/green approach: write failing tests first, then implement.