Architecture
Stack overview
OpsDash is a production-ready, multi-tenant B2B platform built on Next.js 15, Supabase, and Stripe. It implements the complete Lead → Deal → Project → Profitability loop in a single, privately deployable codebase.
Stack
| Layer | Technology |
|---|---|
| Framework | Next.js 15 — App Router, React Server Components, Server Actions |
| Language | TypeScript 5.7 |
| Database | Supabase — PostgreSQL 15 + Auth + Realtime + Storage |
| Styling | Tailwind CSS 3 + Radix UI (@opsdash/ui) |
| Billing | Stripe — subscriptions, entitlements, webhooks |
| i18n | next-intl |
| Validation | Zod |
| Monorepo | Turborepo + pnpm workspaces |
Monorepo structure
/
apps/
web/ # Next.js app — the main product
docs/ # Storybook component docs
packages/
@opsdash/ui # Design system (Radix, TanStack Table, CVA)
@opsdash/database # Supabase client wrappers
@opsdash/auth # Auth helpers
@opsdash/utils # formatDate, clsx, Zod, PDF export
@opsdash/i18n # next-intl wrapper
@opsdash/config # Feature defaults, plan limits, config types
@opsdash/crm # CRM UI components
@opsdash/projects # Projects UI components
@opsdash/forms # Form builder UI
@opsdash/billing # Stripe + billing UI
@opsdash/analytics # Charts (recharts) + analytics UI
@opsdash/ai # AI-related UI components
supabase/
migrations/ # 57 SQL migration files (additive only)
opsdash.config.ts # Buyer entry pointRoute structure
All authenticated pages sit under /[locale]/org/[slug]/. Locale and org slug are resolved by middleware on every request.
/[locale]/
org/[slug]/
dashboard/ # KPIs and activity feed
analytics/ # Charts and trends
reports/ # Pipeline, deal conversion, forecasting
crm/
contacts/ # Contact list + detail
companies/ # Company list + detail
leads/ # Lead list + detail
deals/ # Pipeline board + deal detail
activities/ # CRM activity feed
automation/ # Workflow triggers
export/ # Data export
data-cleanup/ # Go-live checklist + duplicate merge (see file-map.md → crm/data-cleanup/)
projects/ # Project list
projects/[id]/ # Kanban, calendar, gantt, team
support/ # Ticket list + detail
forms/ # Form builder
files/ # File manager
billing/ # Subscription management
settings/ # Org settings
account/ # Profile, 2FA, billing anchor
admin/ # Platform admin (PLATFORM_ADMIN_EMAILS only)Backend: Server Actions
There is no traditional REST API for product data. All CRUD goes through Next.js Server Actions in apps/web/src/app/actions/. The security pattern applied to every action:
const { supabase, org, user } = await withOrg(slug);
// withOrg() verifies auth, resolves org, checks membership — throws on failure
const parsed = schema.safeParse(input);
if (!parsed.success) return { success: false, error: 'validation_error' };
// ... data operation ...
await createAuditLog(supabase, org.id, user.id, 'entity.action', { ... });
return { success: true, data };Data flow
- Browser requests a page → React Server Component renders server-side
- RSC calls a Server Action to fetch data →
withOrg()validates auth and org membership - Supabase Row-Level Security enforces
org_idat the DB layer — even buggy server actions cannot leak cross-org data - For mutations: Client Component calls a Server Action → same
withOrg()+ Zod validation → DB write + audit log
Feature access stack
Every protected feature passes through four layers before data is returned:
- Entitlement check — is the feature included in the org's Stripe plan?
- Subscription status — must be
activeortrialing - Feature flag —
feature_flagstable — is it enabled for this org? - Role check — is the user's role in
feature_flags.allowed_roles?
| Function | Location | Purpose |
|---|---|---|
| canAccessFeature() | org-context.tsx | Client: sidebar, command palette, route guards |
| requireFeatureAccess() | admin.ts → admin-feature-gates.ts | Server actions: returns error if no access |
| ensureFeatureAccess() | admin.ts → admin-feature-gates.ts | Layouts: redirects to dashboard if no access |
| checkFeature() | admin.ts → admin-feature-gates.ts | Subscription/entitlement check only (no role) |