# Demo Bookings Module - Architecture Documentation

**Module:** `lib/staff-dashboard/demo-bookings/`
**Purpose:** Well-structured, maintainable utilities for managing demo appointment bookings
**Version:** 2.0 (Refactored November 16, 2025)

---

## 📁 Module Structure

```
lib/staff-dashboard/demo-bookings/
├── index.ts          # Public API - import everything from here
├── types.ts          # TypeScript type definitions
├── constants.ts      # Configuration and magic numbers
├── mappers.ts        # Data transformation utilities
├── queries.ts        # Supabase query builders
├── validators.ts     # Input validation
├── errors.ts         # Error handling and logging
└── README.md         # This file
```

---

## 🎯 Design Principles

### 1. Separation of Concerns

Each module has a single, clear responsibility:
- **Types**: Define data structures only
- **Constants**: Configuration and magic values only
- **Mappers**: Data transformation only
- **Queries**: Database operations only
- **Validators**: Input validation only
- **Errors**: Error handling and logging only

### 2. Easy to Change

Want to change something? You know exactly where to look:

| What You Want to Change | File to Edit |
|------------------------|--------------|
| Add new status type | `constants.ts` (add to BOOKING_STATUSES) |
| Change error message | `constants.ts` (ERROR_MESSAGES) |
| Modify search logic | `queries.ts` (fetchDemoBookings function) |
| Add validation rule | `validators.ts` (add new function) |
| Change field mapping | `mappers.ts` (mapBookingToFrontend) |
| Adjust logging | `errors.ts` (logOperation, etc.) |

### 3. Type Safety

Everything is fully typed:
- Database records: `DemoBookingRecord` (snake_case)
- Frontend data: `DemoBooking` (camelCase)
- API requests: `DemoBookingUpdateRequest`
- API responses: `DemoBookingsResponse`
- Query results: `QueryResult<T>`
- Validation results: `ValidationResult`

### 4. Testability

Each module can be tested independently:
- Query builders work with any Supabase client
- Mappers are pure functions (easy to test)
- Validators have no dependencies
- Error handlers can be tested in isolation

---

## 📖 Usage Guide

### Importing

**Always import from the index:**
```typescript
// ✅ CORRECT
import {
  fetchDemoBookings,
  mapBookingsToFrontend,
  validateFilters,
} from '@/lib/staff-dashboard/demo-bookings';

// ❌ WRONG - Don't import from sub-modules directly
import { fetchDemoBookings } from '@/lib/staff-dashboard/demo-bookings/queries';
```

### Common Patterns

#### Pattern 1: Fetch and Display Bookings

```typescript
import {
  fetchDemoBookings,
  mapBookingsToFrontend,
  createDatabaseErrorResponse,
} from '@/lib/staff-dashboard/demo-bookings';

export async function GET(request: NextRequest) {
  // 1. Auth check (handled separately)
  const user = await requireStaffAuth();
  if (!user) return createUnauthorizedResponse();

  // 2. Parse filters
  const filters = { status: 'pending', search: 'john' };

  // 3. Query database
  const result = await fetchDemoBookings(supabaseAdmin, filters);
  if (result.error) {
    return createDatabaseErrorResponse(result.error, 'fetch');
  }

  // 4. Transform and return
  const bookings = mapBookingsToFrontend(result.data);
  return NextResponse.json({ bookings });
}
```

#### Pattern 2: Update a Booking

```typescript
import {
  updateDemoBooking,
  mapUpdateRequestToDatabase,
  validateUpdateRequest,
} from '@/lib/staff-dashboard/demo-bookings';

export async function POST(request: NextRequest) {
  // 1. Parse and validate
  const body = await request.json();
  const validation = validateUpdateRequest(body);
  if (!validation.valid) {
    return createBadRequestResponse(validation.errors);
  }

  // 2. Map to database format
  const { bookingId, ...updates } = body;
  const updateData = mapUpdateRequestToDatabase(updates, user.id);

  // 3. Execute update
  const result = await updateDemoBooking(supabaseAdmin, bookingId, updateData);

  // 4. Return result
  return createSuccessResponse({ success: true, booking: result.data });
}
```

#### Pattern 3: Get Statistics

```typescript
import { countBookingsByStatus } from '@/lib/staff-dashboard/demo-bookings';

const counts = await countBookingsByStatus(supabaseAdmin);
console.log(`Pending: ${counts.pending}, Total: ${counts.total}`);
```

---

## 🔄 Data Flow

### Request → Database → Response

```
1. Client Request (camelCase)
   ↓
2. API Route (parse query params/body)
   ↓
3. Validators (validate input)
   ↓
4. Mappers (camelCase → snake_case)
   ↓
5. Query Builders (build Supabase query)
   ↓
6. Supabase Database (snake_case)
   ↓
7. Query Result (snake_case)
   ↓
8. Mappers (snake_case → camelCase)
   ↓
9. API Response (camelCase)
   ↓
10. Client (camelCase)
```

### Example Flow: Confirming a Booking

```typescript
// 1. Frontend sends request (camelCase)
fetch('/api/staff/dashboard/demos/update', {
  body: JSON.stringify({
    bookingId: 'abc123',
    status: 'confirmed',
    meetingLink: 'https://zoom.us/j/123'
  })
});

// 2. API validates
validateUpdateRequest(body); // ✅ Valid

// 3. Mapper transforms (camelCase → snake_case)
mapUpdateRequestToDatabase({ status: 'confirmed', meetingLink: '...' }, staffId);
// Returns: { status: 'confirmed', meeting_link: '...', contacted_by: staffId }

// 4. Query executes
updateDemoBooking(client, 'abc123', updateData);
// SQL: UPDATE demo_bookings SET status='confirmed', meeting_link='...', ...

// 5. Result returned (snake_case)
{ data: { id: 'abc123', full_name: 'John', ... }, error: null }

// 6. Frontend receives (snake_case preserved in this case)
// Or mapped if needed: mapBookingToFrontend(result.data)
```

---

## 🛠️ Extending the Module

### Adding a New Field

**Example: Add "priority" field to bookings**

**Step 1: Database Migration**
```sql
-- supabase/migrations/20251117_add_priority_field.sql
ALTER TABLE demo_bookings ADD COLUMN priority TEXT DEFAULT 'normal';
```

**Step 2: Update Types** (`types.ts`)
```typescript
export type DemoBookingRecord = {
  // ... existing fields
  priority: 'low' | 'normal' | 'high' | null; // Add here
};

export type DemoBooking = {
  // ... existing fields
  priority: 'low' | 'normal' | 'high' | null; // Add here
};
```

**Step 3: Update Constants** (`constants.ts`)
```typescript
export const PRIORITIES = ['low', 'normal', 'high'] as const;

export const DB_FIELDS = {
  // ... existing fields
  PRIORITY: 'priority', // Add here
};
```

**Step 4: Update Mapper** (`mappers.ts`)
```typescript
export function mapBookingToFrontend(record: DemoBookingRecord): DemoBooking {
  return {
    // ... existing mappings
    priority: record.priority, // Add here
  };
}
```

**Done!** The query builders, validators, and API routes automatically work with the new field.

### Adding a New Filter

**Example: Filter by language**

**Step 1: Update Types** (`types.ts`)
```typescript
export type DemoBookingFilters = {
  // ... existing filters
  language?: 'ar' | 'en' | 'all'; // Add here
};
```

**Step 2: Update Validator** (`validators.ts`)
```typescript
export function validateFilters(filters: unknown): ValidationResult {
  // ... existing validation
  if (f.language && f.language !== 'all') {
    if (!LANGUAGES.includes(f.language)) {
      errors.push('Invalid language filter');
    }
  }
}
```

**Step 3: Update Query Builder** (`queries.ts`)
```typescript
export async function fetchDemoBookings(...) {
  // ... existing query building
  if (filters?.language && filters.language !== 'all') {
    query = query.eq(DB_FIELDS.LANGUAGE, filters.language);
  }
}
```

**Done!** The API route automatically supports the new filter.

### Adding a New Query Function

**Example: Fetch bookings needing follow-up**

**Add to** `queries.ts`:
```typescript
/**
 * Fetch bookings that need follow-up
 * (confirmed but no meeting link yet)
 */
export async function fetchBookingsNeedingFollowUp(
  client: SupabaseClient
): Promise<QueryResult<DemoBookingRecord[]>> {
  try {
    const { data, error } = await client
      .from(DEMO_BOOKINGS_TABLE)
      .select('*')
      .eq(DB_FIELDS.STATUS, 'confirmed')
      .is(DB_FIELDS.MEETING_LINK, null)
      .order(QUERY_CONFIG.DEFAULT_ORDER, { ascending: QUERY_CONFIG.DEFAULT_ASCENDING });

    if (error) {
      return { data: null, error: new Error(error.message) };
    }

    return { data: data || [], error: null };
  } catch (error) {
    return { data: null, error: error instanceof Error ? error : new Error('Unknown error') };
  }
}
```

**Export from** `index.ts`:
```typescript
export { fetchBookingsNeedingFollowUp } from './queries';
```

**Use in your code:**
```typescript
import { fetchBookingsNeedingFollowUp } from '@/lib/staff-dashboard/demo-bookings';

const result = await fetchBookingsNeedingFollowUp(supabaseAdmin);
```

---

## 🧪 Testing Strategy

### Unit Testing Query Builders

```typescript
import { fetchDemoBookings } from '@/lib/staff-dashboard/demo-bookings';

const mockSupabase = {
  from: jest.fn().mockReturnValue({
    select: jest.fn().mockReturnThis(),
    eq: jest.fn().mockReturnThis(),
    order: jest.fn().mockReturnThis(),
    limit: jest.fn().mockResolvedValue({ data: [...], error: null }),
  }),
};

const result = await fetchDemoBookings(mockSupabase, { status: 'pending' });
expect(result.data).toHaveLength(5);
```

### Unit Testing Mappers

```typescript
import { mapBookingToFrontend } from '@/lib/staff-dashboard/demo-bookings';

const dbRecord = {
  id: '123',
  full_name: 'John Doe',
  appointment_date: '2025-12-01T10:00:00Z',
  // ... other fields
};

const frontendData = mapBookingToFrontend(dbRecord);

expect(frontendData.fullName).toBe('John Doe'); // camelCase
expect(frontendData.appointmentDate).toBe('2025-12-01T10:00:00Z');
```

### Unit Testing Validators

```typescript
import { validateUpdateRequest } from '@/lib/staff-dashboard/demo-bookings';

const validation = validateUpdateRequest({
  bookingId: 'abc123',
  status: 'confirmed',
});

expect(validation.valid).toBe(true);
expect(validation.errors).toHaveLength(0);
```

---

## 📊 Performance Considerations

### Query Optimization

1. **Indexes** - Ensure these indexes exist:
   ```sql
   CREATE INDEX idx_demo_bookings_status ON demo_bookings(status);
   CREATE INDEX idx_demo_bookings_created_at ON demo_bookings(created_at);
   CREATE INDEX idx_demo_bookings_email ON demo_bookings(email);
   ```

2. **Limits** - Always use limits to prevent large result sets:
   ```typescript
   fetchDemoBookings(client, filters, 100); // Max 100 results
   ```

3. **Selective Fields** - Fetch only what you need:
   ```typescript
   // If you only need counts, don't fetch full records
   countBookingsByStatus(client); // Uses head: true for efficiency
   ```

### Response Size

The mappers automatically exclude webhook fields from frontend responses if not needed.
Consider adding a `minimal` flag to exclude non-essential fields:

```typescript
// Future enhancement
fetchDemoBookings(client, filters, { minimal: true });
// Returns only: id, fullName, email, status, appointmentDate
```

---

## 🔒 Security Considerations

### 1. RLS Policies

Demo bookings table has Row Level Security enabled:
- **Read/Update**: Requires `service_role` (only `supabaseAdmin`)
- **Insert**: Allowed for `anon` (public demo form)

**Always use `supabaseAdmin`, never regular `supabase` client.**

### 2. Input Sanitization

All user input is sanitized before use:
```typescript
sanitizeSearchTerm('user input'); // Removes SQL injection attempts
sanitizeSearch('user input');      // Full validation + sanitization
```

### 3. Audit Logging

All staff actions are logged:
```typescript
await saveAuditLogToBoth({
  userId: user.id,
  type: 'staff_action',
  action: 'Updated demo booking',
  metadata: { bookingId, changes },
});
```

### 4. Error Messages

Errors returned to clients never expose internal details:
```typescript
// Internal log: Full error details
console.error('[STAFF DASHBOARD] Database error:', fullError);

// Client response: Generic message only
return { error: 'Failed to fetch bookings' };
```

---

## 🔄 Data Conventions

### Naming Conventions

| Layer | Convention | Example |
|-------|-----------|---------|
| Database (Supabase) | snake_case | `full_name`, `appointment_date` |
| Frontend (React) | camelCase | `fullName`, `appointmentDate` |
| API Request/Response | camelCase | `meetingLink`, `internalNotes` |
| TypeScript Types (DB) | snake_case | `DemoBookingRecord` |
| TypeScript Types (API) | camelCase | `DemoBooking` |

### Type Mapping

```typescript
// Database record (from Supabase)
const dbRecord: DemoBookingRecord = {
  full_name: "John Doe",        // snake_case
  appointment_date: "...",      // snake_case
  meeting_link: null            // snake_case
};

// Frontend object (for React)
const frontendData: DemoBooking = {
  fullName: "John Doe",         // camelCase
  appointmentDate: "...",       // camelCase
  meetingLink: null             // camelCase
};

// Transformation is automatic
const frontendData = mapBookingToFrontend(dbRecord);
```

---

## 🚀 Quick Reference

### Most Common Functions

```typescript
// Import everything you need
import {
  // Queries
  fetchDemoBookings,
  updateDemoBooking,
  countBookingsByStatus,

  // Mappers
  mapBookingsToFrontend,
  mapUpdateRequestToDatabase,

  // Validators
  validateUpdateRequest,
  validateFilters,

  // Error handlers
  createErrorResponse,
  createSuccessResponse,
  checkSupabaseClient,
} from '@/lib/staff-dashboard/demo-bookings';
```

### Typical API Route Structure

```typescript
export async function GET(request: NextRequest) {
  // 1. Authenticate
  const user = await requireStaffAuth();
  if (!user) return createUnauthorizedResponse();

  // 2. Check Supabase
  const error = checkSupabaseClient(supabaseAdmin);
  if (error) return error;

  // 3. Parse & validate
  const filters = { ... };
  const validation = validateFilters(filters);
  if (!validation.valid) return createBadRequestResponse(validation.errors);

  // 4. Query
  const result = await fetchDemoBookings(supabaseAdmin, filters);
  if (result.error) return createDatabaseErrorResponse(result.error, 'operation');

  // 5. Transform & return
  const bookings = mapBookingsToFrontend(result.data);
  return createSuccessResponse({ bookings });
}
```

---

## 📋 Maintenance Checklist

When modifying this module, ensure:

- [ ] TypeScript types are updated in `types.ts`
- [ ] Constants are updated if adding enums/configs
- [ ] Mappers handle new fields correctly
- [ ] Validators cover new fields
- [ ] Tests are updated (integration tests most important)
- [ ] Documentation is updated (this README)
- [ ] API route JSDoc is updated
- [ ] No breaking changes to existing API responses

---

## 🐛 Troubleshooting

### Issue: "supabaseAdmin is null"

**Cause:** `SUPABASE_SERVICE_ROLE_KEY` not set in environment.

**Fix:**
```bash
# .env.local
SUPABASE_SERVICE_ROLE_KEY=eyJhbGci...
```

### Issue: "Unauthorized" even when logged in

**Cause:** User role is `CUSTOMER`, not staff.

**Fix:** Ensure test user has `ADMIN` or `STAFF` role in database.

### Issue: Bookings not appearing

**Causes:**
1. Supabase not running → `npx supabase start`
2. Migrations not applied → Run migration scripts
3. RLS blocking query → Use `supabaseAdmin` not `supabase`

### Issue: Search returns no results

**Cause:** Search term might be too restrictive or has invalid characters.

**Fix:** Check `sanitizeSearchTerm` - it removes special characters.

---

## 📚 Related Documentation

- **Fix History:** `/STAFF_DASHBOARD_DEMOS_FIX.md`
- **Test Suite:** `/STAFF_DASHBOARD_DEMOS_TESTING.md`
- **Database Schema:** `/supabase/migrations/20251020_create_demo_bookings.sql`
- **Dual Database Arch:** `/DUAL_DATABASE_ARCHITECTURE.md`
- **Staff Auth:** `/lib/staff-auth.ts`

---

## 📈 Metrics

**Lines of Code:**
- types.ts: ~200 lines (comprehensive type safety)
- constants.ts: ~140 lines (centralized configuration)
- mappers.ts: ~140 lines (data transformation)
- queries.ts: ~230 lines (database operations)
- validators.ts: ~180 lines (input validation)
- errors.ts: ~200 lines (error handling)
- **Total: ~1,090 lines of well-documented, focused code**

**API Route Code Reduction:**
- GET route: 107 lines → 40 lines (62% reduction)
- UPDATE route: 87 lines → 50 lines (42% reduction)
- **Total reduction: ~52% while adding MORE functionality**

**Benefits:**
- ✅ Easier to understand (each module has one purpose)
- ✅ Easier to test (functions are isolated)
- ✅ Easier to change (know exactly where to edit)
- ✅ Easier to extend (add new features without touching existing code)
- ✅ Type-safe throughout
- ✅ Consistent error handling
- ✅ Comprehensive documentation

---

**Last Updated:** November 16, 2025
**Module Version:** 2.0
**Maintainer:** Staff Dashboard Team
