Temuulen Ankhbayar 454fb29c85 fix: reset order config state when selecting a new plan and validate MNP phone length
Order wizard was skipping steps (jumping to add-ons) due to stale currentStep
persisting in localStorage from previous orders. Reset store on plan selection
and exclude currentStep from persistence. Also add max(11) validation on MNP
phone number to prevent Salesforce STRING_TOO_LONG errors.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 11:07:51 +09:00
2026-02-24 11:09:35 +09:00
2026-01-15 14:38:25 +09:00

Customer Portal Project

A modern customer portal where users can self-register, log in, browse & buy subscriptions, view/pay invoices, and manage support cases.

Architecture Overview

Systems of Record

  • WHMCS: Billing, subscriptions, invoices, and authoritative address storage
  • Salesforce: CRM (Accounts, Contacts, Cases) and order address snapshots
  • Portal: Modern UI with backend for frontend (BFF) architecture

Identity Management

  • Portal-native authentication (email + password, optional MFA)
  • One-time WHMCS user verification with forced password reset
  • User mapping: user_id ↔ whmcs_client_id ↔ sf_contact_id/sf_account_id

Tech Stack

Frontend (Portal UI)

  • Next.js 15 with App Router
  • Turbopack for ultra-fast development and builds
  • React 19 with TypeScript
  • Tailwind CSS with shadcn/ui components
  • TanStack Query for data fetching and caching
  • Zod for validation
  • React Hook Form for form management

Backend (BFF API)

  • NestJS 11 (Node 24 Current or 22 LTS)
  • Prisma 6 ORM with PostgreSQL 17
  • jsforce for Salesforce REST API integration
  • salesforce-pubsub-api-client for Salesforce Platform Events
  • p-queue for request throttling and queue management
  • WHMCS custom API client with comprehensive service layer
  • Freebit SIM management integration
  • Zod-first validation shared via the domain package
  • Bcrypt for password hashing

Queue Management

  • p-queue for intelligent request throttling to external APIs
  • Separate queues for Salesforce (standard + long-running) and WHMCS
  • Configurable concurrency, rate limiting, and timeout handling
  • Prevents API rate limit violations and resource exhaustion

Logging

  • Centralized structured logging via Pino using nestjs-pino in the BFF
  • Sensitive fields are redacted; each request has a correlation ID
  • Usage pattern in services:
    • Inject Logger from nestjs-pino: constructor(@Inject(Logger) private readonly logger: Logger) {}
    • Log with structured objects: this.logger.error('Message', { error })
    • See docs/LOGGING.md for full guidelines

Data & Infrastructure

  • PostgreSQL 17 for users, ID mappings, and optional mirrors
  • Redis 7 for cache, token blacklists, and rate limiting
  • Docker for local development (Postgres/Redis)

Project Structure

customer-portal/
├── apps/
│   ├── portal/            # Next.js 15 frontend (React 19, Tailwind, shadcn/ui)
│   └── bff/               # NestJS 11 backend (Prisma, p-queue, Zod validation)
├── packages/
│   ├── domain/            # Unified domain layer with contracts & schemas
│   ├── validation/        # Unified validation service (NestJS + React)
│   ├── logging/           # Centralized logging utilities (Pino)
│   └── integrations/      # External service integrations
│       ├── whmcs/         # WHMCS API client
│       └── freebit/       # Freebit SIM management
├── scripts/
│   ├── dev/               # Development management scripts
│   └── prod/              # Production deployment scripts
├── docker/
│   └── dev/               # Docker Compose for local development
│       └── docker-compose.yml  # PostgreSQL 17 + Redis 7
├── docs/                  # Comprehensive documentation
├── secrets/               # Private keys (git ignored)
├── env/                   # Environment file templates
├── package.json           # Root workspace configuration
├── pnpm-workspace.yaml    # pnpm workspace definition
└── README.md              # This file

Getting Started

Prerequisites

  • Node.js: Version 22 (LTS) or 24 (Current) - specified in package.json engines
  • pnpm: Version 10.0.0+ (managed via packageManager field)
  • Docker & Docker Compose: For local PostgreSQL and Redis services
  • Git: For version control

Quick Start (2 minutes)

  1. Clone and Install Dependencies

    git clone <repository-url>
    cd customer-portal
    pnpm install
    
  2. Setup Environment

    # Copy development environment template (if available)
    # Note: .env files may be filtered by .cursorignore
    # Contact your team for environment configuration
    
    # Edit with your values (most defaults work for local development)
    # Required: DATABASE_URL, REDIS_URL, JWT_SECRET
    # Optional for basic dev: WHMCS, Salesforce, Freebit credentials
    
  3. Start Development Environment

    # Start database and Redis services
    pnpm dev:start
    
    # In another terminal, start the applications with hot reload
    pnpm dev
    
  4. Access Your Applications

Development Commands

# === Daily Development ===
pnpm dev:start          # Start PostgreSQL + Redis services
pnpm dev                # Start both apps with hot reload
pnpm dev:stop           # Stop all services

# === Database Management ===
pnpm dev:migrate        # Run database migrations
pnpm db:studio          # Open Prisma Studio (database GUI)
pnpm dev:tools          # Start admin tools (Adminer + Redis Commander)

# === Utilities ===
pnpm dev:status         # Check service status
pnpm dev:logs           # View service logs
pnpm dev:reset          # Reset development environment
pnpm lint               # Run linting across all packages
pnpm type-check         # Run TypeScript checks

Build and Export Images (for Plesk upload)

# Frontend
docker build -t customer-portal-frontend:latest -f apps/portal/Dockerfile .
docker save -o customer-portal-frontend.latest.tar customer-portal-frontend:latest

# Backend
docker build -t customer-portal-backend:latest -f apps/bff/Dockerfile .
docker save -o customer-portal-backend.latest.tar customer-portal-backend:latest

Upload the tar files in Plesk → Docker → Images → Upload, then deploy using the appropriate compose stack configuration.

API Client

The portal uses TanStack Query with a lightweight fetch client that shares request/response contracts from @customer-portal/domain and validates them with Zod:

import { apiClient } from "@/lib/api-client";
import type { DashboardSummary } from "@customer-portal/domain/dashboard";

const { data: summary } = useQuery({
  queryKey: ["dashboard", "summary"],
  queryFn: () => apiClient.get<DashboardSummary>("/api/me/summary"),
});

Because the schemas and types live in the shared domain package there is no separate code generation step.

Environment Configuration

  • Local development: configure environment variables (contact team for template)
  • Docker services use defaults: PostgreSQL (dev/dev/portal_dev), Redis (no auth)
  • Plesk production: use split env files (no secrets under httpdocs)
    • Frontend: ensure NEXT_PUBLIC_API_BASE=/api
    • Backend: ensure TRUST_PROXY=true, DB uses database:5432, Redis uses cache:6379
    • See deployment documentation for full instructions

Key Environment Variables

Required environment variables (contact your team for specific values):

# === Application ===
NODE_ENV=development
BFF_PORT=4000
NEXT_PORT=3000

# === Database & Cache ===
DATABASE_URL=postgresql://dev:dev@localhost:5432/portal_dev?schema=public
REDIS_URL=redis://localhost:6379

# === Frontend (exposed to browser) ===
NEXT_PUBLIC_API_BASE=http://localhost:4000
NEXT_PUBLIC_APP_NAME=Customer Portal (Dev)
NEXT_PUBLIC_ENABLE_DEVTOOLS=true

# === Security ===
JWT_SECRET=<secure_secret_minimum_32_chars>
JWT_REFRESH_SECRET=<different_secure_secret_minimum_32_chars>
BCRYPT_ROUNDS=12

# === External APIs (required for full functionality) ===
WHMCS_BASE_URL=<your_whmcs_url>
WHMCS_API_IDENTIFIER=<your_identifier>
WHMCS_API_SECRET=<your_secret>

SF_LOGIN_URL=<salesforce_instance_url>
SF_CLIENT_ID=<oauth_client_id>
SF_PRIVATE_KEY_PATH=./secrets/sf-private.key
SF_USERNAME=<salesforce_username>

FREEBIT_API_BASE_URL=<freebit_api_url>
FREEBIT_CLIENT_ID=<freebit_client_id>
FREEBIT_CLIENT_SECRET=<freebit_secret>

Salesforce Pub/Sub (Events)

# Enable Pub/Sub subscription for order provisioning
SF_EVENTS_ENABLED=true
SF_PROVISION_EVENT_CHANNEL=/event/Order_Fulfilment_Requested__e
SF_EVENTS_REPLAY=LATEST        # or ALL for retention replay
SF_PUBSUB_ENDPOINT=api.pubsub.salesforce.com:7443
SF_PUBSUB_NUM_REQUESTED=25     # flow control window
  • Verify subscriber status: GET /api/health/sf-events
    • enabled: whether Pub/Sub is enabled
    • channel: topic name
    • replay.lastReplayId: last committed cursor
    • subscriber.status: connected | disconnected | unknown

Read more about the provisioning flow in docs/provisioning/RUNBOOK_PROVISIONING.md.

Development Tools Access

When running pnpm dev:tools, you get access to:

Data Model

Core Tables (PostgreSQL)

  • users - Portal user accounts with auth credentials
  • id_mappings - Cross-system user ID mappings
  • invoices_mirror - Optional WHMCS invoice cache
  • subscriptions_mirror - Optional WHMCS service cache
  • idempotency_keys - Prevent duplicate operations

API Surface (BFF)

Authentication

  • POST /api/auth/signup - Create portal user → WHMCS AddClient → SF upsert
  • POST /api/auth/login - Portal authentication
  • POST /api/auth/migrate - Account migration from legacy portal
  • POST /api/auth/set-password - Required after WHMCS link

User Management

  • GET /api/me - Current user profile
  • GET /api/me/summary - Dashboard summary
  • PATCH /api/me - Update profile
  • PATCH /api/me/address - Update address fields

Catalog & Orders

  • GET /api/services/* - Services catalog endpoints (internet/sim/vpn)
  • POST /api/orders - WHMCS AddOrder with idempotency

Invoices

  • GET /api/invoices - Paginated invoice list (cached 60-120s)
  • GET /api/invoices/:id - Invoice details
  • POST /api/invoices/:id/sso-link - WHMCS CreateSsoToken

Subscriptions

  • GET /api/subscriptions - WHMCS GetClientsProducts

Support Cases (Salesforce)

  • GET /api/cases - Cases list (cached 30-60s)
  • GET /api/cases/:id - Case details with comments
  • POST /api/cases - Create new case
  • POST /api/cases/:id/comments - Add comment to case

Webhooks & Events

  • POST /api/webhooks/whmcs - WHMCS action hooks → update mirrors + bust cache
  • Salesforce Platform Events - Real-time order provisioning via gRPC Pub/Sub

Frontend Pages

Public Pages

  • / - Landing page for non-authenticated users
  • /auth/login - Sign in
  • /auth/signup - Create account
  • /auth/set-password - Set password after WHMCS link

Authenticated Pages

  • /dashboard - Dashboard (invoices, active subs, orders)
  • /catalog - Product catalog home
  • /catalog/internet - Internet plans
  • /catalog/vpn - VPN products
  • /checkout - Checkout flow
  • /orders - Order list
  • /orders/[id] - Order details
  • /subscriptions - Active subscriptions
  • /subscriptions/[id] - Subscription details
  • /billing/invoices - Invoice list
  • /billing/invoices/[id] - Invoice details
  • /billing/payments - Payment methods
  • /support/cases - Support cases list
  • /support/cases/[id] - Case details
  • /support/new - Create new case
  • /account - User account management

Development Milestones

Milestone 1: Identity & Linking

  • Portal login/signup with JWT authentication
  • One-time WHMCS verification with SSO
  • Set new portal password (Bcrypt)
  • Store id_mappings (user ↔ WHMCS ↔ Salesforce)
  • Refresh token rotation
  • Account lockout after failed attempts
  • Rate limiting on auth endpoints

Milestone 2: Catalog & Orders

  • Product catalog (GetProducts from WHMCS)
  • Catalog caching with CDC invalidation
  • Internet plan catalog with address verification
  • VPN product catalog
  • Checkout flow with cart management
  • Order creation (WHMCS AddOrder)
  • Order list and details from Salesforce
  • Real-time order provisioning via Salesforce Platform Events

Milestone 3: Billing & Invoices

  • Invoice list/detail (GetInvoices from WHMCS)
  • Invoice caching with TTL
  • WHMCS SSO deep links for payment
  • Payment method management
  • Payment gateway listing
  • Subscription list and details
  • WHMCS webhooks → cache bust + mirror updates

Milestone 4: Support & Cases

  • Salesforce case list (cached)
  • Case details with comments
  • Create new support case
  • Add comments to existing cases
  • Case file attachments
  • Email notifications for case updates

Milestone 5: SIM Management & Provisioning

  • Freebit SIM management integration
  • Order provisioning workflow
  • Real-time event processing via Salesforce Platform Events
  • Comprehensive error handling and retry logic
  • Customer-facing SIM management UI

Security Features

  • HTTPS only with HttpOnly/SameSite cookies
  • JWT access + refresh tokens with Redis-backed blacklist
  • Bcrypt password hashing (configurable rounds)
  • Account lockout after failed login attempts
  • Rate limiting on auth endpoints and external API calls
  • Idempotency keys on all mutating operations
  • Row-level security (user must own resources)
  • PII minimization with encryption at rest/in transit
  • Audit logging for security-critical actions
  • No WHMCS/SF credentials exposed to browser

Caching Strategy

  • Invoices: TTL-based (90s); bust on WHMCS webhook
  • Catalog: CDC-driven (no TTL); manual bust on data changes
  • Orders: CDC-driven (no TTL); real-time invalidation via Salesforce Platform Events
  • WHMCS API responses: TTL-based caching (configurable per endpoint)
  • Redis-backed with request coalescing to prevent thundering herd
  • Keys include user_id to prevent cross-user leakage
  • Metrics tracking for cache hits, misses, and invalidations

Troubleshooting

Common Issues

Port Already in Use

# Check what's using the port
lsof -i :3000  # or :4000, :5432, :6379
# Kill the process or change ports in .env

Database Connection Issues

# Check if PostgreSQL is running
pnpm dev:status
# Restart services
pnpm dev:restart
# Reset everything
pnpm dev:reset

Environment Variables Not Loading

  • Ensure environment variables are configured (contact team for configuration)
  • Restart applications after changing environment variables
  • Check for typos in variable names
  • Frontend variables must start with NEXT_PUBLIC_

Docker Issues

# Clean up Docker resources
docker system prune -f
# Rebuild containers
pnpm dev:stop && pnpm dev:start

pnpm Issues

# Clear pnpm cache
pnpm store prune
# Reinstall dependencies
rm -rf node_modules && pnpm install

Getting Help

  1. Check the logs: pnpm dev:logs
  2. Verify service status: pnpm dev:status
  3. Review environment configuration in .env
  4. Check the documentation in docs/ folder
  5. Look for similar issues in the project's issue tracker

Documentation

  • Getting Started - Detailed setup guide
  • Development Guide - Quick reference for daily development
  • Deployment Guide - Production deployment instructions
  • Architecture - Code organization and conventions
  • Logging - Logging configuration and best practices
  • Portal Guides - High-level flow, data ownership, and error handling (docs/how-it-works/README.md)

Contributing

  1. Setup Development Environment

    # Configure environment variables (contact team)
    pnpm install
    pnpm dev:start
    
  2. Follow Code Standards

    • Run pnpm lint before committing
    • Use pnpm format to format code
    • Ensure pnpm type-check passes
    • Write tests for new features
  3. Development Workflow

    • Create feature branches from main
    • Make small, focused commits
    • Update documentation as needed
    • Test thoroughly before submitting PRs
  4. Code Quality

    • Follow TypeScript strict mode
    • Use proper error handling (no sensitive info exposed)
    • Implement clean, minimal UI designs
    • Avoid 'V2' suffixes in service names
    • Verify API integration against official documentation

Codebase Coding Standard

  1. Have types and validation in the shared domain layer.
  2. Keep business logic out of the frontend; use services and APIs instead.
  3. Reuse existing types and functions; extend them when additional behavior is needed.
  4. Follow the established folder structures documented in docs/STRUCTURE.md.

Documentation

📚 Complete Documentation - Full documentation index

Key Features

  • Required address at signup - No incomplete profiles
  • Order-type specific flows - Internet orders require verification
  • Real-time WHMCS sync - Address updates
  • Salesforce snapshots - Point-in-time order addresses
  • Clean architecture - Modular, maintainable code

License

[Your License Here] See docs/RUNBOOK_PROVISIONING.md for the provisioning runbook.

Description
Assist Design
Readme 16 MiB
Languages
TypeScript 51%
JavaScript 47.2%
Shell 0.8%
HTML 0.5%
CSS 0.3%
Other 0.1%