# FeedBack — App Overview & Status > Last updated: May 2026 --- ## What It Is FeedBack is a self-hosted VFX review and approval platform for boutique studios. Internal teams (admins, producers, supervisors, artists) manage projects and shots, upload versioned video files, draw frame-accurate annotations, and leave timestamped comments. External clients receive token-gated review links and can approve, reject, or request changes — no login required. --- ## Current Status All core features are built and functional: | Area | Status | |------|--------| | Auth (login, roles, sessions) | ✅ Complete | | Projects & shots (CRUD) | ✅ Complete | | Version upload (local storage) | ✅ Complete | | Review player + frame timeline | ✅ Complete | | Annotation drawing (canvas) | ✅ Complete — persists across frame navigation | | Frame-anchored comments + replies | ✅ Complete | | Approval workflow | ✅ Complete | | Client management (internal) | ✅ Complete | | Client portal (external, token-gated) | ✅ Complete | | Tokenized review links | ✅ Complete | | Slack notifications | ✅ Complete (optional webhook) | | In-app notifications | ✅ Complete | | Email (nodemailer) | ⚙️ Wired, requires SMTP env vars | | S3 / R2 / MinIO / B2 storage | ⚙️ Wired, requires env vars | | Docker Compose deployment | ✅ Complete | --- ## Route Map ### Public / Unauthenticated | Route | Description | |-------|-------------| | `/login` | Credentials sign-in page | | `/client/[token]` | External client project overview — lists shots visible to client | | `/client/[token]/review/[versionId]` | External client review page — video player, comments, approve/reject | ### Dashboard (requires login) | Route | Description | |-------|-------------| | `/dashboard` | Home — stats cards, shot queue, recent activity | | `/projects` | Project list | | `/projects/[id]` | Project detail — shot grid, stats | | `/projects/[id]/shots/[shotId]` | Shot detail — version list, approval history | | `/review/[versionId]` | Full-screen review player (internal) | | `/clients` | Client list — ADMIN/PRODUCER only | | `/clients/[clientId]` | Client detail — linked projects, active review sessions | | `/settings` | User profile settings | --- ## API Routes ### Internal (authenticated) | Method | Route | Purpose | |--------|-------|---------| | GET/POST | `/api/projects` | List / create projects | | GET/POST/DELETE | `/api/shots` | Shot CRUD | | GET | `/api/versions/[versionId]/comments` | Fetch comments for a version | | GET | `/api/versions/[versionId]/annotations` | Fetch annotations for a version | | GET/POST | `/api/clients` | List / create clients | | GET/POST/DELETE | `/api/review-sessions` | Manage tokenized review links | | GET/POST | `/api/notifications` | In-app notification CRUD | | GET | `/api/files/[...key]` | Serve locally-stored files (no auth) | | POST | `/api/upload/local` | Local file upload handler | ### Client portal (no auth — token-gated) | Method | Route | Purpose | |--------|-------|---------| | GET | `/api/client/[token]/project` | Project + shots (CLIENT_REVIEW / REVISIONS / APPROVED / FINAL only) | | GET | `/api/client/[token]/versions/[versionId]` | Version detail + comments | | POST | `/api/client/[token]/approve` | Submit approval decision | | POST | `/api/client/[token]/comment` | Post a comment | --- ## Server Actions (`/actions`) | File | Exports | |------|---------| | `projects.ts` | `createProject`, `updateProject` | | `shots.ts` | `createShot`, `updateShotStatus`, `getShotById` | | `versions.ts` | `createVersion`, `setLatestVersion` | | `comments.ts` | `addComment`, `addReply`, `resolveComment` | | `annotations.ts` | `saveAnnotation`, `getAnnotationsForVersion`, `getAnnotationsForFrame` | | `approvals.ts` | `submitApproval` | --- ## Database Schema ### Enums | Enum | Values | |------|--------| | `Role` | `ADMIN` · `PRODUCER` · `SUPERVISOR` · `ARTIST` · `CLIENT` | | `ProjectStatus` | `ACTIVE` · `ON_HOLD` · `COMPLETED` · `ARCHIVED` | | `ShotStatus` | `WAITING` · `IN_PROGRESS` · `INTERNAL_REVIEW` · `CLIENT_REVIEW` · `REVISIONS` · `APPROVED` · `FINAL` | | `ShotPriority` | `LOW` · `NORMAL` · `HIGH` · `URGENT` | | `ApprovalStatus` | `PENDING_REVIEW` · `APPROVED` · `REJECTED` · `NEEDS_CHANGES` | | `NotificationType` | `VERSION_UPLOADED` · `FEEDBACK_ADDED` · `SHOT_APPROVED` · `SHOT_REJECTED` · `COMMENT_REPLY` · `MENTION` · `REVISION_REQUESTED` | ### Models #### `User` Core identity. Has a `role` (see enum). `passwordHash` used for credentials login. NextAuth `Account` and `Session` models hang off this. | Key field | Type | Notes | |-----------|------|-------| | `id` | cuid | PK | | `email` | String | unique | | `role` | Role | default `ARTIST` | | `passwordHash` | String? | bcrypt hash | | `isActive` | Boolean | soft-disable | #### `Client` Represents an external studio or client company. | Key field | Type | Notes | |-----------|------|-------| | `company` | String | | | `contactPerson` | String | | | `email` | String | unique | | `phone`, `notes`, `logoUrl` | optional | | #### `ClientAccess` Junction between a `User` (with `CLIENT` role) and a `Client` record. Allows a portal user account to be linked to a specific client company. #### `Project` Top-level container. Linked to an optional `Client`, `producer` (User), and `supervisor` (User). | Key field | Type | Notes | |-----------|------|-------| | `code` | String | unique, used as short label | | `status` | ProjectStatus | default `ACTIVE` | | `clientId` | String? | nullable | | `slackWebhook` | String? | per-project Slack alerts | #### `Shot` Belongs to a `Project`. Tracks pipeline status and priority. | Key field | Type | Notes | |-----------|------|-------| | `shotCode` | String | unique per project | | `sequence` | String? | grouping label | | `status` | ShotStatus | default `WAITING` | | `priority` | ShotPriority | default `NORMAL` | | `fps` | Float | default 24 | | `frameStart`, `frameEnd` | Int? | optional frame range | > **Client visibility rule**: only shots with status `CLIENT_REVIEW`, `REVISIONS`, `APPROVED`, or `FINAL` are returned by the client portal API. #### `Version` A specific upload for a shot. Multiple versions per shot; only one has `isLatest = true`. | Key field | Type | Notes | |-----------|------|-------| | `versionNumber` | Int | unique per shot | | `fileUrl` | String | path/URL to video | | `fileSize` | BigInt? | serialized to string in API responses | | `fps` | Float | | | `approvalStatus` | ApprovalStatus | default `PENDING_REVIEW` | | `isLatest` | Boolean | | #### `Comment` Frame-anchored comment on a version. Supports replies via `CommentReply`. | Key field | Type | Notes | |-----------|------|-------| | `frameNumber` | Int | exact frame | | `timestamp` | Float | seconds | | `text` | Text | | | `isResolved` | Boolean | | #### `CommentReply` Flat child of `Comment`. No threading beyond one level. #### `Annotation` Canvas drawing saved against a version + frame. `drawingData` is JSON containing an array of `AnnotationShape` objects with normalized (0–1) coordinates. | Key field | Type | Notes | |-----------|------|-------| | `frameNumber` | Int | | | `drawingData` | Json | `{ shapes, canvasWidth, canvasHeight, version }` | | `color` | String | hex, default `#ef4444` | | `isVisible` | Boolean | soft-hide | | `commentId` | String? | optional companion comment link | #### `Approval` Immutable approval record created each time a user submits a decision. The version's `approvalStatus` is updated separately. | Key field | Type | Notes | |-----------|------|-------| | `status` | ApprovalStatus | | | `notes` | Text? | | #### `Notification` In-app notification for a user. | Key field | Type | Notes | |-----------|------|-------| | `type` | NotificationType | | | `data` | Json? | extra context (versionId, shotCode, etc.) | | `isRead` | Boolean | | #### `ReviewSession` A tokenized, time-limited review link for external clients. Tied to a `Project`. | Key field | Type | Notes | |-----------|------|-------| | `token` | String | unique cuid, used in client portal URLs | | `label` | String? | friendly name shown to client | | `email` | String? | recipient email | | `expiresAt` | DateTime | | | `isActive` | Boolean | can be deactivated manually | | `accessCount` | Int | incremented on each portal visit | --- ## Key Component Map ``` components/ player/ ReviewPlayer.tsx — Forwardref player, keyboard shortcuts, fullscreen FrameTimeline.tsx — Canvas ruler with comment/annotation markers PlaybackControls.tsx — Transport bar, speed selector, annotation toggle annotations/ AnnotationCanvas.tsx — Canvas overlay; per-frame shape map persists navigation AnnotationTools.tsx — Tool/colour/stroke picker (shown when annotating) comments/ CommentPanel.tsx — Comment list, filter, reply, resolve; seekToFrame on click versions/ VersionUpload.tsx — Drag-and-drop uploader VersionList.tsx — Version history with approval badges shots/ ShotCard.tsx — Shot grid card with status dropdown NewShotDialog.tsx — Create shot form projects/ ProjectCard.tsx — Project list card NewProjectDialog.tsx — Create project form clients/ NewClientDialog.tsx — Create client form ShareReviewDialog.tsx — Generate tokenized review link ReviewSessionList.tsx — Active sessions with copy/deactivate actions dashboard/ StatsCards.tsx — Top-level counts ShotQueue.tsx — Shots needing attention RecentActivity.tsx — Latest version/comment events layout/ Sidebar.tsx — Nav + role-based link visibility Header.tsx — Breadcrumb + notification bell NotificationBell.tsx — Dropdown of unread notifications ``` --- ## Auth & Roles Authentication uses **NextAuth v5** with a Credentials provider (email + bcrypt password). Sessions are JWT-based. | Role | Access | |------|--------| | `ADMIN` | Full access including user management, client management | | `PRODUCER` | Full project/shot/version access, client management | | `SUPERVISOR` | Full project/shot/version access, no client management | | `ARTIST` | Own shots and versions; can comment | | `CLIENT` | Dashboard login only (legacy); primary access via portal token links | The **client portal** routes (`/client/[token]/*` and `/api/client/[token]/*`) bypass auth entirely — access is controlled by token validity, `isActive`, and `expiresAt`. --- ## Storage Controlled by `STORAGE_PROVIDER` env var. Currently defaults to `local` (files saved in `/public/uploads/`, served via `/api/files/[...key]`). Switching to S3/R2/B2/MinIO/UploadThing requires only env var changes. --- ## Environment Variables (minimum for local dev) ```env DATABASE_URL="postgresql://user:pass@localhost:5432/feedback" AUTH_SECRET="<32-char random string>" NEXTAUTH_URL="http://localhost:3000" NEXT_PUBLIC_APP_URL="http://localhost:3000" STORAGE_PROVIDER="local" ```