## Goal Create the file upload pipeline (signed URLs to GCS) and the reusable `<FileUpload />` component. No real logic yet, stubs only. ## Folders to create - [x] `src/app/api/v1/uploads/sign/` - [x] `src/app/api/v1/uploads/[id]/` - [x] `src/app/uploads-demo/` - [x] `src/lib/uploads/` - [x] `src/components/uploads/` ## Files to create ### Routes - [x] `src/app/api/v1/uploads/sign/route.ts` — `POST`: returns signed upload URL for GCS; stub returns 501 - [x] `src/app/api/v1/uploads/[id]/route.ts` — `GET`: returns signed download URL for an uploaded file - [x] `src/app/uploads-demo/page.tsx` — internal demo page that mounts `<FileUpload />` so this ticket can be tested in isolation ### Library - [x] `src/lib/uploads/gcs.ts` — GCS client singleton, bucket config - [x] `src/lib/uploads/service.ts` — `createSignedUploadUrl()`, `createSignedDownloadUrl()`, `recordUpload()` - [x] `src/lib/uploads/validation.ts` — allowed MIME types, max size, filename sanitization - [x] `src/lib/uploads/types.ts` — `UploadRecord`, `SignUploadRequest`, `SignUploadResponse` types ### Components - [x] `src/components/uploads/FileUpload.tsx` — standalone component, demo page consumes it; documented public props in contract doc - [x] `src/components/uploads/FilePreview.tsx` — shows uploaded file name + replace/remove actions - [x] `src/components/uploads/UploadProgress.tsx` — progress bar during upload ## Acceptance criteria - [x] All folders and files listed above exist and the project compiles. - [x] `<FileUpload />` renders a placeholder dropzone on `/uploads-demo` with no real upload behavior. - [x] Both API routes return 501.
Goal
Create the file upload pipeline (signed URLs to GCS) and the reusable
<FileUpload />component. No real logic yet, stubs only.Folders to create
src/app/api/v1/uploads/sign/src/app/api/v1/uploads/[id]/src/app/uploads-demo/src/lib/uploads/src/components/uploads/Files to create
Routes
src/app/api/v1/uploads/sign/route.ts—POST: returns signed upload URL for GCS; stub returns 501src/app/api/v1/uploads/[id]/route.ts—GET: returns signed download URL for an uploaded filesrc/app/uploads-demo/page.tsx— internal demo page that mounts<FileUpload />so this ticket can be tested in isolationLibrary
src/lib/uploads/gcs.ts— GCS client singleton, bucket configsrc/lib/uploads/service.ts—createSignedUploadUrl(),createSignedDownloadUrl(),recordUpload()src/lib/uploads/validation.ts— allowed MIME types, max size, filename sanitizationsrc/lib/uploads/types.ts—UploadRecord,SignUploadRequest,SignUploadResponsetypesComponents
src/components/uploads/FileUpload.tsx— standalone component, demo page consumes it; documented public props in contract docsrc/components/uploads/FilePreview.tsx— shows uploaded file name + replace/remove actionssrc/components/uploads/UploadProgress.tsx— progress bar during uploadAcceptance criteria
<FileUpload />renders a placeholder dropzone on/uploads-demowith no real upload behavior.