Skip to content

feat: R2 image upload for recipe cover photos#272

Open
czearing wants to merge 5 commits into
mainfrom
fix/cover-overlay-v2
Open

feat: R2 image upload for recipe cover photos#272
czearing wants to merge 5 commits into
mainfrom
fix/cover-overlay-v2

Conversation

@czearing
Copy link
Copy Markdown
Contributor

Summary

  • Adds R2 presigned URL upload flow for recipe cover photos (presign → XHR PUT → confirm)
  • Compresses uploads to WebP at ≤1920px wide before uploading
  • Validates file type (JPEG/PNG/WebP/GIF), size (≤10 MB), and magic bytes client-side
  • Shows upload progress bar during XHR transfer
  • Controls positioned bottom-center with hover/focus reveal (no overlap with recipe emoji)
  • Uses existing Button component (variant="ghost" / "destructive", size="sm") and sonner toast for errors — no custom UI primitives

Test plan

  • All 8 unit tests pass (yarn test)
  • No lint errors (yarn lint)
  • Visual: "Add cover" button appears below empty cover area
  • Visual: "Change cover" + "Remove" buttons appear on hover over cover image (bottom-center)
  • Upload a JPEG/PNG — progress bar shows, cover updates after confirm
  • Upload an invalid file type — toast.error fires with correct message
  • Upload a file >10 MB — toast.error fires
  • Click "Remove" — cover clears

🤖 Generated with Claude Code

Caleb Zearing and others added 2 commits March 31, 2026 11:08
- Presigned URL upload flow (bypasses Vercel 4.5MB limit)
- Files land in uploads/pending/{userId}/, confirmed to uploads/public/
- Rate limited 20 uploads/hour via MongoDB TTL collection
- Auth enforced on both presign and confirm endpoints
- Client-side magic bytes validation + WebP compression
- Cover overlay buttons positioned top-right (avoids emoji overlap)
- Fix PUT /api/recipes/[id] to allow clearing imageURL with empty string
- 30 new tests across 3 files, 324 passing total

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…positioning

- Replace custom <button> elements with existing <Button> component (variant ghost/destructive, size sm)
- Replace inline error banner with sonner toast.error() calls
- Remove error status from UploadState — errors are ephemeral, not stateful
- Reposition controls to bottom-center with hover reveal (fixes emoji overlap bug)
- Strip all custom button/error CSS; only layout styles remain
- Update tests: mock sonner and assert toast.error calls instead of DOM text

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Mar 31, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
book-cook Error Error Mar 31, 2026 8:09pm

…de emoji

- Break cover out of AppShell padding via --page-x-padding/--page-y-padding CSS variables
- Increase cover height to clamp(280px, 38vh, 440px) for more editorial presence
- Add dark frosted-glass backdrop (rgba 0,0,0,0.48 + backdrop-filter blur) to hover controls
- Override design tokens locally so Button children render white on the dark backdrop
- Hide RecipeEmoji when imageURL exists (cover and emoji are interchangeable)
- Add mainHasCover padding-top so title has breathing room below full-bleed cover

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace separate change/remove buttons with a single camera icon that
  opens a Menu dropdown (Change cover / Remove cover)
- Use forwardRef on RecipeCoverUpload so RecipeHeader can trigger the
  file input via an "Add cover" ghost button in the header area
- Hide emoji when a cover image is present (interchangeable display)
- Full-bleed cover: negative margin + width calc using --page-x-padding
  CSS variables from AppShell
- Frosted-glass overlay (opacity 0 → 1 on hover/focus-within) with
  overridden design tokens for white button text on dark backdrop
- Progress bar during XHR upload; all errors go to sonner toast
- Update tests: mock Menu components to avoid Radix pointer-event
  complexity; assert toast.error calls instead of DOM error text

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Was centered (left: 50% + translateX(-50%)), now anchored to bottom-right.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant