CRM Build Spec · crm-v1.0
Complete spec for the CRM Foundation — Next.js app scaffold, auth via Setpoint DB, facility selector, CRM sidebar, customer directory, contact detail, staff notes, colour tags, and deploy to Vercel. Interface matches PMS admin exactly.
Stack
app
Next.js 16 (App Router, Turbopack) — mirrors apps/admin
styling
Tailwind CSS 4 + shadcn/ui — same globals.css tokens as PMS
auth
Supabase Auth — same auth.users table, signInWithPassword
database
Supabase Postgres — same DB (utommwznukufegwhltqw)
packages
@setpoint/database, @setpoint/common
deployment
Vercel — project crm-setpoint, root directory apps/crm
node
24.x (matches all other Setpoint apps)
Structure
Interface
Email + password login for CRM staff. Identical layout to PMS admin login.
Layout
Full-screen centered card (max-w-sm)
UI Elements
PMS Equivalent
apps/admin/src/app/auth/login/page.tsx
Data Sources
Logged-in users see all their facilities. Auto-redirects to /[facility]/contacts if only one. Identical layout to PMS admin home.
Layout
Full-screen centered container (max-w-2xl)
UI Elements
PMS Equivalent
apps/admin/src/app/page.tsx
Data Sources
Note
Stats on facility card are CRM-relevant (contacts, new this month) not PMS ones (today's bookings, revenue).
Collapsible icon sidebar — identical structure to PMS admin sidebar. w-16 collapsed, w-48 on hover.
Layout
sticky h-screen flex-col border-r bg-sidebar
UI Elements
PMS Equivalent
apps/admin/src/components/admin/admin-sidebar.tsx
Data Sources
Note
CRM sidebar uses BarChart3 for Dashboard instead of Calendar. Otherwise identical interaction pattern.
CRM overview with 4 KPI cards and a recent contacts preview table.
Layout
p-6, flex flex-col gap-6
UI Elements
PMS Equivalent
apps/admin/src/app/[facility]/reports/page.tsx (card pattern)
Data Sources
Full customer directory — search, filter by status, sortable table with aggregated stats.
Layout
p-6, flex flex-col gap-6
UI Elements
PMS Equivalent
apps/admin/src/app/[facility]/customers/page.tsx
Data Sources
Note
The contacts page replaces 'Customers' in PMS. Same table pattern but adds Tags column and richer stats.
Full customer profile — identity, aggregated stats, recent history, staff notes, and tags.
Layout
p-6, grid gap-6 lg:grid-cols-[1fr_340px]
UI Elements
Data Sources
Note
Two-column layout on desktop (lg). Single column on mobile. Notes panel is always visible (no tab required for v1.0).
Placeholder page — ships as empty state in v1.0, implemented in v1.2.
Layout
p-6, flex flex-col gap-6
UI Elements
Placeholder page — ships in v1.3.
Layout
p-6
UI Elements
Placeholder page — ships in v1.4.
Layout
p-6
UI Elements
Database
All three tables use the crm_ prefix and are facility-scoped with RLS using the existing user_facility_role() helper function. Existing tables (customers, facilities, bookings, pos_orders, memberships) are read-only from the CRM.
Private staff notes attached to a customer profile at a specific facility. Visible only to facility staff.
DDL
CREATE TABLE crm_notes ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), facility_id UUID NOT NULL REFERENCES facilities(id) ON DELETE CASCADE, profile_id UUID NOT NULL REFERENCES customers(id) ON DELETE CASCADE, content TEXT NOT NULL CHECK (char_length(content) <= 2000), created_by UUID NOT NULL REFERENCES customers(id), created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() );
RLS Policies
Indexes
Tag definitions per facility. Name must be unique within a facility. Colour is a hex string for the chip background.
DDL
CREATE TABLE crm_tags ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), facility_id UUID NOT NULL REFERENCES facilities(id) ON DELETE CASCADE, name TEXT NOT NULL CHECK (char_length(name) BETWEEN 1 AND 30), colour TEXT NOT NULL DEFAULT '#6B7280', created_at TIMESTAMPTZ NOT NULL DEFAULT now(), UNIQUE (facility_id, name) );
RLS Policies
Indexes
Join table assigning tags to customers within a facility. One row per tag+customer+facility combination.
DDL
CREATE TABLE crm_customer_tags ( tag_id UUID NOT NULL REFERENCES crm_tags(id) ON DELETE CASCADE, profile_id UUID NOT NULL REFERENCES customers(id) ON DELETE CASCADE, facility_id UUID NOT NULL REFERENCES facilities(id) ON DELETE CASCADE, assigned_at TIMESTAMPTZ NOT NULL DEFAULT now(), assigned_by UUID REFERENCES customers(id), PRIMARY KEY (tag_id, profile_id) );
RLS Policies
Indexes
API
Return all facilities the current user has a role at (for sidebar switcher)
Return 4 KPI metrics + 10 most recent contacts for the dashboard
Paginated, searchable, sortable contacts list with aggregated stats
Full contact profile, stats, recent bookings, recent POS orders, membership, notes, tags
Create a new staff note for the contact
Delete a note. Only the author or owner/admin can delete.
Return all tag definitions for a facility (for tag picker popover)
Create a new tag definition for the facility
Assign an existing tag to a contact
Remove a tag from a contact
Build Plan
feat(crm): scaffold Next.js 16 app with design system
feat(crm): auth middleware and login page
feat(crm): facility selector and CRM sidebar navigation
feat(crm): database migration — crm_notes, crm_tags, crm_customer_tags
feat(crm): contacts list with search, filter, and sortable table
feat(crm): contact detail page with notes and tags
feat(crm): dashboard KPIs and placeholder pages
chore: deploy CRM v1.0 to Vercel (crm-setpoint)
Seed Data
QA