# ClientHub — Walking Skeleton (Phase 1) **Project:** ClientHub — Freelancer Client Portal **Phase:** 01 — Foundation & Client Dashboard **Date:** 2026-05-13 **Status:** Blueprint (decisions below are LOCKED for all subsequent phases) --- ## Project Architecture — Locked Decisions This Walking Skeleton establishes the architectural foundation for all future phases. These decisions are **immutable** without explicit user approval. ### Core Stack | Layer | Technology | Why | Locked? | |-------|-----------|-----|---------| | **Framework** | Next.js 15 (App Router, TypeScript, src/) | Server Components + Edge Middleware for performance; Vercel-native | ✅ YES | | **Database** | Postgres on Coolify (Hetzner), via `postgres-js` driver | Self-hosted (no Neon/Supabase cost); persistent via external DB | ✅ YES | | **ORM** | Drizzle ORM with postgres-js | Zero-cost serverless driver; schema-as-code migrations | ✅ YES | | **UI** | Tailwind CSS v4 + shadcn/ui components | Utility-first, copied components, mobile-first | ✅ YES | | **Auth (Admin)** | Auth.js v4 Credentials provider (Phase 2) | Single admin account, JWT cookie | ✅ YES | | **Auth (Client)** | Custom Next.js Middleware + token validation | No session store needed; token in URL | ✅ YES | | **Token Generation** | nanoid (21 chars) | Cryptographically secure, URL-safe, non-enumerable | ✅ YES | | **Deployment** | Vercel (Hobby plan) + custom subdomain | Native Next.js; auto-SSL; single deploy command | ✅ YES | ### Data Model — Locked Entities All tables below **must** exist and maintain these field definitions. Modifications require explicit approval. ``` clients id UUID PK (stable, never changes) name TEXT brand_name TEXT brief TEXT token UUID UNIQUE ← SEPARATE from PK, rotatable accepted_total NUMERIC ← denormalized, only price client sees created_at TIMESTAMPTZ phases id UUID PK client_id UUID FK → clients.id title TEXT sort_order INT status TEXT (upcoming | active | done) tasks id UUID PK phase_id UUID FK → phases.id title TEXT description TEXT status TEXT (todo | in_progress | done) sort_order INT deliverables id UUID PK task_id UUID FK → tasks.id title TEXT url TEXT 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 FK → 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 FK → clients.id label TEXT url TEXT ← external links only, no file uploads created_at TIMESTAMPTZ notes id UUID PK client_id UUID FK → clients.id body 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 FK → clients.id service_id UUID FK → service_catalog.id quantity NUMERIC unit_price NUMERIC subtotal NUMERIC ← NEVER exposed via client API ``` ### Critical Design Principles — Locked 1. **`clients.token` is NOT the primary key.** Data is keyed by stable UUID `id`. Token is a separate, rotatable field. Rotation is a single UPDATE statement. 2. **Client API never exposes `quote_items`.** Server-side filtering enforces this; not a UI trick. The `accepted_total` field is the only price the client API returns. 3. **`deliverables.approved_at` is immutable.** Once set, it cannot be unset. Provides an audit trail for disputes. 4. **Two independent auth systems:** - `/c/[token]/*` → Middleware validates token, 404 on miss - `/admin/*` → Auth.js session check (Phase 2) - No overlap; no shared session store 5. **No file hosting in v1.** Documents are external URLs only (Google Drive, PDFs, Figma links). File uploads → Phase 3+. 6. **No email in v1.** Deliverables are dashboard links, not email attachments. Email integration → Phase 2+. ### Directory Structure — Locked ``` IAMCAVALLI/ ├── src/ │ ├── app/ │ │ ├── c/[token]/ │ │ │ ├── page.tsx ← Client dashboard route │ │ │ └── layout.tsx │ │ ├── admin/ ← Phase 2 (protected by middleware) │ │ │ ├── page.tsx ← Admin dashboard │ │ │ ├── clients/ │ │ │ │ ├── page.tsx │ │ │ │ └── [id]/ │ │ │ ├── catalog/ │ │ │ └── ... │ │ ├── layout.tsx │ │ └── globals.css │ ├── components/ │ │ ├── ui/ ← shadcn/ui components │ │ ├── client-dashboard.tsx │ │ ├── phase-timeline.tsx │ │ ├── payment-status.tsx │ │ ├── documents-section.tsx │ │ ├── notes-section.tsx │ │ └── ... │ ├── db/ │ │ ├── schema.ts ← Drizzle schema (source of truth) │ │ ├── migrations/ ← Generated by drizzle-kit │ │ └── index.ts ← db client export │ ├── lib/ │ │ ├── client-view.ts ← ClientView type + queries │ │ ├── auth.ts ← Phase 2: Auth helpers │ │ └── ... │ └── middleware.ts ← Token validation at edge ├── scripts/ │ ├── seed.ts ← Insert first test client │ └── ... ├── .env.local ← DATABASE_URL, secrets ├── drizzle.config.ts ├── next.config.ts ├── tailwind.config.ts ├── tsconfig.json ├── package.json └── .planning/ ├── ROADMAP.md ├── REQUIREMENTS.md ├── STATE.md └── phases/ └── 01-foundation-client-dashboard/ ├── 01-CONTEXT.md ├── 01-DISCUSSION-LOG.md ├── 01-01-PLAN.md ├── 01-02-PLAN.md ├── 01-03-PLAN.md ├── 01-04-PLAN.md ├── 01-05-PLAN.md └── SKELETON.md ``` ### Deployment — Locked - **Host:** Vercel (Hobby plan, $0/month for Phase 1 scale) - **Domain:** welcomeclient.iamcavalli.net (CNAME to Vercel DNS) - **Database:** Postgres on Coolify (existing Hetzner server, Simone manages) - **Environment:** DATABASE_URL injected via Vercel Secrets - **SSL/TLS:** Vercel auto-provisioning for custom domain ### API Routes Structure (Phase 2+) Routes created in Phase 2 will follow this pattern: **Client-facing routes** (`/api/c/[token]/...`): - No authentication library needed - Middleware validates token - Routes return ClientView shape only **Admin routes** (`/api/admin/...`): - Require Auth.js session - Access full AdminView including quote_items - CRUD operations on all entities ### UI Layer Principles — Locked - **Light & clean visual style:** White backgrounds, strong typography, subtle gray accents - **Mobile-first design:** Tailwind defaults ensure responsive behavior - **Semantic HTML:** Proper heading hierarchy, accessible form controls - **No client-side state management libraries:** Server Components + Server Actions for Phase 1-2 - **Progress visualization:** Global bar (top) + per-phase bars (sections) + task status badges - **Brand consistency:** iamcavalli logo in corner, client brand_name prominent ### Security Assumptions — Locked 1. **Database credentials are secrets:** DATABASE_URL never logged, committed, or exposed 2. **Tokens are non-enumerable:** 21-character nanoid cannot be guessed 3. **Client API is isolated:** Admin data never leaks to `/c/[token]/*` routes 4. **Admin password** (Phase 2): env var `ADMIN_PASSWORD` protects `/admin/*` before Auth.js is added 5. **No PII in logs:** Payment amounts and tokens never logged to Vercel logs --- ## What This Skeleton Delivers After Phase 1 execution: ✅ **Functional client portal:** - One client can open their secret link on any device - Dashboard shows project phases, tasks, status, payments, documents, decision log - No login required; link is the secret ✅ **Production-ready infrastructure:** - Database is live on Coolify Postgres - Custom domain is verified and HTTPS-enabled - Application is deployed on Vercel - One-command deploy pipeline (`git push → Vercel auto-build`) ✅ **Developer-friendly codebase:** - TypeScript with strict mode - Drizzle ORM manages schema as code - Git-tracked migrations (reproducible database state) - One seed script to populate test data - No manual SQL; no database browser required ✅ **Foundation for Phase 2:** - Data model is stable and comprehensive - Admin CRUD can be built without schema changes - Auth.js integration point is clear - Comments and approvals schema already exists --- ## Phase 1 → Phase 2 Contract Phase 2 will extend this skeleton by: 1. **Admin authentication:** Middleware check + Auth.js session on `/admin/*` routes 2. **CRUD operations:** Forms and API routes to edit clients, phases, tasks, deliverables, payments 3. **Comments & approvals:** Client-facing UI for commenting and approving deliverables 4. **Admin workspace:** Dashboard to manage all clients with state summary and quick actions 5. **Payment management:** Update payment status, send payment reminders **No schema changes required.** All Phase 2 features fit into the existing data model. --- ## Validation Checklist (End of Phase 1) - [ ] Next.js 15 application compiles without TypeScript errors - [ ] Database schema is live on Coolify Postgres (all 11 tables) - [ ] Middleware validates tokens at edge - [ ] Client portal route renders complete dashboard with seeded data - [ ] Seed script inserts test client and prints shareable link - [ ] DNS CNAME is live: welcomeclient.iamcavalli.net → Vercel - [ ] Application is deployed on Vercel (accessible via https://welcomeclient.iamcavalli.net/) - [ ] Invalid tokens return 404 (no information leakage) - [ ] Payment amounts are NOT visible on client dashboard (only status) - [ ] Mobile layout is responsive and readable - [ ] All DASH-01 through DASH-10 requirements are satisfied (except DASH-05, DASH-06 which are Phase 2) --- ## Future Extensibility Notes This skeleton is designed for: - **Phase 2:** Admin CRUD + comments + approvals (no schema changes) - **Phase 3:** Service catalog + quote builder (admin-only, client sees only total) - **Phase 4 (v2):** Claude AI onboarding flow (optional; may defer indefinitely) - **Beyond:** Multi-team support, real file uploads, email automation (major schema rework) The current design is intentionally simple. Future phases should resist scope creep and maintain the "client sees only what they need" principle. --- **Skeleton locked:** 2026-05-13 **Next checkpoint:** Phase 2 planning (`/gsd-plan-phase 2`)