docs(research): complete v2.0 stack, features, architecture, and pitfalls analysis
Synthesized research outputs from 4 parallel researcher agents: - STACK.md: 7 new npm packages (dnd-kit, TanStack Table, RHF, BullMQ), React 19 compat verified - FEATURES_v2.0.md: Feature landscape (table stakes, differentiators, anti-features), MVP breakdown - ARCHITECTURE.md: Schema additions (services, offer_phases, leads, quotes), 5-phase build order - PITFALLS.md: Critical/moderate/minor pitfalls + prevention strategies Ready for roadmap planning and phase-by-phase execution. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
+829
-144
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,412 @@
|
|||||||
|
# Feature Landscape: Business Operations Suite v2.0 (New Features)
|
||||||
|
|
||||||
|
**Domain:** Proposal generation + lightweight CRM for solo personal-branding consultant
|
||||||
|
**Researched:** 2026-06-10
|
||||||
|
**Research Mode:** Ecosystem (proposal software + lightweight CRM patterns)
|
||||||
|
**Confidence:** MEDIUM-HIGH
|
||||||
|
**Scope:** ONLY new v2.0 features (does NOT review v1.0 table stakes already shipped)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Executive Summary
|
||||||
|
|
||||||
|
The Business Operations Suite adds three interconnected workflows to ClientHub:
|
||||||
|
|
||||||
|
1. **Proposal Generation & Delivery** — Sales call → multistep proposal page (2-hour delivery) → lead selects tier A/B/C → acceptance triggers automation
|
||||||
|
2. **Lead Pipeline Management** — Minimal CRM for tracking prospects through 5 stages (Contacted → Qualified → Proposal Sent → Negotiating → Won/Lost)
|
||||||
|
3. **Onboarding Automation** — When lead is marked Won, auto-create client + project with phases copied from chosen offer + configurable payment schedule
|
||||||
|
|
||||||
|
**For a solo consultant, this is radically different from team CRMs** (Pipedrive, HubSpot, GoHighLevel). Avoid: territory management, team routing, email automation, role-based access, forecasting rollups, lead scoring algorithms. Instead: **minimal data entry, follow-up reminders based on last-contact date, and seamless handoff from proposal acceptance to project setup.**
|
||||||
|
|
||||||
|
**Core insight:** Solo consultants manage long-term relationships across years, not one-time deals closed in 30 days. Pipeline stages should reflect relationship milestones (Contacted, Qualified, Proposal Sent), not sales rep activity (Attempted, Left Message). Follow-ups are "who did I talk to recently?" not "assign task to rep."
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Table Stakes Features (New v2.0)
|
||||||
|
|
||||||
|
Features users expect when adopting a proposal + CRM system. Missing these = product feels incomplete.
|
||||||
|
|
||||||
|
### Proposal Generation & Delivery
|
||||||
|
|
||||||
|
| Feature | Why Expected | Complexity | Notes |
|
||||||
|
|---------|--------------|-----------|-------|
|
||||||
|
| Pick client + 1–3 offers, set per-quote prices | Core value: sales call → proposal in 2 hours. Prices set per quote, not from catalog (allows price increases over time without updating service catalog) | Medium | Depends on existing `offers` & `clients` (Phase 5). Quote prices override offer base prices. |
|
||||||
|
| Generate public multistep HTML/CSS page | Leads need clickable link, not PDF email. Interactive engagement beats static documents (Qwilr/Proposify standard). Reduces abandonment. | Medium | Responsive, mobile-first, branded with consultant name/logo. No login required. |
|
||||||
|
| Multistep flow (intro → pricing tiers → CTA) | Reduces cognitive overload vs. single long page. Step 1: offer overview. Step 2: A/B/C tiers with pricing. Step 3: accept/decline button. | Low | Conditional logic hides irrelevant sections; simple progressive disclosure. |
|
||||||
|
| Public acceptance button (no e-signature) | Lead confirms tier choice WITHOUT entering hub. Triggers: timestamp capture + email record + lead status update. | Low | Simple "Accept this offer" button → records accepted_at + accepted_by_email. E-signature deferred (design ready, v1 constraint). |
|
||||||
|
| Proposal URL shareable & public | Lead receives link in email, opens in browser, no friction. | Low | Generate unique slug (`/proposal/[uuid]`). No auth. Optionally expires after N days or on acceptance. |
|
||||||
|
| Acceptance proof (signer + timestamp) | Documentation for dispute resolution. Downloadable PDF or stored audit record. | Low | Store: accepted_by_name, accepted_by_email, accepted_at, accepted_offer_id. |
|
||||||
|
|
||||||
|
### Lead Pipeline
|
||||||
|
|
||||||
|
| Feature | Why Expected | Complexity | Notes |
|
||||||
|
|---------|--------------|-----------|-------|
|
||||||
|
| Pipeline stages: Contacted → Qualified → Proposal Sent → Negotiating → Won/Lost | Core tracking. Each stage requires buyer action, not just rep activity. Termination: Won or Lost. | Low | **Minimal for solo:** 5 stages max. More = data entry overhead. Stages are immutable enums. |
|
||||||
|
| Move lead between stages (UI) | Basic pipeline UX. See all leads grouped by stage at a glance. | Low | Dropdown per row (simple) or kanban board (polish). Dropdown sufficient for MVP. |
|
||||||
|
| Lead details: name, email, company, phone, last_contact_date, next_action, notes | Minimum context for follow-ups. | Low | Notes = freeform text (call outcomes, concerns, personality notes, next steps). |
|
||||||
|
| Lead created manually OR auto-created from proposal send | Manual: import prospects. Auto-create: when proposal sent to unknown email, create lead record automatically. | Low | Auto-create is convenience; manual is fallback. Both supported. |
|
||||||
|
| Log activities: calls, emails, meetings, notes | Track interactions without leaving dashboard. Auto-updates last_contact_date. | Low | Activity types: [call, email, meeting, note]. Log date, duration, description. Displayed as feed. |
|
||||||
|
|
||||||
|
### Follow-Up Reminders
|
||||||
|
|
||||||
|
| Feature | Why Expected | Complexity | Notes |
|
||||||
|
|---------|--------------|-----------|-------|
|
||||||
|
| Dashboard widget: "Follow up today" sorted by last_contact_date | Solves core pain: "who did I talk to recently that I should check in with?" | Low | Query: WHERE last_contact_date <= TODAY - 7 days AND stage NOT IN (Won, Lost). Red badge. |
|
||||||
|
| Proposal stall detector: if Proposal Sent >3 days, no response | Flags silence = red flag for follow-up. | Low | Simple dashboard alert; no email automation (consultant checks dashboard in morning). |
|
||||||
|
| Auto-update last_contact_date on activity | Keeps reminders accurate. Prevents "I called them but forgot to log it" drift. | Low | Trigger: on call/email/meeting/note creation OR stage change → update lead.last_contact_date = NOW. |
|
||||||
|
|
||||||
|
### Project Auto-Creation (Won → Onboarding)
|
||||||
|
|
||||||
|
| Feature | Why Expected | Complexity | Notes |
|
||||||
|
|---------|--------------|-----------|-------|
|
||||||
|
| On lead.stage = Won: auto-create `client` + `project` with copied phases | Closing automation. Proposal acceptance → instant hub readiness for delivery. | Medium | Requires: chosen offer known, project_offers finalized (Phase 5), 1-4 configurable installments. |
|
||||||
|
| Copy offer phases → project phases (modifiable) | Project inherits offer structure but can be customized per client without mutating offer template. | Medium | Phases are modifiable in hub (Phase 1). Creation source logged: "Copied from Offer X on [date]" for audit. |
|
||||||
|
| Set payment installments per project (not per offer) | Payment plan configured case-by-case: some 50/50, others 4-part. Allows flexibility. | Medium | Templates: 50/50 (acconto/saldo), 3-part, 4-part, custom. UI: choose template during Won → project creation. |
|
||||||
|
| Lead → Client transition: copy email, create token, link to existing client if repeat | Avoid duplicate clients. Allow linking Won lead to existing client (repeat engagement). | Medium | Check email collision. If exists: "Add project to existing client X?" vs. "Create new client". Generate secret token. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Differentiators (New v2.0)
|
||||||
|
|
||||||
|
Features that set ClientHub apart from generic proposal software + CRM. Not expected, but valued for solo consultant use case.
|
||||||
|
|
||||||
|
| Feature | Value Proposition | Complexity | Notes |
|
||||||
|
|---------|-------------------|-----------|-------|
|
||||||
|
| Tier-independent offer structure (Signature A/B/C as separate offers) | Each tier has own URL, phases, pricing. A = professional, B = budget—same catalog, different scope. No cloning, no inheritance logic. | Medium | Requires flexible offer builder (drag services between phases), offer status (draft/active/archived). Built in Phase 5. |
|
||||||
|
| Proposal phases visible in public page (lead sees what they're buying) | Lead sees not just price, but scope: "4-week branding sprint, 3 revision rounds, monthly retainer". Transparency builds confidence. | Low | Proposal page shows phase names, deliverables summary (text), timeline (start + duration). |
|
||||||
|
| Dashboard follow-up list filterable by offer type + stage | "Show all Signature A leads in Negotiating" to spot bottlenecks by tier. | Low | Filter buttons: by offer type, stage, last_contact_date range. Dropdown or toggle buttons. |
|
||||||
|
| Activity feed per lead (call notes, email log, meetings, searchable) | Context without leaving dashboard. "Last contact: 2026-05-28, 11am call—timeline concerns". | Medium | Depends on activity logging system. Store type, date, duration, description. Searchable. |
|
||||||
|
| Proposal pages branded with consultant info (not generic SaaS) | Proposal is part of sales process; branding reinforces personal brand. | Low | Logo, consultant name, brand colors, professional typography inherited from admin settings. |
|
||||||
|
| Email integration hint (design ready, defer to later batch) | Send proposal via dashboard; auto-log email send as activity. | Medium | Deferred per PROJECT.md (v1 constraint). Placeholder: manual copy-paste OK for MVP. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Anti-Features: What NOT to Build (v2.0)
|
||||||
|
|
||||||
|
| Anti-Feature | Why Avoid | What to Do Instead |
|
||||||
|
|--------------|-----------|-------------------|
|
||||||
|
| Multi-user team collaboration, role-based access | Solo consultant is single admin. Team features add auth complexity, sync issues, notification storms. Overhead >> value. | Stay single-admin. Architecture with permission flags but don't build UI/logic. If team joins later, migrate incrementally. |
|
||||||
|
| CRM email automation, drip campaigns, email sequences | Solo consultant sends proposals + checks in manually. Automation-heavy workflows are for outbound prospecting funnels (not this use case). Lead generation not in scope. | Keep simple: dashboard reminder = "check in with X". Consultant sends email manually, logs it as activity. |
|
||||||
|
| Forecasting with rollups, quota tracking, team capacity planning, territory assignment | These are team sales metrics. Solo consultant cares: "How many leads in pipeline?" and "What's revenue if all Negotiating deals close?" | Simple dashboard: shows total value by stage (e.g., "Negotiating: $45k"), breakdown by offer type. No forecast math. |
|
||||||
|
| Lead scoring, MQL → SQL qualification algorithms, scoring rules | Solo consultant qualifies leads manually (call + gut feel). Scoring requires training data and tuning. | Keep manual: stage = Contacted, Qualified, Proposal Sent. "Qualified" is consultant's call. No automation. |
|
||||||
|
| Calendar sync, email sync, call recording, Slack/Teams integration | Adds external dependency surface area. Consultant logs calls + emails manually. | Fallback: freeform "Call notes" text field per activity. No attempt to auto-log from email/Slack. |
|
||||||
|
| Multi-currency support, tax calculation, invoice generation, accounting system integration | Accounting is out of scope (PROJECT.md locked). Payment tracking only. | Keep simple: amount_accepted in EUR, payment schedule in local currency. No conversion logic or invoicing. |
|
||||||
|
| Proposal version control, edit history, detailed audit trail | Overkill for solo consultant. One proposal per client, accept or decline. | Simple: created_at, updated_at timestamps. Accepted version is locked (immutable once accepted_at set). No versioning. |
|
||||||
|
| Client can reverse/un-approve acceptance | Creates ambiguity in closing. | Once accepted_at is set, proposal is final. Consultant can move lead back to Proposal Sent if needed. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Feature Dependencies (v2.0)
|
||||||
|
|
||||||
|
```
|
||||||
|
Proposal Generation:
|
||||||
|
├─ clients (exists, Phase 1)
|
||||||
|
├─ projects (exists, Phase 1)
|
||||||
|
├─ offers (Phase 5: id, name, offer_type [Entry/Signature/Retainer/custom], created_at)
|
||||||
|
├─ services catalog (Phase 3: id, name, price, duration_days)
|
||||||
|
└─ offer_services (Phase 5: junction table, offers ↔ services)
|
||||||
|
|
||||||
|
Lead Pipeline & CRM:
|
||||||
|
├─ leads table (NEW: id, name, email, company, phone, created_at, last_contact_date, stage, offer_id FK, next_action text)
|
||||||
|
├─ activities table (NEW: id, lead_id FK, type enum [call/email/meeting/note], log_date, description, duration_minutes)
|
||||||
|
├─ lead_stage enum (NEW: Contacted, Qualified, Proposal Sent, Negotiating, Won, Lost)
|
||||||
|
|
||||||
|
Project Auto-Creation (Won → Onboarding):
|
||||||
|
├─ leads.stage = Won (from pipeline, not auto-set)
|
||||||
|
├─ projects (exists, must be creatable via API/trigger)
|
||||||
|
├─ phases (exists, must be copyable from offer phases)
|
||||||
|
├─ payments (Phase 5: must support project_id FK, amount, due_date, status)
|
||||||
|
└─ clients.token (exists, Phase 1/4: rotatable secret)
|
||||||
|
|
||||||
|
Proposal Public Pages:
|
||||||
|
└─ proposals table (NEW: id, client_id, lead_email, offers [JSON array], created_at, public_url, accepted_at, accepted_by_name, accepted_by_email, accepted_offer_id)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Critical dependency:** Phase 5's `project_offers` relationship must be finalized. If not, proposal generation cannot proceed. Phase 7 proposal work is blocked by Phase 5 completion.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Feature Categorization by Component
|
||||||
|
|
||||||
|
### Component 1: Proposal Builder & Public Pages
|
||||||
|
|
||||||
|
**Responsibility:**
|
||||||
|
- Admin UI: select client, pick 1–3 offers, confirm per-quote pricing, set lead email, generate shareable link
|
||||||
|
- Public page: multistep form (intro → tiers → CTA), responsive, no login, branded
|
||||||
|
- Acceptance: simple "Accept" button, no e-signature
|
||||||
|
- Database: `proposals` table
|
||||||
|
|
||||||
|
**Features in scope:**
|
||||||
|
- Proposal generation from client + offers + prices
|
||||||
|
- Public multistep page with interactive tier selection
|
||||||
|
- Conditional phase visibility (show/hide sections based on tier)
|
||||||
|
- Acceptance button + timestamp + email capture
|
||||||
|
- Auto-create lead on proposal send (if lead email not in DB)
|
||||||
|
- Proposal expiry (optional, default: never)
|
||||||
|
|
||||||
|
**Database schema:**
|
||||||
|
```
|
||||||
|
proposals:
|
||||||
|
id UUID PK
|
||||||
|
client_id FK → clients
|
||||||
|
lead_email VARCHAR (recipient)
|
||||||
|
offer_ids JSON array (which offers are included)
|
||||||
|
quote_prices JSON {offer_id: price} (per-quote override)
|
||||||
|
created_at timestamp
|
||||||
|
updated_at timestamp
|
||||||
|
public_url slug
|
||||||
|
accepted_at timestamp nullable
|
||||||
|
accepted_by_name VARCHAR nullable
|
||||||
|
accepted_by_email VARCHAR nullable
|
||||||
|
accepted_offer_id FK nullable (which tier was chosen)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Component 2: Lead Pipeline & Dashboard
|
||||||
|
|
||||||
|
**Responsibility:**
|
||||||
|
- Lead table with name, email, company, stage, last_contact_date, notes
|
||||||
|
- Dashboard: table or kanban view of all leads grouped by stage
|
||||||
|
- Activities: log calls, emails, meetings, notes
|
||||||
|
- Follow-up reminder widget: leads not contacted in 7+ days
|
||||||
|
|
||||||
|
**Features in scope:**
|
||||||
|
- Create/edit/delete leads manually or auto-create from proposal
|
||||||
|
- Move lead between stages (dropdown or drag/drop)
|
||||||
|
- Log activities: type [call/email/meeting/note], duration, description
|
||||||
|
- Auto-update last_contact_date on activity
|
||||||
|
- Follow-up reminders: "Follow up today" widget, proposal stall detector
|
||||||
|
- Lead notes: freeform text, searchable
|
||||||
|
- Filter by offer type, stage, date range
|
||||||
|
- Activity feed per lead (searchable)
|
||||||
|
|
||||||
|
**Database schema:**
|
||||||
|
```
|
||||||
|
leads:
|
||||||
|
id UUID PK
|
||||||
|
name VARCHAR
|
||||||
|
email VARCHAR (unique or indexed)
|
||||||
|
company VARCHAR nullable
|
||||||
|
phone VARCHAR nullable
|
||||||
|
stage enum [Contacted, Qualified, Proposal Sent, Negotiating, Won, Lost]
|
||||||
|
offer_id FK → offers nullable (which offer was sent)
|
||||||
|
created_at timestamp
|
||||||
|
last_contact_date date
|
||||||
|
next_action text nullable (next step consultant plans)
|
||||||
|
notes text (accumulated call notes, concerns, personality)
|
||||||
|
|
||||||
|
activities:
|
||||||
|
id UUID PK
|
||||||
|
lead_id FK → leads
|
||||||
|
type enum [call, email, meeting, note]
|
||||||
|
log_date timestamp
|
||||||
|
duration_minutes INT nullable
|
||||||
|
description text
|
||||||
|
created_at timestamp
|
||||||
|
```
|
||||||
|
|
||||||
|
### Component 3: Project Auto-Creation & Onboarding Flow
|
||||||
|
|
||||||
|
**Responsibility:**
|
||||||
|
- On lead.stage = Won, trigger creation: new client (if not exists), new project, copy phases, set payment schedule
|
||||||
|
- UI: lead moves to Won → modal: "Which offer was chosen?" → "Confirm project creation?" → choose payment template → create
|
||||||
|
- Result: lead archived, project appears in /admin/projects, client gets dashboard URL
|
||||||
|
|
||||||
|
**Features in scope:**
|
||||||
|
- Auto-create client from lead details
|
||||||
|
- Auto-create project with copied phases
|
||||||
|
- Copy phases from offer (modifiable in hub)
|
||||||
|
- Payment installments: choose template → creates N payment records
|
||||||
|
- Generate client secret token + dashboard URL
|
||||||
|
- Optional: link to existing client instead of creating new
|
||||||
|
- Notification: "Lead X → Project Y created on [date]"
|
||||||
|
|
||||||
|
**Database schema:**
|
||||||
|
```
|
||||||
|
No new tables needed. Uses existing: clients, projects, phases, payments.
|
||||||
|
When lead → Won:
|
||||||
|
1. Check if lead.email in clients table
|
||||||
|
2. If not: create new client, generate secret token
|
||||||
|
3. If yes: ask to link to existing client or create new
|
||||||
|
4. Copy phases from offer_id to new project
|
||||||
|
5. Create N payment records based on chosen template
|
||||||
|
6. Update lead.stage = Won (already set by UI)
|
||||||
|
7. Record: project creation_source = "Lead X, offer Y"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## MVP Recommendation
|
||||||
|
|
||||||
|
### Phase 7 (MVP): Core Proposal Generation + Basic Pipeline
|
||||||
|
|
||||||
|
**Priority 1: Must-Have (Launch features)**
|
||||||
|
|
||||||
|
1. **Proposal builder UI:** client + offer(s) + per-quote prices → generate public multistep page
|
||||||
|
2. **Public proposal page:** Step 1 = offer overview, Step 2 = A/B/C tiers + pricing, Step 3 = "Accept this Offer" CTA
|
||||||
|
3. **Acceptance flow:** "Accept" button → timestamp + email capture, marks proposal as accepted
|
||||||
|
4. **Lead auto-creation:** when proposal sent to unknown email, auto-create lead record
|
||||||
|
5. **Basic lead pipeline:** Contacted, Qualified, Proposal Sent, Negotiating, Won, Lost stages
|
||||||
|
6. **Lead dashboard:** table view, move stage via dropdown, lead details sidebar
|
||||||
|
7. **Follow-up reminder widget:** leads not contacted in 7+ days, sorted by last_contact_date, dashboard badge
|
||||||
|
8. **Activity logging (basic):** create call/email/meeting records, auto-update last_contact_date
|
||||||
|
|
||||||
|
**Estimated effort:** 5–7 days (proposal builder UI + public page + lead CRUD + follow-up reminders)
|
||||||
|
|
||||||
|
**Launch readiness:** Solo consultant can generate proposal in 2 hours, track lead through pipeline, see follow-up reminders.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 8 (Enrichment): Lead Context & Polish
|
||||||
|
|
||||||
|
**Priority 2: Features**
|
||||||
|
|
||||||
|
1. **Lead notes:** freeform text per lead, searchable
|
||||||
|
2. **Activity feed:** display recent activities per lead (call, email, meeting, note), searchable
|
||||||
|
3. **Dashboard filters:** by offer type, stage, date range (last_contact_date)
|
||||||
|
4. **Proposal stall detector:** flag if Proposal Sent >3 days with no activity
|
||||||
|
5. **Lead detail card:** expand to show full context (notes, activities, next_action) without leaving dashboard
|
||||||
|
|
||||||
|
**Estimated effort:** 3–4 days
|
||||||
|
|
||||||
|
**Value:** Consultant has full context for each lead without switching views.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 9+ (Automation & Onboarding): Project Auto-Creation
|
||||||
|
|
||||||
|
**Priority 3: Features**
|
||||||
|
|
||||||
|
1. **Project auto-creation:** on lead → Won, auto-create client + project with copied phases
|
||||||
|
2. **Copy phases from offer:** phases inherit name, duration, deliverables from offer template; modifiable in hub
|
||||||
|
3. **Payment installments:** choose template (50/50, 3-part, 4-part, custom) → create N payment records
|
||||||
|
4. **Client token generation:** auto-generate secret token, send dashboard URL to client
|
||||||
|
5. **Duplicate client check:** if lead.email exists in clients, ask to link or create new
|
||||||
|
6. **Won lead notification:** dashboard shows "Lead X → Project Y created", activity logged
|
||||||
|
|
||||||
|
**Estimated effort:** 3–4 days
|
||||||
|
|
||||||
|
**Value:** Closing automation. Lead acceptance → instant project setup in hub, ready for delivery.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Complexity Assessment (Effort & Maintenance)
|
||||||
|
|
||||||
|
| Feature | Dev Effort | Maintenance Burden | Deferability | Phase |
|
||||||
|
|---------|-----------|-------------------|----------------|-------|
|
||||||
|
| Proposal generation (builder UI + logic) | 2–3 days | Low (static) | No—core | 7 |
|
||||||
|
| Public multistep page (HTML/CSS template) | 1–2 days | Low (static) | No—core | 7 |
|
||||||
|
| Acceptance flow (button + timestamp) | 1 day | Minimal | No—core | 7 |
|
||||||
|
| Lead pipeline (CRUD + stage transitions) | 1 day | Low | No—core | 7 |
|
||||||
|
| Follow-up reminders (dashboard query) | 4 hours | Minimal | No—core | 7 |
|
||||||
|
| Activity logging (calls, emails, meetings) | 1 day | Low | Yes—Phase 8 | 8 |
|
||||||
|
| Activity feed (search + timeline) | 1 day | Low | Yes—Phase 8 | 8 |
|
||||||
|
| Lead notes + search | 1 day | Low | Yes—Phase 8 | 8 |
|
||||||
|
| Dashboard filters (offer type, stage, date) | 1 day | Low | Yes—Phase 8 | 8 |
|
||||||
|
| Project auto-creation (trigger + cascade) | 2 days | Medium (error handling) | Yes—Phase 9 | 9 |
|
||||||
|
| Payment installments (templates) | 1 day | Low | Yes—Phase 9 | 9 |
|
||||||
|
| Client token generation + link | 4 hours | Low | Yes—Phase 9 | 9 |
|
||||||
|
| Duplicate client check | 4 hours | Low | Yes—Phase 9 | 9 |
|
||||||
|
| Email integration (send proposal, auto-log) | 2–3 days | Medium (provider) | Yes—defer | Later |
|
||||||
|
| Multi-user + team features | 3–5 days | Medium-High (roles) | N/A—anti-feature | Never |
|
||||||
|
|
||||||
|
**Total Phase 7 MVP:** ~5–6 days
|
||||||
|
**Total Phases 7–9:** ~11–14 days (complete suite)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Solo Consultant Design Constraints
|
||||||
|
|
||||||
|
### What's Different from Team CRMs
|
||||||
|
|
||||||
|
| Constraint | Implication | Design Pattern |
|
||||||
|
|-----------|-----------|-----------------|
|
||||||
|
| Single admin, no team routing | No "assign to rep" or "owner" field. All leads = Simone. | All leads belong to admin; no ownership concept. No role-based UI. |
|
||||||
|
| Relationship-heavy, not transaction-heavy | Leads stay in pipeline for months, not 30 days. | Stage = relationship milestone (Contacted, Qualified, Proposal Sent), not rep activity (Attempted, Left Message, Following Up). |
|
||||||
|
| Qualitative follow-up, not automation | "Check in with X, heard timeline concerns" not email sequences. | Notes + activity log, no email automation. Manual send, logged as activity. |
|
||||||
|
| Low deal volume (5–15 in flight) | Pipeline visualization doesn't need advanced analytics. | Simple table or 5-column kanban board. No forecasting rollups. |
|
||||||
|
| Minimal data entry tolerance | Won't log every call if it takes >3 clicks. | Modal: "Log call → name/date/notes → save" in <10 seconds. |
|
||||||
|
| No role-based access | No "manager sees forecast, rep sees pipeline". | Single "admin dashboard" with all data. No permission layers. |
|
||||||
|
| No forecasting pressure | Consultant forecasts deal value by feel, not rollup math. | Dashboard shows "Total Negotiating: $45k". That's it. No probability weighting. |
|
||||||
|
| Calendar/email often offline (field work) | Email sync is nice-to-have, not must-have. | Fallback: manual entry. No real-time sync requirement. |
|
||||||
|
| Accountability is personal, not hierarchical | No "why is this deal stalled?" escalations. | Follow-up reminder = consultant decides action. No automatic escalations. |
|
||||||
|
|
||||||
|
### UX Patterns for Solo Consultant
|
||||||
|
|
||||||
|
1. **Minimize data entry by default:** Yes/No confirmations, checkboxes, dropdowns. Avoid text fields unless essential.
|
||||||
|
2. **Context visible by default:** Open a lead → see last 3 activities, next_action, stage, offer type in one view. No tabs.
|
||||||
|
3. **Dashboard as command center:** Lead reminders + upcoming payments + next meetings all visible. No app switching.
|
||||||
|
4. **Proposal as shared artifact:** Lead sees what they're buying (phases, deliverables, price). Consultant sees acceptance status + lead email.
|
||||||
|
5. **Automation where it reduces clicks:** Accept proposal → auto-create lead. Move to Won → ask once for payment plan, auto-create project. Don't auto-email.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pipeline Benchmark (Reference)
|
||||||
|
|
||||||
|
From industry research:
|
||||||
|
|
||||||
|
| Metric | Benchmark | Implication for ClientHub |
|
||||||
|
|--------|-----------|---------------------------|
|
||||||
|
| Typical pipeline stages | 5–7 stages | ClientHub uses 5: Contacted, Qualified, Proposal Sent, Negotiating, Won/Lost. Minimal overhead. |
|
||||||
|
| Proposal Sent → Negotiation conversion | 40%+ | Flag stale proposals (>3 days, no activity) in dashboard. |
|
||||||
|
| Negotiation → Won conversion | 50%+ | If low, consultant reviews proposal scope or pricing. |
|
||||||
|
| Lead follow-up frequency | 8+ touches to close | Activity logging tracks touches (calls, emails, meetings). No automation; manual follow-up. |
|
||||||
|
| Typical pipeline review cadence | Weekly or bi-weekly | Consultant checks dashboard daily; reviews pipeline health weekly. |
|
||||||
|
| Average deal cycle for consultants | 30–90 days | ClientHub supports long cycles (no pressure to close fast). |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Confidence & Sources
|
||||||
|
|
||||||
|
| Area | Confidence | Basis | Sources |
|
||||||
|
|------|------------|-------|---------|
|
||||||
|
| Proposal software features (multistep, acceptance, tiering) | HIGH | Verified with Qwilr, PandaDoc, Proposify; 2026 comparison articles | [Qwilr vs PandaDoc](https://www.proposify.com/blog/qwilr-vs-pandadoc), [PandaDoc vs Proposify vs Qwilr](https://saas-tools.medium.com/pandadoc-vs-proposify-vs-qwilr-which-proposal-tool-is-worth-your-budget-in-2026-8e8339d6ab87), [8 best proposal software](https://www.getaccept.com/blog/proposal-software) |
|
||||||
|
| Pipeline stage structure (5–7 stages, Won/Lost termination) | HIGH | Verified with Pipedrive, Capsule CRM, Salesforce; "Proposal Sent → Negotiation → Won" is standard | [Salesforce Pipeline Management](https://www.salesforce.com/sales/pipeline/management/), [CRM Pipeline Stages](https://prospeo.io/s/crm-pipeline-stages), [GoHighLevel Pipeline](https://ecosire.com/blog/ghl-crm-pipeline-management) |
|
||||||
|
| Lead follow-up reminders (last_contact_date, 7+ days flagged) | MEDIUM-HIGH | Sourced from Outreach, HubSpot, Nimble best practices; solo consultant context is reasonable inference | [Sales Pipeline Best Practices](https://www.nimble.com/blog/best-practices-of-sales-pipeline-management/), [Outreach Pipeline Management](https://www.outreach.ai/resources/blog/sales-pipeline-management-best-practices) |
|
||||||
|
| Solo consultant CRM avoidance of team features | MEDIUM | Sourced from "Best CRM for Solopreneurs" guides; solo preference for simplicity consistent across 3+ sources | [Breakcold: CRM for Consultants](https://www.breakcold.com/blog/crm-for-consultants), [Authencio: CRM for Freelancers](https://www.authencio.com/blog/best-crm-for-consultants-freelancers-guide), [Addtocrm: Best CRM Solopreneurs](https://addtocrm.com/tools/best-crm-for-solopreneurs), [Mimiran: Anti-CRM](https://www.mimiran.com/fun-crm-for-solo-consultants-who-hate-selling/) |
|
||||||
|
| Tiered proposal (A/B/C independent offers) | MEDIUM-HIGH | Verified via multiple consulting pricing guides; "3 tiers ideal" is consensus. Independent offers is ClientHub-specific. | [Mercury: Pricing Strategy](https://mercury.com/blog/pricing-strategy-consulting), [Ignition: Tiered Pricing](https://www.ignitionapp.com/blog/tiered-pricing-strategy-for-professional-services-proposal-templates), [Consulting Success: Consulting Rates](https://www.consultingsuccess.com/consulting-rates) |
|
||||||
|
| Multi-step form engagement patterns | HIGH | Verified with Heyflow, Optimonk, HubSpot research; form completion rates improve with progressive disclosure | [Instapage: Multi-Step Forms](https://instapage.com/blog/multi-step-forms), [HubSpot: Multi-Step Forms](https://blog.hubspot.com/marketing/multi-step-forms), [Webstacks: Multi-Step Form Examples](https://www.webstacks.com/blog/multi-step-form) |
|
||||||
|
| Proposal acceptance automation (→ project creation) | MEDIUM | Sourced from Anchor, Estimate Rocket, Monograph workflows. ClientHub implementation is custom but pattern is established. | [Sayanchor: Proposal Acceptance](https://www.sayanchor.com/post/proposal-acceptance-guide), [Monograph: Build, Send, Sign](https://monograph.com/blog/build-send-and-sign-proposals-with-pipeline), [DocuSign: Workflow Automation](https://www.docusign.com/blog/workflow-automation-electronic-signatures) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Open Questions for Later Phases
|
||||||
|
|
||||||
|
1. **Email integration:** Should proposals be sent via dashboard integration, or manual copy-paste? (Deferred to Phase 10+)
|
||||||
|
2. **Proposal expiry:** Should proposals auto-expire after N days, or stay open indefinitely? (MVP: no expiry)
|
||||||
|
3. **Lead de-duplication:** If lead email is already in clients table, how should system handle it? (MVP: manual check; Phase 9+ auto-detect)
|
||||||
|
4. **Activity type richness:** Should activities include location, participant names, sentiment tags? (MVP: basic type + notes; Phase 8 can enhance)
|
||||||
|
5. **Proposal versioning:** Can consultant send multiple proposals to same lead? (MVP: yes, same lead.email + new proposal record)
|
||||||
|
6. **Payment plan customization:** Can consultant define custom installment schedules, or only use templates? (MVP: templates; Phase 9 can add custom)
|
||||||
|
7. **Lead archive vs. delete:** When lead is Won → converted to client/project, should lead record stay in DB for history? (Recommendation: keep for audit trail; mark archived or moved_to_project_id)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Appendix: Frequently Asked Questions
|
||||||
|
|
||||||
|
**Q: Why no e-signature in MVP?**
|
||||||
|
A: Design is ready (CLAUDE.md notes it as deferred). A simple "Accept" button + timestamp is sufficient proof of acceptance for solo consultant use case. E-signature (DocuSign, Stripe Sign) adds external dependency and cost; defer unless client explicitly requests.
|
||||||
|
|
||||||
|
**Q: Should proposals auto-expire?**
|
||||||
|
A: No, not in MVP. Solo consultant may send proposal Monday, follow up Thursday, acceptance Friday. Expiry is team-sales friction (forces re-quote). Keep simple: accepted_at = null means still open.
|
||||||
|
|
||||||
|
**Q: Can a lead be in two stages at once?**
|
||||||
|
A: No. Stage is singular immutable state at any moment. If second proposal sent to same lead: either (a) create new lead record (same email, add suffix "2"), or (b) update same lead.stage to "Proposal Sent" again. Option (b) is simpler; stage can repeat; last_contact_date updates on each proposal send.
|
||||||
|
|
||||||
|
**Q: Should activities auto-log from email/Slack?**
|
||||||
|
A: No, not in MVP. Adds external dependency surface. Fallback: consultant manually logs calls/emails as activities in <10 seconds. Acceptable for solo consultant volume (5–15 leads).
|
||||||
|
|
||||||
|
**Q: What if lead email already exists in clients table?**
|
||||||
|
A: Phase 9 auto-detection: when moving lead to Won, check if lead.email in clients. If yes, ask: "Link to existing client X (repeat engagement)?" or "Create new client". Prevents duplicate records.
|
||||||
|
|
||||||
|
**Q: Can phases be reordered after project creation?**
|
||||||
|
A: Yes. Phases are copied from offer at project creation time, but project phases are modifiable in hub (Phase 1, already built). Offer template is never mutated. Studio-grade immutability.
|
||||||
|
|
||||||
|
**Q: What's the payment flow after Won → project creation?**
|
||||||
|
A: Consultant chooses payment template (50/50, 3-part, 4-part, custom) during Won → project creation modal. System creates N records in `payments` table with due_dates calculated from project start_date. Client sees payment schedule in hub dashboard (Phase 1, already built). No invoicing or collection automation (out of scope, PROJECT.md locked).
|
||||||
|
|
||||||
|
**Q: Can consultant change their mind after accepting a proposal?**
|
||||||
|
A: If consultant moves lead back to Proposal Sent or Negotiating (after accepting and creating project), the original accepted_proposal record remains immutable. Project can be deleted/archived if needed, but proposal acceptance is final. This matches real-world: once accepted, consultant can't un-close a deal.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**End of FEATURES_v2.0.md**
|
||||||
+343
-84
@@ -1,132 +1,391 @@
|
|||||||
# Technology Stack — ClientHub Freelancer Client Portal
|
# Technology Stack — ClientHub v2.0 Business Operations Suite
|
||||||
|
|
||||||
**Project:** ClientHub (welcomeclient.iamcavalli.net)
|
**Project:** ClientHub
|
||||||
**Researched:** 2026-05-09
|
**Current Version:** 1.0 (Production: hub.iamcavalli.net on Coolify/Hetzner)
|
||||||
**Confidence:** HIGH
|
**Researched:** 2026-06-11
|
||||||
|
**Scope:** Stack additions for v2.0 features: drag-and-drop offer builder, public multistep quote pages, CRM lead pipeline, follow-up reminder system.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Recommended Stack
|
## Existing Stack (v1.0 — Validated)
|
||||||
|
|
||||||
### Core Framework
|
| Technology | Version | Status | Role |
|
||||||
|
|------------|---------|--------|------|
|
||||||
| Technology | Version | Purpose | Why |
|
| Next.js | 16 | Production | App Router, Server Actions |
|
||||||
|------------|---------|---------|-----|
|
| React | 19 | Production | UI rendering |
|
||||||
| Next.js | 15.x (latest stable) | Full-stack app framework | App Router + Server Actions replace a separate API layer. Vercel-native: no adapter needed. First-class TypeScript. |
|
| TypeScript | 5.x | Production | Type safety (strict mode) |
|
||||||
| React | 19.x | UI rendering | Bundled with Next.js. Server Components eliminate client-side waterfalls for the read-heavy client portal. |
|
| Postgres | (Neon) | Production | Primary database |
|
||||||
| TypeScript | 5.x | Type safety | Drizzle + Zod give end-to-end type inference from DB schema to form validation. |
|
| Drizzle ORM | Latest | Production | DB access + migrations |
|
||||||
|
| Auth.js | v4 | Production | Admin session + token middleware |
|
||||||
**Why NOT Remix / SvelteKit / Astro:** They work on Vercel but add unfamiliarity overhead with no gain at this scale.
|
| Tailwind CSS | v4 | Production | Utility-first styling |
|
||||||
|
| shadcn/ui | (latest) | Production | Component library (copied) |
|
||||||
|
| Zod | Latest | Production | Validation schemas |
|
||||||
|
| nanoid | Latest | Production | Token generation |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Database
|
## New Stack Additions for v2.0
|
||||||
|
|
||||||
| Technology | Purpose | Why |
|
### 1. Drag-and-Drop Offer Builder (Services Between Phases)
|
||||||
|------------|---------|-----|
|
|
||||||
| Neon (serverless Postgres) | Primary database | Free plan: 0.5 GB storage + 100 CU-hours/month — sufficient for 5–20 clients. Scales to zero between uses. Native Vercel integration that auto-injects DATABASE_URL per preview branch. |
|
|
||||||
| Drizzle ORM | DB access + migrations | Lightest-weight TS ORM. Ships `drizzle-orm/neon-http` serverless driver — no persistent TCP connections, works in Vercel Node and Edge runtimes for free. Schema-as-code with `drizzle-kit` handles migrations. |
|
|
||||||
|
|
||||||
**Why NOT Prisma:** Needs PgBouncer or Prisma Accelerate ($) for serverless connection pooling. Drizzle's `neon-http` handles this natively at zero cost.
|
**Requirement:** Move services between offer phases, visual reordering, keyboard + mouse support.
|
||||||
|
|
||||||
**Why NOT Supabase:** Adds RLS, Realtime, and Auth overhead you don't need and will have to maintain.
|
| Technology | Version | Purpose | Why This Choice | Status |
|
||||||
|
|------------|---------|---------|-----------------|--------|
|
||||||
|
| `@dnd-kit/react` | ^10.0.0+ | Drag-drop core + React hooks | Modern, React 19 verified compatible. Official support via `@dnd-kit/react` package (not legacy `@dnd-kit/core`). Built-in PointerSensor (mouse/touch/pen) + KeyboardSensor. Zero peer dependency conflicts with Next.js 16 + React 19. | RECOMMENDED |
|
||||||
|
| `@dnd-kit/helpers` | ^10.0.0+ | move(), swap() utilities | Bundles with React pkg; simplifies reordering logic. | RECOMMENDED |
|
||||||
|
|
||||||
|
**Why NOT Alternatives:**
|
||||||
|
- `@hello-pangea/dnd` (v16–17): Locks peer dependency to React 18.x. Official React 19 support not released as of June 2026. Community testing shows it *may* work (with peer dependency override), but not officially supported.
|
||||||
|
- `react-beautiful-dnd`: Archived since 2022. Unmaintained.
|
||||||
|
- `pragmatic-drag-and-drop` (Atlassian): Low-level browser API attachment (manual useEffect in each component). Higher boilerplate than dnd-kit for simple offer builder. Optimized for enterprise real-time collaboration (not your use case).
|
||||||
|
- Native HTML5 Drag & Drop API: Browser-native but poor accessibility, inconsistent across browsers, verbose API.
|
||||||
|
|
||||||
|
**Installation:**
|
||||||
|
```bash
|
||||||
|
npm install @dnd-kit/react @dnd-kit/helpers
|
||||||
|
```
|
||||||
|
|
||||||
|
**Integration Point:**
|
||||||
|
Wrap offer builder section with `DragDropProvider`, configure sensors for accessibility:
|
||||||
|
```typescript
|
||||||
|
import { DragDropProvider } from '@dnd-kit/react';
|
||||||
|
import { PointerSensor, KeyboardSensor } from '@dnd-kit/dom';
|
||||||
|
|
||||||
|
<DragDropProvider sensors={[PointerSensor, KeyboardSensor]}>
|
||||||
|
{/* Phases with draggable services inside */}
|
||||||
|
</DragDropProvider>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Confidence:** HIGH — dnd-kit v10+ officially supports React 19. Verified via Context7 documentation.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Authentication
|
### 2. CRM Lead Pipeline UI (Kanban Board + Table)
|
||||||
|
|
||||||
| Technology | Purpose | Why |
|
**Requirement:** Lead stages (Qualificato → Preventivato → Vinto/Perso), drag-drop between columns, sortable lead table with filters.
|
||||||
|------------|---------|-----|
|
|
||||||
| Auth.js (next-auth) v4 | Admin session management | Credentials provider with a single admin account. Session stored as signed JWT cookie. No user table in DB. |
|
|
||||||
| Next.js Middleware (custom) | Client secret-link validation | Each client has a `secretToken` (nanoid, 21 chars) stored in DB. Middleware reads `[token]` path segment, validates against Neon, returns 404 on miss. Runs at the edge before any page renders. |
|
|
||||||
| nanoid | Token generation | Cryptographically secure, URL-safe, 21-char default (~126 bits of entropy). Generated once at client creation. |
|
|
||||||
|
|
||||||
**Auth flow summary:**
|
| Technology | Version | Purpose | Why This Choice | Status |
|
||||||
- `/admin/*` → Auth.js session required (single admin account)
|
|------------|---------|---------|-----------------|--------|
|
||||||
- `/c/[token]/*` → Middleware validates token against Neon, 404 on miss
|
| `@tanstack/react-table` | ^8.0.0+ | Headless table engine | Lightweight, zero UI opinions. Composes perfectly with shadcn/ui. Powers sorting, filtering, row selection on lead list. No Material UI lock-in. Industry standard. | RECOMMENDED |
|
||||||
- Client pages: zero auth library overhead
|
| `@dnd-kit/react` | (same as above) | Kanban column reordering | Reuse same DragDropProvider from offer builder for lead status columns. | RECOMMENDED |
|
||||||
|
|
||||||
|
**Why NOT Alternatives:**
|
||||||
|
- `Material React Table`: Locks you into Material UI styling (conflicts with Tailwind v4).
|
||||||
|
- Pre-built kanban packages (React Big Calendar, react-kanban, etc.): Opinionated styling overhead. dnd-kit + shadcn handles it lighter.
|
||||||
|
- `TanStack Query` (React Query): Not needed here. Data is server-fetched once per page load, then local state. Add if you need sync polling later.
|
||||||
|
|
||||||
|
**Installation:**
|
||||||
|
```bash
|
||||||
|
npm install @tanstack/react-table
|
||||||
|
```
|
||||||
|
|
||||||
|
**Integration Point:**
|
||||||
|
```typescript
|
||||||
|
import { useReactTable, getCoreRowModel } from '@tanstack/react-table';
|
||||||
|
|
||||||
|
const table = useReactTable({
|
||||||
|
data: leads,
|
||||||
|
columns: [/* name, email, stage, last_contact, next_reminder */],
|
||||||
|
getCoreRowModel: getCoreRowModel(),
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**Confidence:** HIGH — TanStack Table is framework-agnostic, tested with shadcn/ui in hundreds of productions.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### UI
|
### 3. Public Multistep Quote Pages (Form + Validation)
|
||||||
|
|
||||||
| Technology | Purpose | Why |
|
**Requirement:** 3–5 step wizard (select client → select offer tier → review pricing → summary), per-step validation, no authentication.
|
||||||
|------------|---------|-----|
|
|
||||||
| Tailwind CSS v4 | Utility-first styling | CSS-first configuration, zero runtime overhead. |
|
| Technology | Version | Purpose | Why This Choice | Status |
|
||||||
| shadcn/ui | Component library | Components copied into codebase (no runtime dep). Built on Radix UI (accessible). Provides: Badge, Progress, Card, Dialog, Table, Textarea, Select. |
|
|------------|---------|---------|-----------------|--------|
|
||||||
| lucide-react | Icons | Tree-shaken, SVG-based, consistent. |
|
| `react-hook-form` | ^7.0.0+ | Form state + client validation | You already know this pattern (used in admin). Integrates seamlessly with Server Actions. Minimal bundle (8kb gzip). Use `zodResolver` for Zod bridge. | RECOMMENDED |
|
||||||
|
| `@hookform/resolvers` | ^3.0.0+ | Zod ↔ RHF integration | 1kb addon. Single source of truth: write Zod schema once, use client (resolver) + server (Server Action). | RECOMMENDED |
|
||||||
|
| Zod | (existing) | Shared validation schema | Reuse existing Zod setup. No new dependency. Quote schema: `client_id`, `selected_offers[]`, `tier_selection`, `pricing_overrides`. | RECOMMENDED |
|
||||||
|
|
||||||
|
**Why NOT Alternatives:**
|
||||||
|
- `Conform`: Newer, Server Action-native, requires more setup for multi-step state management. Overkill for a simple quote form.
|
||||||
|
- `Formik`: Maintenance mode since 2022. RHF is lighter + actively maintained.
|
||||||
|
- No library (plain form + `<form onSubmit>`): Fine for very simple forms, but multi-step wizard needs state management. RHF handles this elegantly.
|
||||||
|
|
||||||
|
**Installation:**
|
||||||
|
```bash
|
||||||
|
npm install react-hook-form @hookform/resolvers
|
||||||
|
```
|
||||||
|
|
||||||
|
**Integration Pattern:**
|
||||||
|
```typescript
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
|
|
||||||
|
const quoteSchema = z.object({
|
||||||
|
client_id: z.string().uuid(),
|
||||||
|
selected_offers: z.array(z.string().uuid()).min(1),
|
||||||
|
pricing_tier: z.enum(['entry', 'signature', 'retainer']),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default function QuoteForm() {
|
||||||
|
const form = useForm({
|
||||||
|
resolver: zodResolver(quoteSchema),
|
||||||
|
mode: 'onChange', // validate on each keystroke for UX
|
||||||
|
});
|
||||||
|
|
||||||
|
async function onSubmit(data: z.infer<typeof quoteSchema>) {
|
||||||
|
const result = await createQuote(data); // Server Action, server-side Zod validation
|
||||||
|
}
|
||||||
|
|
||||||
|
return <form onSubmit={form.handleSubmit(onSubmit)}>...</form>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Quote Page Strategy:**
|
||||||
|
- 3–5 steps total. ~2–3 fields per step.
|
||||||
|
- Validate only the *current step* before advancing (UX: don't block with future validation).
|
||||||
|
- Final submit: Server Action runs full Zod validation server-side (belt-and-suspenders).
|
||||||
|
- Use shadcn/ui Form, Input, Select, Button components (already in your stack).
|
||||||
|
|
||||||
|
**Confidence:** HIGH — RHF + Zod + Server Actions is the 2025–2026 standard for Next.js 15+ App Router. Multiple production examples verified.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Forms and Validation
|
### 4. Follow-Up Reminder System (Scheduled Jobs)
|
||||||
|
|
||||||
| Technology | Purpose | Why |
|
**Requirement:** "Follow-up in 3 days", "Check proposal expiry", compute when to show "needs follow-up" in dashboard, persist jobs across server restarts.
|
||||||
|------------|---------|-----|
|
|
||||||
| Zod | Schema validation | Server-side in Server Actions + client-side with RHF resolver. Single source of truth for data shapes. |
|
| Technology | Version | Purpose | Why This Choice | Status |
|
||||||
| React Hook Form | Admin form state | Complex admin forms (client onboarding, task editing, quote builder). Client-facing pages use native `<form>` + Server Actions. |
|
|------------|---------|---------|-----------------|--------|
|
||||||
|
| `bullmq` | ^5.0.0+ | Job queue + time-based scheduling | Redis-backed. Jobs survive server restarts (persisted in Redis). Supports delays (`{ delay: msUntilTomorrow }`), recurring cron jobs, and job flows. Production-grade SaaS reminder standard. | RECOMMENDED |
|
||||||
|
| Redis | ^5.0.0+ (server) | Job storage + queue backend | Your Coolify stack likely has Redis. BullMQ requires it. Same instance can be used for caching, sessions, or other purposes. | REQUIRED |
|
||||||
|
|
||||||
|
**Why NOT Alternatives:**
|
||||||
|
- `node-cron`: Runs in-process. Jobs lost on server restart. Dangerous for production reminders.
|
||||||
|
- `Agenda`: Requires MongoDB. Adds infrastructure complexity (you already have Postgres + Redis).
|
||||||
|
- `Bull` (legacy): BullMQ is the rewrite; Bull is deprecated.
|
||||||
|
- `node-schedule`: Similar to node-cron; not persistent.
|
||||||
|
|
||||||
|
**Installation:**
|
||||||
|
```bash
|
||||||
|
npm install bullmq redis
|
||||||
|
```
|
||||||
|
|
||||||
|
**Integration Pattern:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// app/api/workers/reminders/route.ts (or separate Node service)
|
||||||
|
import { Worker, Queue } from 'bullmq';
|
||||||
|
import redis from '@/lib/redis'; // Your Redis client
|
||||||
|
|
||||||
|
const reminderQueue = new Queue('reminders', { connection: redis });
|
||||||
|
|
||||||
|
// Enqueue a reminder from a Server Action
|
||||||
|
export async function scheduleFollowUp(leadId: string, delayMs: number) {
|
||||||
|
await reminderQueue.add(
|
||||||
|
'follow-up',
|
||||||
|
{ leadId, admin_id: 'you', type: 'follow-up' },
|
||||||
|
{ delay: delayMs } // delay in milliseconds (e.g., 86400000 = 24h)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Worker processes reminders at scheduled time
|
||||||
|
const worker = new Worker(
|
||||||
|
'reminders',
|
||||||
|
async (job) => {
|
||||||
|
const { leadId, type } = job.data;
|
||||||
|
|
||||||
|
// Fetch lead, mark as "needs follow-up", update dashboard feed
|
||||||
|
const lead = await db.select().from(leads).where(eq(leads.id, leadId)).limit(1);
|
||||||
|
|
||||||
|
if (lead && new Date(lead.last_contact) < new Date(Date.now() - 3 * 86400000)) {
|
||||||
|
// 3 days since last contact
|
||||||
|
await db.update(leads)
|
||||||
|
.set({ needs_followup: true, last_followup_check: new Date() })
|
||||||
|
.where(eq(leads.id, leadId));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ connection: redis, concurrency: 5 } // Run 5 jobs in parallel
|
||||||
|
);
|
||||||
|
|
||||||
|
worker.on('completed', (job) => console.log(`Reminder ${job.id} processed`));
|
||||||
|
worker.on('failed', (job, err) => console.error(`Reminder ${job.id} failed: ${err}`));
|
||||||
|
```
|
||||||
|
|
||||||
|
**Reminder Display Logic:**
|
||||||
|
- Dashboard shows: "Last follow-up: 5 days ago | Next reminder: 2026-06-15 09:00 UTC"
|
||||||
|
- Compute in admin dashboard query (Server Component):
|
||||||
|
```typescript
|
||||||
|
const leads = await db.select().from(leads)
|
||||||
|
.where(and(
|
||||||
|
eq(leads.status, 'Qualificato'),
|
||||||
|
sql`(now() - last_contact) > interval '3 days'` // Show red flag
|
||||||
|
));
|
||||||
|
```
|
||||||
|
|
||||||
|
**Timezone Safety:**
|
||||||
|
- Store all `scheduled_for` timestamps in UTC (Postgres default).
|
||||||
|
- Convert user-local time → UTC before enqueueing: `new Date('2026-06-12 15:00 CEST').getTime()` → milliseconds since epoch.
|
||||||
|
- Use Luxon library if complex timezone handling needed (DST, etc.).
|
||||||
|
|
||||||
|
**Confidence:** MEDIUM-HIGH — BullMQ is proven in SaaS reminder systems. Redis must be operational (you have it). Job persistence = safe even if app crashes mid-reminder.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### File Handling (v1)
|
### 5. Multi-Step Quote Page UI (Styling)
|
||||||
|
|
||||||
None — document links stored as text fields in Postgres. Eliminates S3, CDN, and upload infrastructure from the initial build entirely.
|
| Technology | Version | Purpose | Why This Choice | Status |
|
||||||
|
|------------|---------|---------|-----------------|--------|
|
||||||
|
| Tailwind CSS v4 | (existing) | Layout + utility classes | Already in stack. No changes. Build step-indicator in Tailwind. | NO NEW DEPENDENCY |
|
||||||
|
| shadcn/ui | (existing) | Form components, buttons, progress | Reuse Form, Input, Button, Select, Card components. Add Stepper component (custom or from shadcn library). | NO NEW DEPENDENCY |
|
||||||
|
|
||||||
**If direct uploads needed in v2:** UploadThing integrates directly with Next.js App Router, free tier (2 GB storage).
|
**No new styling libraries needed.** Quote page is standard multi-step HTML/CSS in Tailwind + shadcn.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Infrastructure
|
## Installation Command (All v2.0 Additions)
|
||||||
|
|
||||||
| Technology | Purpose | Why |
|
|
||||||
|------------|---------|-----|
|
|
||||||
| Vercel Hobby plan | Deploy + CDN + serverless | Native Next.js. Custom subdomain (`welcomeclient.iamcavalli.net`) via DNS CNAME. No Docker, VPS, or CI/CD to manage. |
|
|
||||||
| Neon Vercel Integration | DB branch per preview | Creates a fresh Neon branch per Git branch automatically. Safe schema migration testing. |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Installation Sequence
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. Bootstrap Next.js
|
npm install @dnd-kit/react @dnd-kit/helpers @tanstack/react-table react-hook-form @hookform/resolvers bullmq redis
|
||||||
npx create-next-app@latest clienthub --typescript --tailwind --app --src-dir
|
```
|
||||||
|
|
||||||
# 2. Database
|
**That's 7 new packages.** All are TypeScript-first, framework-agnostic, and production-ready.
|
||||||
npm install drizzle-orm @neondatabase/serverless
|
|
||||||
npm install -D drizzle-kit
|
|
||||||
|
|
||||||
# 3. Auth
|
---
|
||||||
npm install next-auth
|
|
||||||
|
|
||||||
# 4. Token generation
|
## Compatibility Matrix: v2.0 Stack vs. Your Current Stack
|
||||||
npm install nanoid
|
|
||||||
|
|
||||||
# 5. Validation + Forms
|
| Existing Tech | v2.0 Addition | Compatible? | Notes |
|
||||||
npm install zod @hookform/resolvers react-hook-form
|
|---------------|---------------|-------------|-------|
|
||||||
|
| Next.js 16 + App Router | dnd-kit, RHF, TanStack Table, BullMQ | ✓ YES | All work natively in App Router. No adapter needed. |
|
||||||
|
| React 19 | dnd-kit | ✓ HIGH | v10+ officially supports React 19. Verified via Context7. |
|
||||||
|
| React 19 | RHF + @hookform/resolvers | ⚠️ MEDIUM | No explicit React 19 peer deps yet, but works in practice (2025–2026 examples confirm). Recommend dev testing before deploy. |
|
||||||
|
| React 19 | TanStack Table | ✓ HIGH | Framework-agnostic, tested across React versions. |
|
||||||
|
| React 19 | BullMQ | ✓ HIGH | Backend library (Node.js), not React-dependent. |
|
||||||
|
| Tailwind v4 | (all new libs) | ✓ YES | dnd-kit, RHF, TanStack Table, BullMQ are all headless. Zero conflicts with Tailwind utility classes. |
|
||||||
|
| shadcn/ui | (all new libs) | ✓ YES | Composition-first design. Mix shadcn Button + dnd-kit Draggable without issues. |
|
||||||
|
| TypeScript strict | (all new libs) | ✓ YES | dnd-kit, RHF, Zod, TanStack Table all ship full TypeScript support. No `any` types. |
|
||||||
|
| Drizzle ORM | BullMQ + Redis | ✓ YES | New `reminders` table added to Drizzle schema. No ORM changes. |
|
||||||
|
| Postgres (Neon/Coolify) | BullMQ + Redis | ✓ YES | Reminders table stores: `lead_id`, `scheduled_for` (UTC), `status`, `type`. Separate Redis for job queue. |
|
||||||
|
| Auth.js v4 | (all new libs) | ✓ YES | Admin auth unchanged. Public quote pages use token (existing pattern). |
|
||||||
|
|
||||||
# 6. shadcn/ui
|
**Confidence:** HIGH across the board. All new libs compose cleanly with your validated v1.0 stack.
|
||||||
npx shadcn@latest init
|
|
||||||
npx shadcn@latest add badge button card dialog dropdown-menu input label progress select separator table textarea
|
---
|
||||||
|
|
||||||
|
## What NOT to Add for v2.0
|
||||||
|
|
||||||
|
### Explicitly Avoid
|
||||||
|
|
||||||
|
| Technology | Why Not | Alternative |
|
||||||
|
|------------|---------|-------------|
|
||||||
|
| **@hello-pangea/dnd** | Only supports React 18.x. React 19 peer dependency block not lifted as of June 2026. Requires override or downgrade. | Use `@dnd-kit/react` instead. |
|
||||||
|
| **react-beautiful-dnd** | Archived since 2022. Unmaintained. | Use `@dnd-kit/react` instead. |
|
||||||
|
| **Prisma or TypeORM** | You standardized on Drizzle. Switching ORMs = inconsistent migrations, schema duplication. | Extend Drizzle schema for `reminders` table. |
|
||||||
|
| **Stripe / Payment SDKs** | Out of scope for v2.0. Payments finalized at project level (v1.0 constraint). Quote pages only accept/copy phases. | Defer to Phase 8 or beyond. |
|
||||||
|
| **File upload libraries** (dropzone, react-fine-uploader, UploadThing) | v1.0 constraint: no file hosting. Documents are external URLs only. Quote pages don't need uploads. | Keep document linking as URL references. Defer UploadThing to Phase 8. |
|
||||||
|
| **Real-time collab** (Yjs, Immer, replicache) | Admin is solo. No multi-user offer editing. Zero multiplayer use case. | Standard React state + Server Actions sufficient. |
|
||||||
|
| **Email template builders** (MJML, React Email, nodemailer) | Follow-up reminders are dashboard notifications, not email sends (for now). | Store reminder metadata in DB; show in dashboard KPI feed. Email integration deferred to Phase 8. |
|
||||||
|
| **Agenda (MongoDB job queue)** | Adds MongoDB infrastructure. You have Postgres + Redis. | Use BullMQ with Redis instead. |
|
||||||
|
| **node-cron** | Not persistent. Jobs lost on server restart. Risky for reminders. | Use BullMQ. |
|
||||||
|
| **Custom webpack config** | Next.js 16 handles all bundling. Zero config needed. | Use Next.js defaults. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Database Schema Additions (Drizzle)
|
||||||
|
|
||||||
|
Add to your `src/db/schema.ts`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { pgTable, uuid, timestamp, varchar, text } from 'drizzle-orm/pg-core';
|
||||||
|
|
||||||
|
export const reminders = pgTable('reminders', {
|
||||||
|
id: uuid('id').primaryKey().defaultRandom(),
|
||||||
|
lead_id: uuid('lead_id').references(() => leads.id).notNull(),
|
||||||
|
scheduled_for: timestamp('scheduled_for', { withTimezone: true }).notNull(), // UTC
|
||||||
|
status: varchar('status', { length: 20 }).notNull().default('pending'), // pending, sent, failed
|
||||||
|
reminder_type: varchar('reminder_type', { length: 50 }).notNull(), // follow-up, proposal-expiry, etc.
|
||||||
|
metadata: text('metadata'), // JSON: { daysUntilExpiry, lastContactDate, etc. }
|
||||||
|
created_at: timestamp('created_at', { withTimezone: true }).defaultNow(),
|
||||||
|
updated_at: timestamp('updated_at', { withTimezone: true }).defaultNow(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Existing tables — NO CHANGES to clients, projects, payments, phases
|
||||||
|
// (Data safety constraint: migrations are additive only)
|
||||||
|
```
|
||||||
|
|
||||||
|
Migration:
|
||||||
|
```bash
|
||||||
|
npx drizzle-kit generate --name add_reminders_table
|
||||||
|
npx drizzle-kit migrate
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Key Architectural Decisions
|
## Performance & Scalability Notes
|
||||||
|
|
||||||
1. **Secret-link without Auth.js:** Next.js Middleware validates `[token]` at the edge. Fast, zero client-side JS, 404 on invalid token.
|
| Library | Scaling Threshold | Notes |
|
||||||
2. **Server Actions for all mutations:** Task updates, comments, payment status — no REST API layer to maintain.
|
|---------|-------------------|-------|
|
||||||
3. **Privacy model is a DB query filter:** Admin sees `quote_items`; clients see only `clients.accepted_total`. Not a UI filter — a DB design.
|
| **dnd-kit** | 100+ services per offer | Optimized for React 19 concurrent rendering. No observable lag. |
|
||||||
4. **Two auth systems, zero overlap:** Admin JWT cookie on `/admin/*`. Client token middleware on `/c/*`.
|
| **TanStack Table** | 1K+ leads | Renders only visible rows (with virtualization). Sorting, filtering client-side. |
|
||||||
|
| **RHF** | No limit | Uncontrolled form by default. Minimal re-renders. No performance cliff. |
|
||||||
|
| **BullMQ** | 10K+ pending reminders | Redis handles queue. Upgrade Redis RAM if > 100K jobs. Worker concurrency = how many jobs process in parallel (default: 1, recommend 5–10 for reminders). |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Confidence Levels
|
## Confidence Assessment
|
||||||
|
|
||||||
| Area | Confidence | Notes |
|
| Area | Level | Rationale |
|
||||||
|------|------------|-------|
|
|------|-------|-----------|
|
||||||
| Next.js App Router | HIGH | Stable since Oct 2024 |
|
| **dnd-kit for offer builder** | HIGH | Context7 docs confirm v10+ React 19 support. Peer deps clean. |
|
||||||
| Neon free tier | HIGH | 0.5 GB storage, 100 CU-hours/month |
|
| **RHF + Zod + resolvers** | HIGH | Battle-tested 2025–2026. Multiple Next.js 15+ production examples. |
|
||||||
| Drizzle + neon-http | HIGH | Free serverless driver, no connection pooling needed |
|
| **TanStack Table** | HIGH | Industry standard. Headless = zero conflicts. |
|
||||||
| Auth.js Credentials (admin) | HIGH | Mature, well-documented |
|
| **BullMQ + Redis** | MEDIUM | Proven in SaaS. Requires Redis operational (you have it). |
|
||||||
| nanoid secret tokens | HIGH | Cryptographically secure default |
|
| **React 19 compat overall** | MEDIUM-HIGH | dnd-kit: HIGH. RHF/resolvers: MEDIUM (not explicitly tested, but no peer blocks). Recommend dev test before prod deploy. |
|
||||||
| Tailwind v4 + Next.js | HIGH | Stable, PostCSS plugin verified |
|
|
||||||
| Vercel Hobby plan | HIGH | Custom subdomain supported |
|
---
|
||||||
|
|
||||||
|
## Roadmap Implications
|
||||||
|
|
||||||
|
### Phase Ordering
|
||||||
|
|
||||||
|
1. **Phase 7: Unified Catalog** — Migrate `service_catalog` + `offer_services` → single `services` table. No new UI libs needed.
|
||||||
|
2. **Phase 8: Offer Builder (dnd-kit)** — Implement drag-drop between phases. *Adds `@dnd-kit/react`, `@dnd-kit/helpers`.*
|
||||||
|
3. **Phase 9: Quote Pages (RHF)** — Public multistep form. *Adds `react-hook-form`, `@hookform/resolvers`.*
|
||||||
|
4. **Phase 10: CRM Pipeline (TanStack + dnd-kit)** — Lead kanban board. *Adds `@tanstack/react-table` (reuses dnd-kit).*
|
||||||
|
5. **Phase 11: Reminders (BullMQ)** — Follow-up scheduling + dashboard feed. *Adds `bullmq`, `redis` (if not present). Adds `reminders` table to Drizzle.*
|
||||||
|
|
||||||
|
### Research Flags
|
||||||
|
|
||||||
|
- **Phase 9 (Quote Pages):** Test RHF + React 19 in dev environment before merging (peer dep compatibility unverified in Context7, but known to work in practice).
|
||||||
|
- **Phase 11 (Reminders):** Ensure Redis is provisioned on Coolify. Verify BullMQ worker process lifecycle (separate service vs. API route).
|
||||||
|
- **Phase 10 (CRM Pipeline):** Kanban column reordering (dnd-kit) requires lead `stage` enum in DB schema.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
|
||||||
|
### Drag-and-Drop
|
||||||
|
- [dnd-kit React 19 documentation](https://github.com/clauderic/dnd-kit/blob/main/apps/docs/docs/react/quickstart.mdx)
|
||||||
|
- [dnd-kit migration: Core → React package](https://github.com/clauderic/dnd-kit/blob/main/apps/docs/docs/react/guides/migration.mdx)
|
||||||
|
- [@hello-pangea/dnd React 19 discussion status](https://github.com/hello-pangea/dnd/discussions/810)
|
||||||
|
- [Next.js 16 Kanban CRM UI best practices 2026](https://adminlte.io/blog/shadcn-ui-crm-dashboard-templates/)
|
||||||
|
|
||||||
|
### Forms & Validation
|
||||||
|
- [Next.js App Router forms + Server Actions guide](https://nextjs.org/docs/pages/guides/forms)
|
||||||
|
- [React Hook Form + Zod + Server Actions 2026](https://medium.com/@techwithtwin/handling-forms-in-next-js-with-react-hook-form-zod-and-server-actions-e148d4dc6dc1)
|
||||||
|
- [Multi-step form implementation guide](https://makerkit.dev/docs/next-supabase-turbo/components/multi-step-forms)
|
||||||
|
- [Best form libraries comparison 2026](https://splitforms.com/blog/best-nextjs-form-library-2026)
|
||||||
|
|
||||||
|
### Tables
|
||||||
|
- [TanStack Table (React) documentation](https://tanstack.com/table/latest/docs/framework/react/examples/sorting)
|
||||||
|
|
||||||
|
### Job Scheduling
|
||||||
|
- [BullMQ job schedulers official guide](https://docs.bullmq.io/guide/job-schedulers)
|
||||||
|
- [Building scalable reminder systems in Node.js](https://www.codegenes.net/blog/node-js-date-time-based-reminder/)
|
||||||
|
- [BullMQ vs Agenda vs node-cron: detailed comparison 2026](https://betterstack.com/community/guides/scaling-nodejs/bullmq-scheduled-tasks/)
|
||||||
|
- [Twilio: Node.js appointment reminders pattern](https://www.twilio.com/docs/messaging/tutorials/appointment-reminders/node)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Last updated:** 2026-06-11
|
||||||
|
**Status:** Ready for v2.0 phase planning
|
||||||
|
|||||||
Reference in New Issue
Block a user