Profile
The profile module is the per-user surface — the place where the signed-in user edits their own identity. Three sub-pages cover the three things that change over a user’s lifetime:
-
Name and Email — display identity
-
Photo — avatar with crop
-
Password — change credentials
-
Source:
src/features/user/profile/ -
API:
src/api/users/,src/api/auth/ -
Routes:
/user/profile/name-and-email,/user/profile/photo,/user/profile/password
The whole area sits under the user menu in the dashboard header.
Layout
Profile.tsx is a section root (VerticalTabs + Outlet) — same pattern as Accounts and Settings. The three sub-pages are sibling tabs.
There is no top-level “profile overview” page. The user lands on Name and Email by default via the parent route’s index redirect.
Name and Email
Route
/user/profile/name-and-email
Model hook
useNameAndEmailViewModel responsibilities:
- Yup schema:
first_nameandlast_namerequired;emailis read-only in the UI (email changes typically need verification flows the mock does not implement). - Edit toggle, cancel, submit handlers.
- Calls
useUpdateUserMutationfromsrc/api/users/users.apion submit. - Dispatches
setUserInfoto update theuserslice on success (so the header avatar and name refresh immediately). - Maps server errors per-field via
useFieldsFromError.
API
PUT /users/:id with { first_name, last_name }. The response updates the slice and the UI re-renders.
Why email is read-only
Changing an email address is a flow, not a field edit. A real implementation needs:
- A verification email to the new address.
- A grace window where both addresses work.
- An audit log of the change.
The mock backend does not model any of this. In production you would route the email change through a dedicated endpoint and dialog, separate from the name edit.
Photo
Route
/user/profile/photo
Model hook
usePhotoViewModel responsibilities:
- File pick via
react-dropzone(getRootProps,getInputProps,isDragActive). - Crop via
useAvatarEditor(which wrapsreact-easy-crop):crop,zoom,rotation,onCropComplete. - Two-step upload flow on save:
useUploadAvatarMutation({ base64, fileName })→ returns{ path }.useUpdateUserMutation({ id, avatar: path })→ updates the user record.
- On success, dispatches
setUserInfoso the header avatar refreshes.
Components
EditImage— the crop UI surface (src/components/editImage/).AvatarCard— the display mode showing the current photo.
File flow
User picks file
↓
react-dropzone reads it as a base64 data URL
↓
react-easy-crop produces a cropped/rotated canvas
↓
canvas → base64 → POST /upload-avatar → { path }
↓
PUT /users/:id with { avatar: path }
↓
setUserInfo dispatches → header re-rendersThe two-step split matters: uploading the image and updating the user are separate concerns and may be authorized differently in production.
Password
Route
/user/profile/password
Model hook
usePasswordViewModel responsibilities:
- Yup schema with three fields:
passwordOld— current password (required)password— new password (required, min 4, must differ frompasswordOld)passwordRepeat— must matchpassword
- Show/hide toggles per field.
- Calls
useUpdateUserMutation(fromsrc/api/auth/auth.api) on submit.
API
PUT /users/:id with { email, password, id }. In a real backend this would be a dedicated change-password endpoint that verifies passwordOld server-side — the mock simply accepts the new password.
Note: the helper text on the form notes that old-password verification is mocked. When wiring a real backend, ensure the endpoint enforces it.
PasswordStrength
The new password field is paired with the PasswordStrength component (powered by zxcvbn). The strength score updates as the user types and visually reinforces the schema’s “min 4” rule with a qualitative bar.
Notifications affordance
The /user route group also includes a notifications entry (the bell icon area, shared with the dashboard layout). It is navigation-only, not a separate page — clicking the entry opens the notifications drawer wherever the user currently is.
See Notifications for the drawer wiring.
Logout
/user/logout is the third entry under /user. It is a redirect-only route that dispatches the logout action from user.slice (see Auth → Logout).
How profile changes propagate
All three flows end with the same step: setUserInfo (or setUser) dispatched to the user slice. Components that show the current user — the header avatar, the welcome line, the user menu — subscribe to that slice and re-render automatically.
This is why profile is a sub-area of /user/ and not under /application/: it edits the signed-in user’s own record, not other users’. Editing another user lives in Accounts.
Permissions
No special abilities are required — every authenticated user can edit their own profile. The mutations on the backend should verify that the :id in the URL matches the signed-in user (the mock does not enforce this).
Forking the profile module
If your product needs different profile fields (phone, address, preferences):
- Add the fields to
UserTypeinsrc/api/users/users.types.ts. - Either extend
nameAndEmailif the new fields are display identity, or add a new sub-page tab if they are a separate concern. - Update the yup schema and the form view.
- Adjust the
PUT /users/:idpayload — RTK Query mutations transparently send the new fields.
The two-step upload flow for photo is a good template for any “user uploads a file and we save a reference” pattern (CV upload, signature upload, etc.).
Anti-patterns
- Editing the current user from
/application/accounts/users. That page is for editing other users. Self-edit goes through the profile module so the dispatching pattern is consistent. - Calling
setUserInfowithout refetching the user. The dispatch updates the slice; if your mutation does not return the full user record, the slice will diverge from the server. Always either return the full record or refetch. - One huge “edit profile” page. Coreola splits it into three sub-pages on purpose — each form has its own concerns and submission semantics.
- Storing the avatar URL with the cropped image baked in. Coreola stores the path returned by
/upload-avatarand renders the avatar from that path so the crop is reversible if needed later.
Next steps
- Auth — the sign-in flow that populates the slice
- Accounts — the admin surface for editing other users
- Notifications — the drawer reachable from the
/usermenu