infra(04-00): route /c/ → /client/, Dockerfile, Gitea deploy
- Rename src/app/c/[token] → src/app/client/[token] - Update proxy.ts, ClientRow, admin client detail with /client/ path - Add output: "standalone" to next.config.ts for Docker build - Add Dockerfile (multi-stage, node:20-alpine) and .dockerignore - Push schema to Coolify Postgres via SSH tunnel (drizzle-kit push ✓) - Update CLAUDE.md constraint 4 to reflect /client/ route - Add Phase 4 planning artifacts (04-00, 04-RESEARCH, 04-PATTERNS) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,191 @@
|
||||
# Architecture — ClientHub Freelancer Client Portal
|
||||
|
||||
**Project:** ClientHub (welcomeclient.iamcavalli.net)
|
||||
**Researched:** 2026-05-09
|
||||
**Confidence:** HIGH
|
||||
|
||||
---
|
||||
|
||||
## Component Boundaries
|
||||
|
||||
Single Next.js application on Vercel. No separate backend. All server logic lives in Route Handlers (`/api/**`). One Postgres database (Neon serverless) accessed via Drizzle ORM. Admin auth via env-var secret + cookie. Client access via UUID token in URL — no auth library needed for clients.
|
||||
|
||||
| Component | Responsibility | Communicates With |
|
||||
|-----------|---------------|-------------------|
|
||||
| Client Portal `/c/[token]` | Read-only view: status, phases, tasks, deliverables, payments, documents | API Routes (GET only) |
|
||||
| Admin Dashboard `/admin` | List all clients with status summary | API Routes (full CRUD) |
|
||||
| Admin Client Workspace `/admin/clients/[id]` | Edit phases, tasks, deliverables, payments, documents | API Routes (full CRUD) |
|
||||
| Service Catalog Manager `/admin/catalog` | CRUD on service items + unit prices | API Routes (catalog entity) |
|
||||
| Quote Builder `/admin/clients/[id]/quote` | Compose quote from catalog items, write `accepted_total` to client row | Catalog + API Routes |
|
||||
| Comments System | Client posts on task/deliverable; admin replies | API Route POST |
|
||||
| Approval Flow | Client PATCHes a deliverable to `approved` | API Route, validates token ownership |
|
||||
| API Routes `/api/**` | Validate token or admin session; query/mutate DB; return JSON | Postgres only |
|
||||
| Database | Single source of truth | API Routes only — never queried from browser |
|
||||
|
||||
---
|
||||
|
||||
## Data Flow
|
||||
|
||||
**Client reading their dashboard:**
|
||||
```
|
||||
Browser → GET /c/[token]
|
||||
→ Next.js server component
|
||||
→ DB: clients WHERE token = [token] → 404 if missing
|
||||
→ JOIN: project + phases + tasks + deliverables + payments + documents
|
||||
→ Omit: quote_items, service prices
|
||||
→ Render read-only portal
|
||||
```
|
||||
|
||||
**Client posting a comment:**
|
||||
```
|
||||
Browser → POST /api/comments { token, entity_type, entity_id, body }
|
||||
→ Validate token → write comment { author: 'client' }
|
||||
→ 201 → re-fetch thread
|
||||
```
|
||||
|
||||
**Client approving a deliverable:**
|
||||
```
|
||||
Browser → PATCH /api/deliverables/[id]/approve { token }
|
||||
→ Validate token owns deliverable → set status='approved', approved_at=now()
|
||||
→ Return updated deliverable
|
||||
```
|
||||
|
||||
**Admin editing:**
|
||||
```
|
||||
Browser (admin) → PATCH /api/admin/tasks/[id] + admin cookie
|
||||
→ Validate session → update row → return updated task
|
||||
```
|
||||
|
||||
**Quote building:**
|
||||
```
|
||||
Admin UI selects services → computes line items
|
||||
→ POST /api/admin/clients/[id]/quote { line_items[], accepted_total }
|
||||
→ Write quote_items rows + write clients.accepted_total (denormalized)
|
||||
→ Client portal reads clients.accepted_total — never touches quote_items
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Data Model
|
||||
|
||||
```
|
||||
clients
|
||||
id UUID PK
|
||||
name TEXT
|
||||
brand_name TEXT
|
||||
brief TEXT
|
||||
token UUID UNIQUE ← the secret link key (separate from PK!)
|
||||
accepted_total NUMERIC ← denormalized; only price client ever sees
|
||||
created_at TIMESTAMPTZ
|
||||
|
||||
phases
|
||||
id UUID PK
|
||||
client_id UUID → clients.id
|
||||
title TEXT
|
||||
sort_order INT
|
||||
status TEXT (upcoming | active | done)
|
||||
|
||||
tasks
|
||||
id UUID PK
|
||||
phase_id UUID → phases.id
|
||||
title TEXT
|
||||
description TEXT
|
||||
status TEXT (todo | in_progress | done)
|
||||
sort_order INT
|
||||
|
||||
deliverables
|
||||
id UUID PK
|
||||
task_id UUID → tasks.id
|
||||
title TEXT
|
||||
url TEXT ← external link (Google Drive, PDF, etc.)
|
||||
status TEXT (pending | submitted | approved)
|
||||
approved_at TIMESTAMPTZ ← immutable audit trail
|
||||
|
||||
comments
|
||||
id UUID PK
|
||||
entity_type TEXT (task | deliverable)
|
||||
entity_id UUID
|
||||
author TEXT (client | admin)
|
||||
body TEXT
|
||||
created_at TIMESTAMPTZ
|
||||
|
||||
payments
|
||||
id UUID PK
|
||||
client_id UUID → clients.id
|
||||
label TEXT ("Acconto 50%" / "Saldo 50%")
|
||||
amount NUMERIC
|
||||
status TEXT (da_saldare | inviata | saldato)
|
||||
paid_at TIMESTAMPTZ
|
||||
|
||||
documents
|
||||
id UUID PK
|
||||
client_id UUID → clients.id
|
||||
label TEXT
|
||||
url TEXT
|
||||
created_at TIMESTAMPTZ
|
||||
|
||||
service_catalog
|
||||
id UUID PK
|
||||
name TEXT
|
||||
description TEXT
|
||||
unit_price NUMERIC
|
||||
active BOOLEAN
|
||||
|
||||
quote_items
|
||||
id UUID PK
|
||||
client_id UUID → clients.id
|
||||
service_id UUID → service_catalog.id
|
||||
quantity NUMERIC
|
||||
unit_price NUMERIC ← snapshot at time of quote
|
||||
subtotal NUMERIC
|
||||
-- NEVER exposed via client API
|
||||
```
|
||||
|
||||
**Key design decisions:**
|
||||
- `clients.token` is the only secret. Rotation = single UPDATE. No session store needed.
|
||||
- `clients.accepted_total` is deliberately denormalized so client API never touches `quote_items`.
|
||||
- Approval `approved_at` stored as immutable audit trail — disputes resolved by timestamp.
|
||||
- `comments` use `entity_type + entity_id` polymorphic pair — correct at this scale.
|
||||
- `payments` always two rows per client (created when quote is finalized).
|
||||
|
||||
---
|
||||
|
||||
## Suggested Build Order
|
||||
|
||||
```
|
||||
1. DB schema + migrations
|
||||
└─ everything depends on this
|
||||
|
||||
2. API: token lookup + project read (GET only)
|
||||
└─ unblocks client portal
|
||||
|
||||
3. Client portal UI /c/[token]
|
||||
└─ the core deliverable; clients need this first
|
||||
|
||||
4. Admin auth middleware (env-var secret, cookie check)
|
||||
└─ gate before admin routes go live
|
||||
|
||||
5. Admin: client list + client workspace CRUD
|
||||
└─ phases, tasks, status, documents, payments
|
||||
|
||||
6. Comments system + deliverable approval
|
||||
└─ depends on both client portal and admin workspace
|
||||
|
||||
7. Service catalog CRUD ← can run parallel with step 5
|
||||
└─ independent of client-facing features
|
||||
|
||||
8. Quote builder
|
||||
└─ depends on catalog + client entity
|
||||
|
||||
9. Claude onboarding flow (v2)
|
||||
└─ depends on all CRUD APIs being complete
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Roadmap Implications
|
||||
|
||||
- Phase 1: DB schema + token API + client portal (all three coupled)
|
||||
- Phase 2: Admin auth + CRUD management + comments + approvals
|
||||
- Phase 3: Service catalog + quote builder
|
||||
- Phase 4 (v2): Claude onboarding flow
|
||||
Reference in New Issue
Block a user