feat(client): profile picture upload via expo-image-picker #217

Closed
opened 2026-06-21 16:38:56 +00:00 by james · 0 comments
Owner

Context

The universal client's /profile screen shows the user's picture as a read-only initials avatar with a "manage from web" hint. The picture upload flow that already exists on the API side (/api/profile/picture) is reachable only through the deleted Next.js PWA. Native (Android, Flatpak) users have no way to set a picture without a separate browser session.

Source

apps/client/app/(app)/profile.tsx lines 28-31:

Picture upload is intentionally web-only on the existing PWA today; the universal client surfaces it as a "manage from web" hint until expo-image-picker is wired up in a follow-up ticket. The avatar itself renders from initials when no picture is set.

PR #201 ("Out of scope"):

Picture upload on the universal client — needs expo-image-picker, follow-up ticket.

Scope

  • Add expo-image-picker to apps/client/ (pure-JS picker UI on web; native picker on Android).
  • Wire a "Change picture" action on the Profile screen that opens the picker, validates size/MIME locally, and POSTs the bytes to /api/profile/picture via a new @carol/api-client mutation hook (useUpdateProfilePicture).
  • Add a "Remove picture" action that DELETEs /api/profile/picture.
  • Match the existing API behaviour: image is resized server-side via sharp (already in apps/api); per-user scoping via the storage abstraction (apps/api/lib/storage/).
  • All strings via react-i18next; tokens via useTheme().

Acceptance criteria

  • Selecting an image on web and Android uploads it through the typed client and the avatar refreshes.
  • Removing the picture falls back to initials.
  • Image size + MIME validation matches the server-side limits.
  • Tests cover the new hook (mock the picker).
  • No new permissions baked into the Android manifest beyond what expo-image-picker requires; documented in apps/client/README.md.

Out of scope

  • Cropping / rotation in-app (the API's sharp pipeline handles resize).
  • Drag-and-drop on the web target (out of pattern for the rest of the screen).

Composes with

  • #176 (universal client epic).
  • #142 (closed — Profile screen rebuild).
  • ADR-0018 (storage abstraction).
## Context The universal client's `/profile` screen shows the user's picture as a read-only initials avatar with a "manage from web" hint. The picture upload flow that already exists on the API side (`/api/profile/picture`) is reachable only through the deleted Next.js PWA. Native (Android, Flatpak) users have no way to set a picture without a separate browser session. ## Source `apps/client/app/(app)/profile.tsx` lines 28-31: > Picture upload is intentionally web-only on the existing PWA today; the universal client surfaces it as a "manage from web" hint until expo-image-picker is wired up in a follow-up ticket. The avatar itself renders from initials when no picture is set. PR #201 ("Out of scope"): > Picture upload on the universal client — needs `expo-image-picker`, follow-up ticket. ## Scope - Add `expo-image-picker` to `apps/client/` (pure-JS picker UI on web; native picker on Android). - Wire a "Change picture" action on the Profile screen that opens the picker, validates size/MIME locally, and POSTs the bytes to `/api/profile/picture` via a new `@carol/api-client` mutation hook (`useUpdateProfilePicture`). - Add a "Remove picture" action that DELETEs `/api/profile/picture`. - Match the existing API behaviour: image is resized server-side via `sharp` (already in `apps/api`); per-user scoping via the storage abstraction (`apps/api/lib/storage/`). - All strings via `react-i18next`; tokens via `useTheme()`. ## Acceptance criteria - [ ] Selecting an image on web and Android uploads it through the typed client and the avatar refreshes. - [ ] Removing the picture falls back to initials. - [ ] Image size + MIME validation matches the server-side limits. - [ ] Tests cover the new hook (mock the picker). - [ ] No new permissions baked into the Android manifest beyond what `expo-image-picker` requires; documented in `apps/client/README.md`. ## Out of scope - Cropping / rotation in-app (the API's `sharp` pipeline handles resize). - Drag-and-drop on the web target (out of pattern for the rest of the screen). ## Composes with - #176 (universal client epic). - #142 (closed — Profile screen rebuild). - ADR-0018 (storage abstraction).
james closed this issue 2026-06-23 14:01:59 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
james/carol#217
No description provided.