feat(frontend): avatar upload QOL — feedback, re-crop, drop, progress, history

Task 12 batch around the existing crop/preview flow:
- Success Notice (auto-dismiss ~2.5s) after an avatar upload.
- "Reposition" re-crops the current avatar without re-picking a file.
- Drag & drop an image straight onto the profile avatar circle.
- Real upload progress via a new XHR-backed api.upload (fetch has none);
  refreshOnce now also stores the renewed token (reload-restore fix).
- Local avatar history (last 5, IndexedDB, this-device-only) strip that
  re-uploads a stored crop. Stored as ArrayBuffer (Blob is not cloneable
  under structuredClone); label makes the per-device scope explicit.
- Added a "success" tone to Notice.

Infra (same session): registered @shared/@features/@pages/@app/@test path
aliases in vite.config + tsconfigs and rewrote deep ../../../ imports;
moved avatar components into components/avatar/ to keep folders at <=4 files.

Tests: avatarHistory cap/order, recrop visibility, drop-opens-cropper,
client upload/progress/401-retry. bun test 103 pass, lint 0 errors, build ok.
This commit is contained in:
loki5512344 2026-09-28 22:56:01 +02:00
parent e79b0aedd2
commit fd2e29cede
Signed by: boba
GPG key ID: 253067914055423B
92 changed files with 808 additions and 287 deletions

View file

@ -1,9 +1,22 @@
import tailwindcss from '@tailwindcss/vite'
import react from '@vitejs/plugin-react'
import { fileURLToPath } from 'node:url'
import { defineConfig } from 'vitest/config'
// Path aliases mirror tsconfig `paths` — register here once instead of writing `../../../` chains.
const src = fileURLToPath(new URL('./src', import.meta.url))
export default defineConfig({
plugins: [react(), tailwindcss()],
resolve: {
alias: {
'@shared': `${src}/shared`,
'@features': `${src}/features`,
'@pages': `${src}/pages`,
'@app': `${src}/app`,
'@test': `${src}/test`,
},
},
server: {
// Tests read the mod's theme presets (outside this package) to check the generated copy.
fs: process.env.VITEST ? { allow: ['.', '../mod/src/main/java/dev/loki/lovisual/features/theme/presets'] } : undefined,