Data Layer
Source of truth
| Data | Lives in | Import from |
|---|---|---|
| Projects | Supabase projects table | lib/projects-db.ts (server-only) |
| Leads / CRM | Supabase | lib/supabase.ts |
| Questionnaire templates | Supabase questionnaire_templates | lib/questionnaire-db.ts |
| Questionnaire responses | Supabase questionnaire_responses | lib/questionnaire-db.ts |
| Proposals | Supabase | lib/proposal-data.ts |
| Contracts | Supabase | lib/contract-data.ts |
| Invoices | Supabase | lib/invoice-data.ts |
| Work orders | Supabase | (actions/work-orders.ts) |
| Products | Static file | data/products.ts |
| Services | Static file | data/services.ts |
data/projects.tsis deleted. Any import of it will break the build.
Server / client split
lib/projects-db.ts— usesserver-only; never import in Client Componentslib/project-types.ts— types only, noserver-only; safe to import anywherelib/questionnaire-db.ts— server-only; use for all template CRUDlib/questionnaire-data.ts— legacy; onlygetResponseByTokenis still used
Supabase naming conventions
Supabase returns snake_case columns. All data-layer helpers map rows to camelCase TypeScript models before returning them.
// ✅ correct — use typed model fields
template.serviceType
template.createdAt
// ❌ wrong — raw DB column names
template.service_type
template.created_at
See the mapRow pattern in lib/questionnaire-db.ts as the reference implementation.
Project types
// lib/project-types.ts
export type Project = {
slug: string; title: string; category: string
tags: string[]; featured: boolean; thumb: string
videoUrl?: string; year: number; mediaType: 'image' | 'video'
description: string; client?: string; deliverables: string[]
industry?: string; challenge?: string; solution?: string; outcome?: string
processSteps?: ProcessStep[]; results?: Record<string, string>
testimonial?: string; testimonialAuthor?: string; gallery?: string[]
}
categoryLabels and categoryDotClass are also in lib/project-types.ts — import from there, never redeclare.
generateStaticParams
For Supabase-backed detail pages, pre-render slugs at build time:
export async function generateStaticParams() {
const projects = await getAllProjects({ status: 'published' });
return projects.map((p) => ({ category: p.category, slug: p.slug }));
}
Call notFound() when a slug isn't found at runtime.