feat: enhance feedback form with image upload, user/project context, and scene graph (#113)

* feat: enhance feedback form with image upload, user/project context, and scene graph

- Add invisible drag-and-drop zone that reveals on hover+drag with dashed border overlay
- Multi-image upload (max 5, max 5MB each) to Supabase Storage feedback-images bucket
- Subtle attach button + thumbnail previews with remove on hover
- Auto-capture authenticated user email/name from Better Auth session
- Pass projectId from editor context
- Snapshot scene graph (nodes + rootNodeIds) on submit
- DB migration adds user_email, user_name, project_id, images (jsonb), scene_graph (jsonb) columns
- Storage bucket + RLS policies for public read / service role write

* refactor: use existing user_id FK instead of denormalized email/name columns

Removed user_email and user_name — the user_id already links to the users table.
Simpler schema, no data duplication.

* fix: guard against undefined in removeImage

* refactor: direct Supabase Storage upload via signed URLs

Bypass Vercel's 4.5MB serverless body-size limit by uploading images
directly from the client to Supabase Storage.

- New createImageUploadUrls server action generates signed upload URLs
- Client PUTs files directly to Supabase (no bytes through Vercel)
- submitFeedback now receives only image paths, not FormData with files
- No migration changes needed (existing RLS policies support signed URLs)

* fix: remove relative class that broke dialog centering

twMerge was replacing the Dialog's fixed positioning with relative,
pushing the dialog to the bottom of the viewport.

---------

Co-authored-by: Anton Pascal <anton-pascal@users.noreply.github.com>
This commit is contained in:
Anton
2026-02-22 23:52:42 -05:00
committed by GitHub
co-authored by Anton Pascal
parent c0a7fd8067
commit c476d30303
5 changed files with 317 additions and 29 deletions
+4 -1
View File
@@ -4,8 +4,11 @@ import { id, createdAt } from '../../helpers'
export const feedback = pgTable('feedback', (t) => ({
id: id('feedback'),
userId: t.text('user_id'), // nullable — stores Better Auth user ID or null for anonymous
userId: t.text('user_id'),
projectId: t.text('project_id'),
message: t.text('message').notNull(),
images: t.jsonb('images').$type<string[]>(),
sceneGraph: t.jsonb('scene_graph'),
createdAt,
})).enableRLS()