Add full application: receipt scanning, auth, billing, and account deletion
Brings the working codebase (Next.js app, auth system, Stripe billing, Docker/deploy config, tests, docs) into version control on top of the placeholder initial commit, and adds account self-deletion (Danger Zone in Settings, password + typed-email confirmation, cascading DB cleanup, Stripe cancellation) per GDPR right-to-erasure. Excludes local build caches, node_modules, and internal agent scratch files; .gitignore hardened to keep those out going forward. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
142
PROJECT.md
Normal file
142
PROJECT.md
Normal file
@@ -0,0 +1,142 @@
|
||||
# Project: Receipt Scanner Web Application UI/UX Upgrade
|
||||
|
||||
## Architecture
|
||||
- **Framework & Runtime**: Next.js 15 App Router, React 19, TypeScript 5.7 (strict), Tailwind CSS 3.4
|
||||
- **Design System**: Zenith Silver (`#F6F9FF` background, `#FFFFFF` surface cards, `#E2E8F0` borders, `#000000` accents, crisp 0px sharp corners, Hanken Grotesk / Inter / JetBrains Mono typography)
|
||||
- **Data & Storage**: Local-first IndexedDB (`idb`) with PostgreSQL / Drizzle ORM schema compatibility
|
||||
- **Export Formats**: Dual-sheet `.xlsx` (ExcelJS), DATEV-compliant `.csv` (UTF-8 BOM), structured `.json`
|
||||
|
||||
## Feature Inventory
|
||||
| # | Feature | Description | Milestone | Source | Status |
|
||||
|---|---------|-------------|-----------|--------|:------:|
|
||||
| 1 | Full-page Drag & Drop Overlay | High-visibility global drag-and-drop backdrop overlay across dashboard | M1 | ORIGINAL_REQUEST R1 | DONE |
|
||||
| 2 | Dedicated Dropzone Component | Visual upload card supporting PDF, PNG, JPEG, WebP with laser scanline | M1 | ORIGINAL_REQUEST R1 | DONE |
|
||||
| 3 | Batch Upload Drawer & Queue | Multi-file batch queue with instant thumbnail previews, live progress bars | M1 | ORIGINAL_REQUEST R1 | DONE |
|
||||
| 4 | Error Boundaries & File Retry | Isolated per-file failure handling, corrupt file rejection without halting queue, quick retry/remove | M1 | ORIGINAL_REQUEST R1 | DONE |
|
||||
| 5 | Side-by-Side Dual Pane Modal | 50/50 split review modal with document on LEFT and editable fields on RIGHT | M2 | ORIGINAL_REQUEST R2 | DONE |
|
||||
| 6 | Interactive Document Viewer | CSS transform Zoom (0.25x–5x), Pan (grab/trackpad), Rotate, Fit-to-Page | M2 | ORIGINAL_REQUEST R2 | DONE |
|
||||
| 7 | Bounding-Box Visual Cues | 2-way highlight synchronization between image bounding boxes and extracted form fields | M2 | ORIGINAL_REQUEST R2 | DONE |
|
||||
| 8 | Dynamic Line Items Editor | Inline-editable table for line items (description, quantity, price, total) with add/delete row controls | M2 | ORIGINAL_REQUEST R2 | DONE |
|
||||
| 9 | Field Audit & Immediate Save | "AI Extracted" vs "Manually Edited" indicators, 1-click revert, debounced IndexedDB auto-save, Prev/Next navigation | M2 | ORIGINAL_REQUEST R2 | DONE |
|
||||
| 10 | Payment Method & Schema Extension | Added payment method selection and extended receipt schema | M2 | ORIGINAL_REQUEST R2 | DONE |
|
||||
| 11 | Editable Table Affordances | Clear dotted underlines, hover edit cues, human-edited flags in LiveTable | M3 | ORIGINAL_REQUEST R3 | DONE |
|
||||
| 12 | 3-Tier Status Badges | Standardized badges: Scanned (Emerald), Pending Review (Amber), Confirmed (Slate/Blue) | M3 | ORIGINAL_REQUEST R3 | DONE |
|
||||
| 13 | Floating Batch Actions Toolbar | Multi-select toolbar for Bulk Export (Selected Only to XLSX/CSV/JSON), Bulk Categorize, Bulk Status Update, Bulk Delete | M3 | ORIGINAL_REQUEST R3 | DONE |
|
||||
| 14 | Quick Search & Filter Chips Bar | Clickable chip filters for temporal ranges (Today/Week/Month/Year), status counters, categories, amount brackets | M3 | ORIGINAL_REQUEST R3 | DONE |
|
||||
| 15 | Responsive Dashboard Navigation | Mobile-friendly sidebar drawer / bottom nav (`hidden md:flex`) eliminating horizontal clipping on <768px | M4 | ORIGINAL_REQUEST R4 | DONE |
|
||||
| 16 | Interactive KPI Statistics Cards | Total Scanned, Monthly Spend, Pending Reviews, Average Accuracy cards with click-to-filter micro-interactions | M4 | ORIGINAL_REQUEST R4 | DONE |
|
||||
| 17 | Accessible Information Hierarchy | WCAG AA contrast, crisp typography, clean header layout, zero horizontal overflow | M4 | ORIGINAL_REQUEST R4 | DONE |
|
||||
| 18 | E2E & Unit Test Coverage | Comprehensive tests verifying R1–R4 features, build integrity, and typechecks | M5 | ORIGINAL_REQUEST AC | DONE |
|
||||
|
||||
## Milestones
|
||||
| # | Name | Scope | Dependencies | Status |
|
||||
|---|------|-------|-------------|--------|
|
||||
| M1 | Ingestion & Batch Upload (R1) | Global dropzone, batch queue drawer, thumbnails, progress, retry/remove | none | DONE |
|
||||
| M2 | Side-by-Side Inspector & Split Review (R2) | Dual-pane modal, DocumentViewer (zoom/pan), BBox sync, LineItemsEditor, audit badges | none | DONE |
|
||||
| M3 | Interactive Table & Batch Operations (R3) | Cell edit affordances, status badges, BatchActionBar, FilterChipsBar, selection hooks | none | DONE |
|
||||
| M4 | Accessible Hierarchy & Responsive Shell (R4) | Responsive Sidebar/TopNav, interactive KPI cards, mobile drawer, zero-overflow | M1, M2, M3 | DONE |
|
||||
| M5 | E2E Test Suite & Final Verification | Test infra, Tier 1-5 tests, adversarial tests, `npm run build`, `npx tsc --noEmit` | M1, M2, M3, M4 | DONE |
|
||||
|
||||
## Interface Contracts
|
||||
|
||||
### M1 Ingestion ↔ Dashboard
|
||||
- `BatchUploadDrawer`: Accepts `onComplete: (receipts: ProcessedReceipt[]) => void`, `isOpen: boolean`, `onClose: () => void`.
|
||||
- `BatchUploadDropzone`: Accepts `onFilesSelected: (files: File[]) => void`.
|
||||
- `GlobalDropzoneOverlay`: Listens to `window` drag events; triggers batch queue on file drop.
|
||||
|
||||
### M2 Inspector ↔ Table & Storage
|
||||
- `ReceiptInspectorModal`: Accepts `receipt: ProcessedReceipt`, `isOpen: boolean`, `onClose: () => void`, `onSave: (updated: ProcessedReceipt) => void`, `onNavigate?: (direction: 'prev' | 'next') => void`.
|
||||
- `ProcessedReceipt` schema additions:
|
||||
- `paymentMethod?: string`
|
||||
- `boundingBoxes?: Record<string, { x: number; y: number; width: number; height: number }>`
|
||||
- `editedFields?: Record<string, boolean>`
|
||||
- `originalExtraction?: Partial<ProcessedReceipt>`
|
||||
|
||||
### M3 LiveTable ↔ BatchActionBar & FilterChipsBar
|
||||
- `useReceiptFilters`: returns `{ filteredReceipts, activePeriod, setPeriod, activeStatus, setStatus, activeCategory, setCategory, searchQuery, setSearchQuery, amountRange, setAmountRange, resetFilters }`.
|
||||
- `useTableSelection`: returns `{ selectedIds, isSelected, toggleSelect, toggleSelectAll, clearSelection, selectAll, count }`.
|
||||
- `BatchActionBar`: Accepts `selectedIds: string[]`, `receipts: ProcessedReceipt[]`, `onBulkDelete`, `onBulkCategorize`, `onBulkStatusUpdate`, `onBulkExport`, `onClearSelection`.
|
||||
|
||||
### M4 Responsive Shell ↔ Dashboard Layout
|
||||
- `Sidebar`: Desktop fixed sidebar (`hidden md:flex`) and mobile drawer (`block md:hidden`) triggered via `TopNav` hamburger button with smooth backdrop transition and zero horizontal overflow.
|
||||
- `TopNav`: Header with breadcrumb/view indicator, quick search trigger, user/workspace menu, and mobile drawer toggle button.
|
||||
- KPI Statistics Cards in `page.tsx`: Interactive cards for `Total Scanned`, `Monthly Spend`, `Pending Reviews`, and `Average Accuracy` (calculated dynamically from AI confidence/status with micro-interaction hover states and click-to-filter triggers).
|
||||
|
||||
## Code Layout
|
||||
```
|
||||
src/
|
||||
├── app/
|
||||
│ ├── dashboard/
|
||||
│ │ ├── layout.tsx # Responsive shell (TopNav + mobile drawer + desktop Sidebar)
|
||||
│ │ ├── page.tsx # Overview with KPI cards + Ingestion dropzone + LiveTable
|
||||
│ │ ├── activity/page.tsx # Activity archive with FilterChipsBar + LiveTable + BatchActionBar
|
||||
│ │ ├── export/page.tsx # Export hub with summary statistics & date filters
|
||||
│ │ └── settings/page.tsx # Settings & preferences
|
||||
├── components/
|
||||
│ ├── dashboard/
|
||||
│ │ ├── BatchUploadDropzone.tsx # [M1] Dedicated ingestion dropzone card (DONE)
|
||||
│ │ ├── BatchUploadDrawer.tsx # [M1] Multi-file upload queue & progress modal/drawer (DONE)
|
||||
│ │ ├── GlobalDropzoneOverlay.tsx # [M1] Full-screen dragover overlay (DONE)
|
||||
│ │ ├── ReceiptInspectorModal.tsx # [M2] 50/50 Dual-pane review modal (DONE)
|
||||
│ │ ├── DocumentViewer.tsx # [M2] Zoom, pan, rotate & bounding-box canvas (DONE)
|
||||
│ │ ├── LineItemsEditor.tsx # [M2] Line item table & editor (DONE)
|
||||
│ │ ├── StatusBadge.tsx # [M3] 3-tier status badges (DONE)
|
||||
│ │ ├── BatchActionBar.tsx # [M3] Multi-select batch operations floating bar (DONE)
|
||||
│ │ ├── FilterChipsBar.tsx # [M3] Responsive filter chips bar (DONE)
|
||||
│ │ ├── LiveTable.tsx # [M3] Spreadsheet table with cell edit indicators (DONE)
|
||||
│ │ ├── Sidebar.tsx # [M4] Responsive sidebar & mobile drawer (DONE)
|
||||
│ │ ├── TopNav.tsx # [M4] Header with search, mobile trigger, status (DONE)
|
||||
│ │ └── KPICards.tsx # [M4] Interactive KPI cards with micro-interactions (DONE)
|
||||
└── lib/
|
||||
├── hooks/
|
||||
│ ├── useReceiptFilters.ts # [M3] Filtering hook (DONE)
|
||||
│ └── useTableSelection.ts # [M3] Selection hook (DONE)
|
||||
├── schema/
|
||||
│ └── receipt.ts # [M2] Extended receipt schema (DONE)
|
||||
└── utils/
|
||||
└── boundingBoxes.ts # [M2] Bounding box calculation & heuristics (DONE)
|
||||
```
|
||||
|
||||
## Appendix: Database Least Privilege
|
||||
|
||||
**Why.** The app must connect to PostgreSQL with only the rights it actually
|
||||
needs — not superuser. The docker-compose defaults make `receipt_user` the
|
||||
database superuser (`POSTGRES_USER`). If the application is compromised, an
|
||||
attacker holding the app's credentials would otherwise get full control of the
|
||||
database: read every user's receipts, drop or alter tables, or grant themselves
|
||||
rights. A restricted runtime role limits the blast radius to reading and
|
||||
modifying rows.
|
||||
|
||||
**Two roles.**
|
||||
- `receipt_user` — **owner / migration role**. Keeps full rights (DDL) and is
|
||||
used for migrations and schema init (`src/lib/db/init.ts`). Never the runtime
|
||||
connection in production.
|
||||
- `receipt_app` — **runtime role** (created by `scripts/db-permissions.sql`).
|
||||
LOGIN, no superuser, no CREATEDB/CREATEROLE. Granted exactly: CONNECT on the
|
||||
database, USAGE on schema `public` (no CREATE), SELECT/INSERT/UPDATE/DELETE on
|
||||
all tables, USAGE/SELECT on all sequences, and matching `ALTER DEFAULT
|
||||
PRIVILEGES` so future tables/sequences created by the owner during migrations
|
||||
are covered automatically. `CREATE` on `public` is additionally revoked from
|
||||
the PUBLIC pseudo-role.
|
||||
|
||||
**How to apply.**
|
||||
- Fresh volume (`docker compose up` with no existing data): the postgres service
|
||||
mounts `scripts/db-permissions.sql` into
|
||||
`/docker-entrypoint-initdb.d/10-db-permissions.sql`; the image runs it once as
|
||||
`POSTGRES_USER` (superuser) before the app starts.
|
||||
- Existing database (e.g. the local dev DB): `node scripts/apply-db-permissions.mjs`
|
||||
— connects with the owner URL from `.env.local`, executes the same SQL, safe
|
||||
to re-run. Set `APP_DATABASE_PASSWORD` to override the documented default
|
||||
password (also rotates it on an existing role).
|
||||
|
||||
**How to verify.** `node scripts/verify-db-permissions.mjs` connects both as the
|
||||
owner and as `receipt_app` and asserts: no superuser/CREATEDB/CREATEROLE, CONNECT
|
||||
+ schema USAGE, DML on all tables/sequences, no schema CREATE, no DDL (a real
|
||||
`CREATE TABLE` attempt is denied), default privileges are in place, and the owner
|
||||
can still run DDL. Prints PASS/FAIL and exits 1 on failure.
|
||||
|
||||
**Limitation.** The runtime role has no DDL, so migrations and schema init must
|
||||
use the owner URL. In production: run migrations with `DATABASE_URL` set to the
|
||||
owner URL, then run the app with `DATABASE_URL` (or `APP_DATABASE_URL`, plumbed
|
||||
through docker-compose) set to the restricted `receipt_app` URL.
|
||||
|
||||
Reference in New Issue
Block a user