Skip to content

Project Structure - ThriveSend B2B2G

Last Updated: October 22, 2025


ThriveSend B2B2G follows Next.js 15 App Router conventions with a clear separation of concerns. The project is organized into distinct layers: presentation (app), components, business logic (lib/services), and data access (Prisma).


thrive-send-b2b2g/
├── src/ # Source code
├── prisma/ # Database schema and migrations
├── public/ # Static assets
├── docs-new/ # Documentation (this site)
├── e2e/ # Playwright end-to-end tests
├── scripts/ # Utility scripts
├── .github/ # GitHub Actions CI/CD
├── .husky/ # Git hooks
├── package.json # Project dependencies
├── next.config.mjs # Next.js configuration
├── tailwind.config.ts # Tailwind CSS configuration
├── tsconfig.json # TypeScript configuration
└── mkdocs.yml # Documentation site configuration

src/
├── app/ # Next.js 15 App Router
│ ├── api/ # API Routes (92 endpoints)
│ ├── (dashboard)/ # Dashboard pages (13 pages)
│ ├── onboarding/ # Onboarding flow (4 pages)
│ ├── invitations/ # Invitation acceptance
│ ├── sign-in/ # Clerk authentication
│ └── sign-up/ # Clerk registration
├── components/ # React components (96+)
│ ├── ui/ # Base UI components (46)
│ ├── dashboard/ # Dashboard components (29)
│ ├── analytics/ # Analytics components (6)
│ ├── layout/ # Layout components (3)
│ ├── auth/ # Auth components
│ ├── compliance/ # Compliance components
│ ├── consent/ # Consent management
│ └── content/ # Content management
├── lib/ # Utilities and services
│ ├── services/ # Business logic (25 services)
│ ├── constants/ # Application constants
│ ├── middleware/ # Custom middleware
│ ├── templates/ # Email templates
│ ├── utils/ # Utility functions
│ └── db.ts # Prisma client
├── context/ # React Context providers
│ └── ServiceProviderContext.tsx
├── hooks/ # Custom React hooks
│ └── use-service-provider.ts
├── __tests__/ # Test files
│ ├── api/ # API tests
│ ├── compliance/ # Compliance tests
│ ├── integration/ # Integration tests
│ └── utils/ # Utility tests
└── middleware.ts # Next.js middleware (Clerk auth)

The app directory follows Next.js 15 App Router conventions.

All 92 REST API endpoints organized by domain:

src/app/api/
├── analytics/ # Analytics API (7 endpoints)
│ ├── route.ts # GET /api/analytics
│ ├── dashboard/ # Dashboard metrics
│ ├── performance/ # Performance data
│ └── compliance/ # Compliance metrics
├── campaigns/ # Campaign API (13 endpoints)
│ ├── route.ts # GET/POST /api/campaigns
│ ├── [campaignId]/ # Campaign CRUD
│ ├── bulk/ # Bulk operations
│ └── templates/ # Campaign templates
├── clients/ # Client API (7 endpoints)
│ ├── route.ts # GET/POST /api/clients
│ └── [clientId]/ # Client CRUD
├── compliance/ # Compliance API (4 endpoints)
│ ├── alerts/ # Compliance alerts
│ └── status/ # Compliance status
├── content/ # Content API (11 endpoints)
│ ├── route.ts # GET/POST /api/content
│ ├── [contentId]/ # Content CRUD
│ └── templates/ # Content templates
├── team/ # Team API (6 endpoints)
│ ├── route.ts # GET/POST /api/team
│ └── [memberId]/ # Member operations
├── service-provider/ # Service Provider API (9 endpoints)
│ ├── route.ts # GET/POST /api/service-provider
│ ├── organization/ # Organization CRUD
│ ├── billing/ # Billing management
│ ├── compliance/ # Compliance config
│ └── security/ # Security config
├── invitations/ # Invitation API (6 endpoints)
│ ├── route.ts # GET/POST /api/invitations
│ ├── accept/ # Accept invitation
│ └── resend/ # Resend invitation
├── audit/ # Audit Log API (3 endpoints)
├── consent-preferences/ # Consent API (3 endpoints)
├── data-subject-requests/ # POPIA DSR API (4 endpoints)
├── webhooks/ # Webhook handlers
│ ├── clerk/ # Clerk webhooks
│ └── email/ # Email webhooks (Resend)
├── health/ # Health check
│ └── route.ts # GET /api/health
└── __tests__/ # API tests

Screenshot Placeholder:

../../../assets/images/screenshots/dev-project-api-structure.png

Protected dashboard routes (requires authentication):

src/app/(dashboard)/
├── layout.tsx # Dashboard layout wrapper
├── dashboard/ # Main dashboard
│ └── page.tsx # /dashboard
├── clients/ # Client management
│ ├── page.tsx # /dashboard/clients
│ └── [clientId]/ # /dashboard/clients/[id]
│ └── page.tsx
├── campaigns/ # Campaign management
│ ├── page.tsx # /dashboard/campaigns
│ ├── new/ # /dashboard/campaigns/new
│ └── [campaignId]/ # /dashboard/campaigns/[id]
│ └── page.tsx
├── content/ # Content management
│ ├── page.tsx # /dashboard/content
│ └── [contentId]/ # /dashboard/content/[id]
│ └── page.tsx
├── analytics/ # Analytics dashboard
│ └── page.tsx # /dashboard/analytics
├── team/ # Team management
│ └── page.tsx # /dashboard/team
├── settings/ # Organization settings
│ ├── page.tsx # /dashboard/settings
│ ├── profile/ # Profile settings
│ ├── billing/ # Billing settings
│ ├── compliance/ # Compliance settings
│ └── security/ # Security settings
└── admin/ # Admin panel (admin only)
└── page.tsx # /dashboard/admin

First-time setup for service providers:

src/app/onboarding/
├── organization/ # Step 1: Create organization
│ └── page.tsx # /onboarding/organization
├── service-provider/ # Step 2: Service provider profile
│ └── page.tsx # /onboarding/service-provider
└── team/ # Step 3: Invite team members
└── page.tsx # /onboarding/team

46 reusable UI primitives built with Radix UI and shadcn/ui:

src/components/ui/
├── button.tsx # Button component
├── input.tsx # Text input
├── select.tsx # Select dropdown
├── dialog.tsx # Modal dialog
├── table.tsx # Data table
├── card.tsx # Card container
├── form.tsx # Form wrapper (react-hook-form)
├── badge.tsx # Badge/pill
├── avatar.tsx # User avatar
├── toast.tsx # Toast notifications (Sonner)
├── tabs.tsx # Tab navigation
├── accordion.tsx # Accordion/collapsible
├── dropdown-menu.tsx # Dropdown menu
├── alert.tsx # Alert messages
├── skeleton.tsx # Loading skeleton
└── ... (31 more components)

Code Example:

src/components/ui/button.tsx
import { cn } from "@/lib/utils"
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'default' | 'outline' | 'ghost'
size?: 'sm' | 'md' | 'lg'
}
export function Button({
variant = 'default',
size = 'md',
className,
...props
}: ButtonProps) {
return (
<button
className={cn(
"inline-flex items-center justify-center rounded-md",
{
'bg-primary text-primary-foreground': variant === 'default',
'border border-primary': variant === 'outline',
'h-8 px-3 text-sm': size === 'sm',
'h-10 px-4': size === 'md',
},
className
)}
{...props}
/>
)
}

Dashboard Components (src/components/dashboard/)

Section titled “Dashboard Components (src/components/dashboard/)”

29 feature-specific components for dashboard functionality:

src/components/dashboard/
├── analytics-dashboard.tsx # Main analytics view
├── metrics-card.tsx # KPI metrics card
├── campaign-list.tsx # Campaign list table
├── campaign-card.tsx # Campaign card
├── campaign-form.tsx # Campaign creation form
├── client-list.tsx # Client list table
├── client-card.tsx # Client card
├── client-form.tsx # Client creation form
├── content-editor.tsx # TipTap content editor
├── content-list.tsx # Content list
├── security-clearance-badge.tsx # Security clearance indicator
├── sector-badge.tsx # Government/Business badge
├── approval-flow-status.tsx # Content approval status
├── team-member-list.tsx # Team members table
├── invite-form.tsx # Team invitation form
└── ... (14 more components)

Code Example:

src/components/dashboard/client-card.tsx
'use client'
import { Card } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
import { SecurityClearanceBadge } from './security-clearance-badge'
import type { Client } from '@prisma/client'
interface ClientCardProps {
client: Client
onEdit?: (client: Client) => void
}
export function ClientCard({ client, onEdit }: ClientCardProps) {
return (
<Card className="p-6">
<div className="flex items-start justify-between">
<div>
<h3 className="text-lg font-semibold">{client.name}</h3>
<div className="mt-2 flex gap-2">
<Badge variant={client.sector === 'GOVERNMENT' ? 'destructive' : 'success'}>
{client.sector}
</Badge>
<SecurityClearanceBadge clearance={client.securityClearance} />
</div>
</div>
{onEdit && (
<Button variant="ghost" size="sm" onClick={() => onEdit(client)}>
Edit
</Button>
)}
</div>
</Card>
)
}

Screenshot Placeholder:

../../../assets/images/screenshots/dev-component-client-card.png

25 service modules containing business logic:

src/lib/services/
├── analytics.service.ts # Analytics and reporting
├── campaign.service.ts # Campaign management
├── client.service.ts # Client operations
├── compliance.service.ts # POPIA compliance
├── content.service.ts # Content management
├── team.service.ts # Team member management
├── auth.service.ts # Authentication helpers
├── audit.service.ts # Audit logging
├── consent.service.ts # Consent management
├── email.service.ts # Email sending (Resend)
├── invitation.service.ts # Invitation system
├── organization.service.ts # Organization management
├── reports/ # Report generation
│ ├── analytics-reports.ts
│ ├── compliance-reports.ts
│ └── campaign-reports.ts
└── __tests__/ # Service tests
├── client.service.test.ts
└── compliance.service.test.ts

Code Example:

src/lib/services/client.service.ts
import { prisma } from '@/lib/db'
import { auditLog } from './audit.service'
import type { Client, ClientSector, SecurityClearance } from '@prisma/client'
export const clientService = {
/**
* Create a new client with POPIA audit logging
*/
async create(data: CreateClientInput, userId: string): Promise<Client> {
const client = await prisma.client.create({
data: {
name: data.name,
sector: data.sector,
securityClearance: data.securityClearance,
organizationId: data.organizationId,
contactInfo: data.contactInfo,
},
})
// POPIA audit log
await auditLog({
organizationId: data.organizationId,
userId,
action: 'CLIENT_CREATED',
resource: 'Client',
resourceId: client.id,
details: { clientName: client.name, sector: client.sector },
})
return client
},
/**
* Get clients by sector with security clearance filtering
*/
async getBySector(
organizationId: string,
sector: ClientSector,
userClearance: SecurityClearance
): Promise<Client[]> {
return prisma.client.findMany({
where: {
organizationId,
sector,
securityClearance: {
lte: userClearance, // User can only see clients within their clearance
},
},
orderBy: { createdAt: 'desc' },
})
},
}

Prisma schema and migrations:

prisma/
├── schema.prisma # Database schema (28 models)
├── migrations/ # Database migration files
│ ├── 20250922_init/
│ ├── 20250923_clients/
│ ├── 20251015_campaigns/
│ └── ...
└── seed.ts # Database seeding script

Code Example:

// prisma/schema.prisma (excerpt)
model Client {
id String @id @default(cuid())
organizationId String
name String
sector ClientSector // GOVERNMENT or BUSINESS
clientType ClientType
securityClearance SecurityClearance // BASIC, ENHANCED, CONFIDENTIAL
contactInfo Json?
organization Organization @relation(fields: [organizationId], references: [id])
campaigns Campaign[]
projects Project[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([organizationId])
@@index([sector])
@@index([securityClearance])
}
enum ClientSector {
GOVERNMENT
BUSINESS
}
enum SecurityClearance {
BASIC
ENHANCED
CONFIDENTIAL
}

Next.js configuration:

/** @type {import('next').NextConfig} */
const nextConfig = {
typescript: {
ignoreBuildErrors: false,
},
eslint: {
ignoreDuringBuilds: false,
},
experimental: {
serverActions: {
allowedOrigins: ['localhost:3000', 'thrivesend.isutech.co.za'],
},
},
}
export default nextConfig

Tailwind CSS with semantic colors:

import type { Config } from 'tailwindcss'
const config: Config = {
content: ['./src/**/*.{ts,tsx}'],
theme: {
extend: {
colors: {
primary: 'hsl(var(--primary))',
secondary: 'hsl(var(--secondary))',
success: 'hsl(var(--success))',
warning: 'hsl(var(--warning))',
error: 'hsl(var(--error))',
},
},
},
}
export default config

  • React Components: PascalCase with .tsx extension
    • Example: ClientCard.tsx, CampaignForm.tsx
  • Server Components: Default export, no ‘use client’ directive
  • Client Components: Must include ‘use client’ directive
  • Route Handlers: route.ts (Next.js convention)
  • Dynamic Routes: [paramName]/route.ts
  • HTTP Methods: Named exports (GET, POST, PUT, DELETE)
  • Services: kebab-case.service.ts
    • Example: client.service.ts, analytics.service.ts
  • Unit Tests: *.test.ts or *.test.tsx
  • Integration Tests: *.integration.test.ts
  • E2E Tests: *.spec.ts (Playwright)

TypeScript path aliases for cleaner imports:

// tsconfig.json
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"],
"@/components/*": ["./src/components/*"],
"@/lib/*": ["./src/lib/*"],
"@/app/*": ["./src/app/*"]
}
}
}

Usage:

// Instead of:
import { Button } from '../../../components/ui/button'
// Use:
import { Button } from '@/components/ui/button'


Last Updated: October 22, 2025