2 Commits

Author SHA1 Message Date
MechaCat02
c95c1805df feat: upload flow revamp (0.20.0)
- `/upload` is now manga-only with optional N initial chapters
  staged inline.
- Additional chapters from a new `/manga/[id]/upload-chapter` route,
  reached via an "Upload chapter" button on the manga page.
- New `ChapterPagesEditor` component: thumbnails next to each row,
  click-to-preview-modal, drag-drop + reorder.
- Pages renamed to `page-NNN.<ext>` before multipart submission;
  original filenames shown as dimmed reference text during upload
  and dropped on submit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 18:59:22 +02:00
MechaCat02
b259d1f571 feat: drag-drop upload page for manga and chapters
Frontend-only branch consuming the multipart endpoints from feat/uploads.

- /upload page with two sections:
  - "Create manga": title (required), author, description, optional
    cover. Submit posts the FormData to POST /api/v1/mangas via the
    existing createManga client.
  - "Upload chapter": manga selector (preloaded via listMangas
    sort=title, limit=200), chapter number, optional title, and a
    drag-drop zone for page images. Pages render in an ordered list
    with up/down/remove controls so the user can fix order without
    re-uploading. The same hidden file input is used by both the
    "browse" link and Playwright's setInputFiles, so the e2e test
    exercises the real submission code path even though it doesn't
    simulate the drag mechanics.

- Client-side preflight in lib/upload-validation.ts (extracted so
  Vitest can target it directly): rejects files over 20 MiB with a
  sized message and rejects MIME types outside the
  jpeg/png/webp/gif/avif whitelist. Files with an empty file.type fall
  through to the backend's magic-byte sniff, which stays the
  authoritative check. The submit button is disabled while any pending
  page has a client-side error, so an oversized file never reaches the
  network.

- API errors are surfaced via the envelope: 401 redirects to /login,
  everything else is rendered as the form's role=alert message. The
  backend's 415/413/422/409 message strings carry enough context that
  the user can act on them without us repeating the field name
  client-side (matches what we already surface for /auth errors).

- /upload requires auth: anonymous users see a "Sign in to upload"
  prompt linking to /login instead of empty forms.

Vitest coverage (10 cases):
- validateImageFile null on small images and on each of the five
  whitelisted MIMEs.
- Oversized files → sized "too large" message that names the file.
- Non-image MIME → "unsupported image type X" naming the type.
- Empty file.type → passes (deferred to backend sniff).
- formatBytes handles B / KiB / MiB.

Playwright coverage (e2e/upload.spec.ts, 4 cases):
- Anonymous user sees the sign-in prompt.
- A "page.png" whose bytes are a PDF (client validator passes because
  it trusts the declared MIME for preflight) reaches the mocked
  backend, which 415s, and the form renders the backend's message.
- Happy path: create a manga, then upload a 2-page chapter, with both
  successes asserted from the mocked 201 responses.
- A 21 MiB file is added to the pages list with a "too large" error,
  the submit button stays disabled, and zero POSTs leave the browser.

Lockstep version bump to 0.9.0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 22:54:00 +02:00