Problem
Avatar upload UI will appear in web-account now and likely web-applications later. Per repo convention (separate PR per component), ship the shared component first with no runtime API coupling.
Scope
src/components/AvatarUploader/:
AvatarUploader.tsx: avatar circle, hover overlay (upload + remove buttons), file input, drag-drop
- Props:
currentUrl?: string, onUpload: (file: File) => Promise<string>, onRemove: () => Promise<void>, maxBytes?: number, acceptedMimes?: string[]
- States: idle, uploading (spinner), success (flash + new image), error (toast hook)
- Client-side validation: MIME and size (mirrors backend caps)
- Storybook story with mocked async handlers (all states)
- RTL tests: file accepted, file rejected (size), file rejected (mime), remove flow
- Export from package barrel
- A11y: keyboard-accessible buttons, alt text, aria-busy during upload
Out-of-scope
- Cropping / dimension editing
- Direct API calls (props-only contract — consumers wire I/O)
Acceptance criteria
Dependencies
None
Problem
Avatar upload UI will appear in
web-accountnow and likelyweb-applicationslater. Per repo convention (separate PR per component), ship the shared component first with no runtime API coupling.Scope
src/components/AvatarUploader/:AvatarUploader.tsx: avatar circle, hover overlay (upload + remove buttons), file input, drag-dropcurrentUrl?: string,onUpload: (file: File) => Promise<string>,onRemove: () => Promise<void>,maxBytes?: number,acceptedMimes?: string[]Out-of-scope
Acceptance criteria
api-client-authreleaselabel on merge publishes new versionDependencies
None