Technical Reference
Version: 1.0.1
Architecture, API routes, database schema, and storage configuration for developers and system administrators.
Architecture Overview
┌─────────────────────────────────────────────────────────────┐
│ Browser │
│ ┌─────────────┐ ┌──────────────┐ ┌─────────────────────┐ │
│ │ AuthContext │ │ Page UI │ │ Supabase Client │ │
│ │ (JWT cookie)│ │ (permissions)│ │ (direct CRUD) │ │
│ └──────┬──────┘ └──────────────┘ └──────────┬──────────┘ │
└─────────┼──────────────────────────────────────┼────────────┘
│ │
▼ ▼
┌─────────────────────┐ ┌───────────────────────┐
│ Next.js API Routes │ │ Supabase │
│ /api/auth/* │ │ ├── PostgreSQL │
│ /api/users/* │ │ └── Storage buckets │
│ /api/business-* │ └───────────────────────┘
│ /api/complaints/* │
└─────────────────────┘
Key design decisions:
- Authentication — Custom JWT session in httpOnly cookie (
courier_session), not Supabase Auth
- Authorization — Role + per-page permissions checked in UI; admin-only routes on API
- Data access — Complaints, workflows, types read/written via Supabase anon client from browser
- Uploads — Server-side API routes upload to Supabase Storage
Tech Stack
| Layer | Technology |
|---|
| Framework | Next.js 14 (App Router) |
| Language | TypeScript |
| UI | Tailwind CSS, Lucide React icons |
| Database | Supabase PostgreSQL |
| Storage | Supabase Storage |
| Auth | bcrypt (passwords) + jose (JWT sessions) |
| Hosting | Vercel (typical) |
Project Structure
Complaint-Managment-System/
├── app/ # Next.js App Router pages & API
│ ├── api/ # Server API routes
│ ├── complaints/ # Complaints pages
│ ├── dashboard/ # Redirects to /
│ ├── login/ # Login page
│ ├── profile/ # User profile & business branding
│ ├── resolution-workflow/ # Resolution workflow page
│ └── settings/ # Admin settings pages
├── components/ # React components
│ ├── complaints/ # Print view, etc.
│ └── dashboard/ # Dashboard home
├── context/ # AuthContext provider
├── database/ # SQL schema file
├── Documentation/ # This documentation
├── lib/ # Shared utilities & data access
├── public/ # Static assets (sw.js)
└── middleware.ts # Auth middleware
API Routes
Authentication
| Endpoint | Method | Auth | Body / Response |
|---|
/api/auth/login | POST | Public | Body: { email, password } → { user } + sets cookie |
/api/auth/logout | POST | Public | Clears session cookie |
/api/auth/me | GET | Cookie | { user, permissions } |
/api/auth/session | GET | Cookie | { user } |
/api/auth/change-password | POST | Authenticated | Body: { currentPassword, newPassword } |
Session cookie: courier_session — httpOnly, 7-day expiry, secure in production.
Users (Admin Only for Write)
| Endpoint | Method | Auth | Description |
|---|
/api/users | GET | Authenticated | List all users |
/api/users | POST | Admin | Create user with permissions |
/api/users/[id] | GET | Authenticated | Get single user + permissions |
/api/users/[id] | PATCH | Admin | Update user |
/api/users/[id] | DELETE | Admin | Delete user (not self) |
Create user body:
{
"name": "Agent Name",
"email": "agent@company.com",
"password": "securepass",
"role": "editor",
"permissions": [
{ "page_key": "complaints", "can_view": true, "can_edit": true, "can_delete": false }
]
}
Business Profile
| Endpoint | Method | Auth | Description |
|---|
/api/business-profile | GET | Authenticated | Get business name & logo URL |
/api/business-profile | PUT | Admin | Update business profile |
/api/business-profile/logo-upload | POST | Admin | Upload logo file (multipart) |
File Uploads
| Endpoint | Method | Auth | Limits |
|---|
/api/complaints/images-upload | POST | Authenticated | Max 10 files, 2MB each, PNG/JPG/WEBP |
/api/business-profile/logo-upload | POST | Admin | Max 2MB, PNG/JPG/WEBP |
Database Schema
Run database/cms-schema.sql once in Supabase SQL Editor.
Tables
app_users
| Column | Type | Notes |
|---|
| id | uuid | Primary key |
| name | text | Display name |
| email | text | Unique login email |
| password_hash | text | bcrypt hash |
| role | text | admin, editor, viewer |
| created_at | timestamptz | |
| updated_at | timestamptz | |
user_permissions
| Column | Type | Notes |
|---|
| id | uuid | Primary key |
| user_id | uuid | FK → app_users |
| page_key | text | Page identifier |
| can_view | boolean | |
| can_edit | boolean | |
| can_delete | boolean | |
| | UNIQUE(user_id, page_key) |
complaints
| Column | Type | Notes |
|---|
| id | uuid | Primary key |
| complaint_id | text | Unique display ID (CMP-00001) |
| complaint_date | date | |
| customer_name | text | |
| order_id | text | |
| complaint_type | text | |
| priority | text | Default: Medium |
| status | text | Default: Open |
| note | text | |
| old_tracking_id | text | |
| created_at | timestamptz | |
| updated_at | timestamptz | |
complaint_images
| Column | Type | Notes |
|---|
| id | uuid | Primary key |
| complaint_id | uuid | FK → complaints (CASCADE) |
| image_url | text | Supabase storage URL |
| created_at | timestamptz | |
resolution_workflows
| Column | Type | Notes |
|---|
| id | uuid | Primary key |
| complaint_id | uuid | FK → complaints (CASCADE) |
| resolution_type | text | |
| refund_amount | numeric(12,2) | Nullable |
| agent_note | text | |
| priority_override | text | Syncs to complaint |
| status_override | text | Syncs to complaint |
| new_order_id | text | |
| new_tracking_id | text | |
| created_at | timestamptz | |
| updated_at | timestamptz | |
complaint_types
| Column | Type | Notes |
|---|
| id | uuid | Primary key |
| name | text | Unique |
| template | text | Message template with {Variable} placeholders |
| created_at | timestamptz | |
| updated_at | timestamptz | |
app_business_profile
| Column | Type | Notes |
|---|
| id | integer | Always 1 (singleton) |
| business_name | text | |
| logo_url | text | |
| updated_by | uuid | FK → app_users |
| updated_at | timestamptz | Auto-updated via trigger |
Relationships
app_users ──1:*── user_permissions
app_users ──0:1── app_business_profile (updated_by)
complaints ──1:*── complaint_images
complaints ──1:*── resolution_workflows
complaint_types (standalone lookup)
Row Level Security (RLS)
All data tables have permissive anon policies (full CRUD for anon role). Application-level security is enforced via JWT session and UI permissions, not Supabase Auth RLS.
Supabase Storage Buckets
| Bucket | Used For | API Route |
|---|
complaint-images | Complaint evidence photos | /api/complaints/images-upload |
branding-assets | Business logo | /api/business-profile/logo-upload |
Environment Variables
| Variable | Required | Description |
|---|
NEXT_PUBLIC_SUPABASE_URL | Yes | Supabase project URL |
NEXT_PUBLIC_SUPABASE_ANON_KEY | Yes | Public anon key |
SUPABASE_SERVICE_ROLE_KEY | Yes | Server-side operations |
JWT_SECRET | Yes | Session token signing |
Auth Flow (Technical)
1. POST /api/auth/login { email, password }
2. Server: SELECT from app_users WHERE email = ?
3. bcrypt.compare(password, password_hash)
4. jose.SignJWT({ userId, email, role }) → courier_session cookie
5. Client: AuthContext.refreshSession() → GET /api/auth/me
6. Response: { user, permissions[] }
7. UI: canView/canEdit/canDelete helpers available globally
8. Middleware: verify JWT on each request (except /login, /api/auth/*)
Key Library Files
| File | Purpose |
|---|
lib/auth.ts | JWT sign/verify, cookie config |
lib/permissions.ts | Page keys, path mapping |
lib/complaints.ts | Complaint CRUD, image sync, ID generation |
lib/resolution-workflow.ts | Workflow CRUD, complaint field sync |
lib/complaintTypes.ts | Type CRUD, message template rendering |
lib/businessProfile.ts | Business profile fetch, favicon apply |
lib/supabase.ts | Browser Supabase client |
lib/supabase-server.ts | Server Supabase client (service role) |
context/AuthContext.tsx | Global auth state and permission helpers |
Complaint ID Generation
Format: CMP-##### (zero-padded 5 digits)
Algorithm (lib/complaints.ts):
- Fetch highest existing
complaint_id matching CMP-%
- Parse numeric suffix
- Increment by 1
- Pad to 5 digits
Example sequence: CMP-00001, CMP-00002, … CMP-00100
Theme System
| Setting | Storage Key | Default |
|---|
| Theme (dark/light) | theme in localStorage | dark |
| Sidebar collapsed | sidebar-collapsed in localStorage | false |
| Cached logo URL | cms-business-logo-url in localStorage | — |
ThemeInit component applies dark class before first paint to prevent flash.
Production Integrations
When NODE_ENV === 'production':
- Vercel Speed Insights — performance monitoring
- Vercel Analytics — usage analytics
Both are loaded in app/layout.tsx.
Service Worker
public/sw.js is a minimal stub that prevents 404 errors from browser PWA detection. The app does not use offline caching.
Error Handling
app/error.tsx — Client-side error boundary with "Try again" button
- API routes return JSON
{ error: "message" } with appropriate HTTP status codes
- Page components show inline error banners with Retry for data fetch failures