5bf5dfce71
- 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>
178 lines
7.4 KiB
Markdown
178 lines
7.4 KiB
Markdown
# Pitfalls — ClientHub Freelancer Client Portal
|
|
|
|
**Domain:** Freelancer client portal with secret-link access, solo developer, Vercel deploy
|
|
**Researched:** 2026-05-09
|
|
**Confidence:** HIGH
|
|
|
|
---
|
|
|
|
## Critical Pitfalls
|
|
|
|
### 1. Secret Links That Are Guessable or Enumerable
|
|
|
|
**What goes wrong:** If client tokens are generated from names, sequential IDs, or short strings, they can be enumerated. `/client/mario-rossi` or `/client/3` are not secrets.
|
|
|
|
**Prevention:**
|
|
- Generate tokens as cryptographically random UUIDs (v4) or nanoid (21 chars / ~126 bits of entropy)
|
|
- Never derive from name, company, or sequential ID
|
|
- Never log or display full tokens in admin analytics
|
|
|
|
**Warning signs:** Client slug contains the client's name. Token under 20 characters. Link can be reconstructed from info the client already knows.
|
|
|
|
**Phase mapping:** Phase 1 — data model + routing. Cannot be retrofitted after links are distributed.
|
|
|
|
---
|
|
|
|
### 2. Client API Exposes Admin Data (Hidden in UI Only)
|
|
|
|
**What goes wrong:** Developers fetch all client data and conditionally render fields. A technical client opens DevTools and sees the full quote breakdown — the exact thing the product prevents.
|
|
|
|
**Prevention:**
|
|
- Define two distinct data shapes: `ClientView` and `AdminView`
|
|
- Client API routes (`/api/c/[token]/`) return `ClientView` only — enforced server-side, not in the UI
|
|
- `accepted_total` goes on the `clients` row. Client API never queries `quote_items`
|
|
|
|
**Warning signs:** Client API response includes `lineItems` filtered in the frontend. "Client sees only total" enforced with `display: none`.
|
|
|
|
**Phase mapping:** Data model and API shape — Phase 1.
|
|
|
|
---
|
|
|
|
### 3. Data Loss from Vercel Filesystem / In-Memory Storage
|
|
|
|
**What goes wrong:** Vercel serverless functions are stateless. File writes to local filesystem and in-memory state are lost between invocations. SQLite on disk silently vanishes after cold start. Bug only manifests in production.
|
|
|
|
**Prevention:**
|
|
- External persistent DB from day one: Neon (Postgres free tier)
|
|
- Never write to `fs` for persistent data on Vercel
|
|
- Validate persistence in the first production deploy
|
|
|
|
**Warning signs:** Project stores data in a `data/` JSON directory. SQLite without a remote adapter. Data changes in one request not visible in the next.
|
|
|
|
**Phase mapping:** Day-one infrastructure decision — Phase 1, before any real data is entered.
|
|
|
|
---
|
|
|
|
### 4. Over-Engineering Before the First Client Uses It
|
|
|
|
**What goes wrong:** Building the Claude onboarding flow and quote generator before a single client has opened their dashboard. Real clients still managed via email while the portal is "almost ready."
|
|
|
|
**Prevention:**
|
|
- Hard success criterion for Phase 1: one client link is shareable and works
|
|
- Phase 1 ships read-only client dashboard only
|
|
- Collect direct feedback from one real client before adding features
|
|
|
|
**Warning signs:** >2 weeks pass without a shareable client link. Claude integration started before admin edit UI exists.
|
|
|
|
**Phase mapping:** Enforced by roadmap phase ordering.
|
|
|
|
---
|
|
|
|
### 5. Token Is the Primary Key (Unrotatable)
|
|
|
|
**What goes wrong:** A client forwards their link. The link appears in a screenshot. There is no mechanism to rotate without losing data or breaking bookmarks. Worse if token = primary key: rotation requires a migration.
|
|
|
|
**Prevention:**
|
|
- Data model: stable internal UUID as PK; secret token is a separate, independently updatable field
|
|
- Build "Regenerate link" in admin area during Phase 2 — it's a single field UPDATE
|
|
- Overwriting the token field is sufficient to invalidate the old link
|
|
|
|
**Warning signs:** Token is used as PK of the client record. No admin affordance to regenerate a link.
|
|
|
|
**Phase mapping:** Data model separation — Phase 1. Admin rotation UI — Phase 2.
|
|
|
|
---
|
|
|
|
### 6. Client Approval Has No Immutable Record
|
|
|
|
**What goes wrong:** Client clicks "Approve." Later: "I never approved that." If approval is a boolean with no timestamp, there is no evidence. Weakens your position in commercial disputes.
|
|
|
|
**Prevention:**
|
|
- Store `approved_at` timestamp alongside the approval boolean — from day one
|
|
- Display approval timestamp in admin view
|
|
- Approvals are immutable for clients — no "undo" button
|
|
|
|
**Warning signs:** Approval is a boolean column with no timestamp. Client-visible "undo approval" button exists.
|
|
|
|
**Phase mapping:** Schema — Phase 1. Display in admin — Phase 2.
|
|
|
|
---
|
|
|
|
## Moderate Pitfalls
|
|
|
|
### 7. Payment Status Without a Valid State Machine
|
|
|
|
Payment has three states (da_saldare / inviata / saldato) for two payments. If transitions are not enforced, the dashboard shows contradictory states.
|
|
|
|
**Prevention:** Enforce valid transitions: `da_saldare → inviata → saldato`. Admin UI offers only valid next states.
|
|
|
|
**Phase mapping:** Admin UI — Phase 2.
|
|
|
|
---
|
|
|
|
### 8. Google Drive Links That Rot
|
|
|
|
Document links break when sharing settings change or the Drive account changes. Client sees a broken link to their own deliverable.
|
|
|
|
**Prevention:** Store a display name alongside the URL so broken links are visible in admin. Build a simple "update link" affordance in Phase 2.
|
|
|
|
---
|
|
|
|
### 9. Admin Area With No Real Access Control
|
|
|
|
`/admin` is a secret route with no authentication. A client who guesses the URL accesses all client data.
|
|
|
|
**Prevention:** Add Next.js middleware check against `ADMIN_PASSWORD` env variable before Phase 2 ships. Never rely on security through obscurity for a route that contains all client data.
|
|
|
|
**Phase mapping:** Phase 2, before admin area contains real data.
|
|
|
|
---
|
|
|
|
### 10. Comments Scope Creeping Into Threading
|
|
|
|
"Clients can leave comments" becomes complex when replies, read/unread state, and notifications are added. Can double Phase 1 scope.
|
|
|
|
**Prevention:** Phase 1 ships comments as a flat append-only list per task. No threading, no replies, no email notifications.
|
|
|
|
---
|
|
|
|
## Minor Pitfalls
|
|
|
|
### 11. DNS Configuration as a Last-Minute Task
|
|
|
|
`welcomeclient.iamcavalli.net` requires a CNAME to Vercel's DNS. Propagation takes minutes to hours. Doing this the day of a client demo misses the deadline.
|
|
|
|
**Prevention:** Configure DNS in Phase 1 before the UI is complete. Verify propagation independently.
|
|
|
|
---
|
|
|
|
### 12. Mobile Responsiveness as an Afterthought
|
|
|
|
Clients open the link on their phone from a shared message. A broken mobile layout is the first impression.
|
|
|
|
**Prevention:** Use Tailwind mobile-first defaults from the start. Test on a real phone before any link is sent.
|
|
|
|
---
|
|
|
|
### 13. No Empty States
|
|
|
|
A new client record with no tasks shows a blank page. The client assumes something is broken.
|
|
|
|
**Prevention:** Design minimal empty states for no-tasks, no-documents, no-comments.
|
|
|
|
---
|
|
|
|
## Phase-Specific Warnings Summary
|
|
|
|
| Phase Topic | Likely Pitfall | Mitigation |
|
|
|---|---|---|
|
|
| Client token generation | Guessable slug from name | Crypto-random UUID/nanoid, never name-derived |
|
|
| Client-facing API | Admin data in JSON response | `ClientView` type enforced server-side |
|
|
| Storage choice | Vercel filesystem not persistent | External DB (Neon) before first data write |
|
|
| Admin area access | No real auth | Middleware check before Phase 2 ships |
|
|
| Approval recording | Boolean-only, no audit trail | Store `approved_at` from day one |
|
|
| Token in data model | Token = PK, unrotatable | Separate stable ID from rotatable token field |
|
|
| Phase ordering | Claude flow before dashboard | Enforce: client view → admin edit → Claude |
|
|
| Comments | Threading scope creep | Flat list in Phase 1 |
|
|
| DNS | Last-minute failure | Configure and verify in Phase 1 |
|