Assist_Design/CLAUDE.md

5.7 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project Overview

Customer portal with BFF (Backend for Frontend) architecture. Users can self-register, manage subscriptions, view/pay invoices, and manage support cases.

Systems of Record:

  • WHMCS: Billing, subscriptions, invoices, authoritative address storage
  • Salesforce: CRM (Accounts, Contacts, Cases), order address snapshots
  • Portal: Next.js UI + NestJS BFF

Development Commands

# Start development environment (PostgreSQL + Redis via Docker)
pnpm dev:start

# Start both frontend and backend with hot reload
pnpm dev

# Build domain package (required before running apps if domain changed)
pnpm domain:build

# Type checking
pnpm type-check

# Linting
pnpm lint
pnpm lint:fix

# Database commands
pnpm db:migrate     # Run migrations
pnpm db:studio      # Open Prisma Studio GUI
pnpm db:generate    # Generate Prisma client

# Run tests
pnpm test           # All packages
pnpm --filter @customer-portal/bff test   # BFF only

# Stop services
pnpm dev:stop

Access points:

Architecture

Monorepo Structure

apps/
├── portal/          # Next.js 15 frontend (React 19, Tailwind, shadcn/ui)
└── bff/             # NestJS 11 backend (Prisma, BullMQ, Zod validation)

packages/
└── domain/          # Unified domain layer (contracts, schemas, provider mappers)

Three-Layer Boundary (Non-Negotiable)

Layer Location Purpose
Domain packages/domain/ Shared contracts, Zod validation, provider mappers. Framework-agnostic.
BFF apps/bff/ HTTP boundary, orchestration, external integrations (Salesforce/WHMCS/Freebit)
Portal apps/portal/ UI layer. Pages are thin wrappers over feature modules.

Domain Package Structure

Each domain module follows this pattern:

packages/domain/<module>/
├── contract.ts       # Normalized types (provider-agnostic)
├── schema.ts         # Zod validation schemas
├── index.ts          # Public exports
└── providers/        # Provider-specific adapters (BFF-only)
    └── whmcs/
        ├── raw.types.ts   # Raw API response types
        └── mapper.ts      # Transform raw → domain

Import Rules (ESLint Enforced)

Allowed (Portal + BFF):

import type { Invoice } from "@customer-portal/domain/billing";
import { invoiceSchema } from "@customer-portal/domain/billing";
import { Formatting } from "@customer-portal/domain/toolkit";

Allowed (BFF only):

import { Whmcs } from "@customer-portal/domain/billing/providers";

Forbidden everywhere:

// Root import
import { Billing } from "@customer-portal/domain";
// Deep imports beyond entrypoints
import { Invoice } from "@customer-portal/domain/billing/contract";
import { transformWhmcsInvoice } from "@customer-portal/domain/billing/providers/whmcs/mapper";

Forbidden in Portal:

// Portal must NEVER import provider adapters
import { Whmcs } from "@customer-portal/domain/billing/providers";

Portal Feature Architecture

apps/portal/src/
├── app/              # Next.js App Router (thin route shells, no API calls)
├── components/       # Atomic design: atoms/, molecules/, organisms/, templates/
├── core/             # App infrastructure: api/, logger/, providers/
├── features/         # Feature modules with: api/, stores/, components/, hooks/, views/
└── shared/           # Cross-feature: hooks/, utils/, constants/

Feature module pattern:

  • api/: Data fetching layer (built on shared apiClient)
  • stores/: Zustand state management
  • hooks/: React Query hooks wrapping API services
  • components/: Feature-specific UI
  • views/: Page-level view components
  • index.ts: Feature public API (barrel exports)

BFF Integration Pattern

Map Once, Use Everywhere:

External API → Integration Service → Domain Mapper → Domain Type → Use Directly

Integration services live in apps/bff/src/integrations/{provider}/:

  • services/: Connection services, entity-specific services
  • utils/: Query builders (SOQL, etc.)

Domain mappers live in packages/domain/{module}/providers/{provider}/:

  • Integration services fetch data and call domain mappers
  • No business logic in integration layer
  • No double transformation

Key Patterns

Validation (Zod-First)

  • Schemas live in domain: packages/domain/<module>/schema.ts
  • Derive types from schemas: export type X = z.infer<typeof xSchema>
  • Query params: use z.coerce.* for URL strings

BFF Controllers

  • Controllers are thin: no business logic, no Zod imports
  • Use createZodDto(schema) with global ZodValidationPipe
  • Integrations: build queries in utils, fetch data, transform via domain mappers

Logging

  • BFF: Use nestjs-pino Logger, inject via constructor
  • Portal: Use @/core/logger
  • No console.log in production code (ESLint enforced)

Naming

  • No any in public APIs
  • No console.log (use logger)
  • Avoid V2 suffix in service names

Documentation

Read before coding:

  • docs/README.md (entrypoint)
  • docs/development/ (BFF/Portal/Domain patterns)
  • docs/architecture/ (boundaries)
  • docs/integrations/ (external API details)

Rule: Never guess endpoint behavior or payload shapes. Find docs or existing implementation first.