Architecture
ADR 0005 — Internal apps frontend (CRM & client portal)
- Status: Accepted
- Date: 2026-06-10
Context
The platform needs polished, cohesive internal web apps: first the team CRM, later the client portal. Earlier attempts assembled the UI from component kits (shadcn/ui primitives, Tailwind Plus Catalyst, Tailwind Plus Application UI). Hand-assembling produced an inconsistent, unfinished look and slow progress — a component kit is raw building blocks, not a finished application.
The documentation site proved the opposite approach: adopting a complete, professionally designed template (Tailwind Plus Syntax) gave an excellent result immediately. We apply the same principle to the apps.
Decision
Adopt Shadcn UI Kit — Dashboard (shadcnuikit.com) as the foundation for apps/crm (and the future portal):
- Stack: Next.js 16, React 19, Tailwind v4, shadcn/ui — identical to our app stack, so there is zero migration friction.
- License: one-time commercial (Pro tier), source included, self-hostable, unlimited projects, no feature-gating — consistent with our self-hosted, subscription-free philosophy.
- Ships a CRM dashboard plus the app surfaces we need (Kanban for the pipeline, Mail/Chat, Calendar, Tasks, Notes) for both the CRM and the portal.
Branding (locked)
- Theme locked to the Ocean Breeze preset (the closest match to brand blue
#467df6) withmdradius; the theme customizer is removed so the look stays on-brand for every user. - Satoshi (self-hosted, official) as the typeface; official Feedback Studios logo; "Feedback OS" wordmark.
- Removed vendor artifacts: the
assetPrefixpointing at the vendor domain (which broke asset loading in production), the "Get Pro" link, demo projects, and the external analytics script.
Authentication
Server-side session, reusing the backend (Payload) auth:
- Login posts to
PAYLOAD_URL/api/users/login; on success the JWT is stored in an httpOnly cookie namedcrm_token. lib/payload.tsforwards the JWT asAuthorization: JWT <token>on server-side fetches to the backend.proxy.ts(Next.js 16's renamed middleware) guards everything under/dashboard/*, redirects/to/dashboard/crm, and gates unauthenticated users to/dashboard/login/v1.- The current user is fetched via
/api/users/meand shown in the sidebar.
Structure
app/dashboard/(auth)/*— the gated application (CRM dashboard, companies, contacts, deals, services, pipeline, workspace apps).app/dashboard/(guest)/*— login / register / forgot-password.- Sidebar trimmed to CRM (Dashboard, Companies, Contacts, Deals, Pipeline, Services) and Workspace (Mail, Chat, Calendar, Tasks, Notes).
Consequences
- Fast, cohesive, on-brand result; the design work is done by the template authors.
- We stay within the template's shadcn/ui conventions — a constraint, not a limitation.
- Data is wired to the Payload backend via
payloadFetch; the list pages (companies/contacts/deals/services) start as placeholders and are connected to the backend in a follow-up phase. - The same foundation will host the client portal.