This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
This is a Next.js 15+ payroll management system migrating from Laravel/PHP. The application manages employee payroll, invoices, commissions, and document management for Choice Marketing Partners.
Tech Stack:
- Next.js 15 with App Router + Turbopack
- TypeScript (strict mode)
- MySQL database with Kysely ORM (type-safe SQL)
- NextAuth.js (credentials + bcrypt)
- Vercel Blob Storage
- Tailwind CSS + shadcn/ui components
bun dev # Development server with Turbopack
bun build # Production build with Turbopack
bun start # Production server
bun lint # ESLint checking# Unit tests (Jest)
bun test # Run all unit tests
bun test:watch # Watch mode
bun test:coverage # Coverage report
bun test:ci # CI mode
# E2E tests (Playwright)
bun test:e2e # Run all E2E tests
bun test:e2e:headed # Run with visible browser
bun test:e2e:ui # Interactive UI mode
bun test:e2e:debug # Debug mode
bun test:e2e basic.spec.ts # Run specific test file
bun test:e2e:report # View HTML report
bun test:install # Install Playwright browsersTest Structure:
- Unit tests:
src/**/__tests__/**/*.{test,spec}.{ts,tsx} - E2E tests:
tests/**/*.spec.ts(Playwright) - Test database users:
admin@test.com,manager@test.com,employee@test.com(all usepassword123)
choice-marketing-partners/
├── src/
│ ├── app/ # Next.js App Router
│ │ ├── (portal)/ # Protected authenticated routes
│ │ ├── admin/ # Admin-only pages
│ │ ├── manager/ # Manager+ access pages
│ │ └── api/ # API routes
│ ├── lib/
│ │ ├── database/ # Kysely client & auto-generated types
│ │ ├── repositories/ # Business logic layer (USE THIS)
│ │ ├── auth/ # NextAuth configuration
│ │ ├── storage/ # Vercel Blob utilities
│ │ └── utils/ # Shared utilities
│ └── components/ # React components
├── tests/ # E2E Playwright tests
├── memory-bank/ # Project context documentation
└── legacy/ # Old Laravel application (reference only)
Always use repositories for database operations - never write raw SQL queries in API routes or components.
Key repositories in src/lib/repositories/:
EmployeeRepository.ts- Employee management, user linking, role checksPayrollRepository.ts- Payroll calculations, paystubs, commission trackingInvoiceRepository.ts- Invoice management and audit trailsDocumentRepository.ts- File uploads/downloads with access controlVendorRepository.ts- Vendor/sales management
Example pattern:
// ❌ Don't write SQL in API routes
await db.selectFrom('employees').where('id', '=', id).execute()
// ✅ Use repository methods
const employeeRepo = new EmployeeRepository()
await employeeRepo.getEmployeeById(id, session.user)Session structure (from NextAuth):
session.user = {
id: string // User UID
email: string
isAdmin: boolean // Admin access flag
isManager: boolean // Manager access flag
isActive: boolean // Account active status
employeeId: number // Linked employee ID
salesIds: string[] // Sales IDs for commission tracking
}Role-based access hierarchy: Admin > Manager > Employee
All API routes and server components must:
- Check session:
const session = await getServerSession(authOptions) - Validate role permissions before operations
- Filter data by user role (repositories handle this automatically)
Route protection handled by middleware.ts:
- Public routes:
/,/blog/*,/about-us,/comma-club - Protected routes: Everything else requires authentication
- Admin routes:
/admin/* - Manager routes:
/manager/*
Self-service password reset with JWT tokens:
- Request:
POST /api/auth/request-reset(validates active user, sends email) - Reset:
POST /api/auth/reset-password(validates token, updates password) - Token expiration: 1 hour
- Audit logging: All requests logged to
password_resetstable - Email delivery: Resend API
UI Flow:
- User clicks "Forgot password?" on sign in page
- Enters email at
/auth/forgot-password - Receives email with reset link
- Clicks link →
/auth/reset-password?token=... - Enters new password
- Redirected to sign in page
Security:
- JWT signed with
NEXTAUTH_SECRET - Only active accounts can reset (
is_active = 1) - Prevents email enumeration (same message for existing/non-existing emails)
- Password minimum 8 characters
CRITICAL: Preserve existing MySQL schema from Laravel migration - do NOT make structural database changes.
Key patterns:
- Employee-User linking: Via
employee_userjunction table - Role flags:
is_admin,is_mgronemployeestable - Soft deletes:
deleted_attimestamps,is_activeflags - Sales tracking:
sales_id1,sales_id2,sales_id3for payroll calculations - Manager assignments:
manager_employeestable for employee relationships
Database client configuration (src/lib/database/client.ts):
- Connection pooling optimized for serverless (limit: 1)
- Supports
DATABASE_URLor individual env vars - Development query logging enabled
- Type-safe queries via Kysely with auto-generated types
Database types are auto-generated in src/lib/database/types.ts using kysely-codegen. When schema changes:
bun run kysely-codegenAll database operations are fully type-safe through the DB interface.
Dual storage system during migration:
- Legacy files:
public/uploads/(read-only) - New uploads: Vercel Blob with presigned URLs
- Access control: Download proxy through API routes
Upload flow:
import { put } from '@vercel/blob'
const { url, downloadUrl } = await put(filename, file, { access: 'public' })
// Store downloadUrl in databaseStandard structure for all API routes:
// app/api/[resource]/route.ts
export async function GET(request: Request) {
const session = await getServerSession(authOptions)
// Role-based authorization
if (!session?.user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
// Use repository pattern
const repo = new ResourceRepository()
const data = await repo.getData(filters, session.user)
return NextResponse.json(data)
}IMPORTANT: All queries must filter by user role and relationships - no global queries.
shadcn/ui Integration:
- UI components in
src/components/ui/ - Tailwind CSS with class-variance-authority for variants
- Forms: React Hook Form + Zod validation
Role-based rendering:
{session?.user?.isAdmin && <AdminOnlyComponent />}
{(session?.user?.isAdmin || session?.user?.isManager) && <ManagerComponent />}Required in .env.local (see .env.example):
DATABASE_URL="mysql://user:password@localhost:3306/choice_marketing"
NEXTAUTH_URL="http://localhost:3000"
NEXTAUTH_SECRET="your-secret"
BLOB_READ_WRITE_TOKEN="vercel-blob-token"
RESEND_API_KEY="resend-email-key"
NEXT_PUBLIC_POSTHOG_KEY="analytics-key"
NEXT_PUBLIC_POSTHOG_HOST="https://app.posthog.com"- Payroll access: Strictly controlled by employee-manager relationships
- Invoice changes: Trigger automatic paystub recalculation
- Data isolation: Users only see data they're authorized for
- Audit trails: All financial changes must be trackable
- MySQL compatibility: No PostgreSQL-specific features (no RETURNING clause, etc.)
This is an active Laravel → Next.js migration. Reference memory-bank/ for:
activeContext.md- Current development phase (Phase 8 cutover planning)systemPatterns.md- Architecture patterns and migration strategyproductContext.md- Business requirements and user rolestechContext.md- Technology stack details
Current Status: Core functionality complete (Phases 0-7), preparing for production cutover.
- Path alias:
@/*maps tosrc/* - Strict mode enabled
- Target: ES2017
- Tests excluded from main compilation
- Don't skip repositories - Always use the repository pattern for database queries
- MySQL syntax only - No PostgreSQL features (avoid RETURNING, etc.)
- Connection pooling - Serverless environment has connection limit of 1
- Soft deletes - Check
deleted_atandis_activewhen querying - Role filtering - Never write queries that bypass role-based access control
- Next.js 15 params - Params are async in route handlers:
const params = await request.params