# Sandbox Mode Implementation Summary

## Overview
Sandbox/Test mode provides complete data isolation for testing. Users can toggle between Live and Test modes via a switch in the header. Each mode has its own:
- Database schema (for tenant-specific data like appointments, resources, services)
- Customer records (filtered by `is_sandbox` flag on User model)

## Architecture

### Backend Components

1. **Tenant Model** (`core/models.py`)
   - `sandbox_schema_name`: PostgreSQL schema for sandbox data (e.g., `demo_sandbox`)
   - `sandbox_enabled`: Boolean to enable/disable sandbox for tenant
   - Auto-generates sandbox schema name on save

2. **SandboxModeMiddleware** (`core/middleware.py:16-118`)
   - Switches database schema based on:
     - API token prefix (`ss_test_*` = sandbox, `ss_live_*` = live)
     - `X-Sandbox-Mode: true` header
     - Session value `sandbox_mode`
   - Sets `request.sandbox_mode = True/False` for views to use
   - MUST run AFTER `SessionMiddleware` in middleware order

3. **User Model** (`smoothschedule/users/models.py`)
   - `is_sandbox`: Boolean field to mark sandbox customers
   - Live customers have `is_sandbox=False`, test customers have `is_sandbox=True`

4. **API Endpoints** (`schedule/api_views.py`)
   - `GET /api/sandbox/status/` - Get current sandbox state
   - `POST /api/sandbox/toggle/` - Toggle sandbox mode (sets session)

5. **CustomerViewSet** (`schedule/views.py:199-249`)
   - Filters customers by `request.sandbox_mode`
   - `perform_create` sets `is_sandbox` based on current mode

6. **StaffViewSet** (`schedule/views.py:302-366`)
   - Filters staff by `request.sandbox_mode`
   - Staff created via invitations inherit sandbox mode from request

7. **TicketViewSet** (`tickets/views.py:65-167`)
   - Filters tickets by `request.sandbox_mode` (except PLATFORM tickets)
   - `perform_create` sets `is_sandbox` based on current mode
   - PLATFORM tickets are always created in live mode

8. **PublicCustomerViewSet** (`public_api/views.py:888-968`)
   - Also filters by sandbox mode for API customers

9. **APIToken Model** (`public_api/models.py`)
   - `is_sandbox`: Boolean for token type
   - Key prefixes: `ss_test_*` (sandbox) or `ss_live_*` (live)

### Frontend Components

1. **SandboxContext** (`contexts/SandboxContext.tsx`)
   - Provides `isSandbox`, `sandboxEnabled`, `toggleSandbox`, `isToggling`
   - Syncs state to localStorage for API client

2. **SandboxToggle** (`components/SandboxToggle.tsx`)
   - Toggle switch component with Live/Test labels

3. **SandboxBanner** (`components/SandboxBanner.tsx`)
   - Orange warning banner shown in test mode

4. **API Client** (`api/client.ts:23-51`)
   - Reads `localStorage.getItem('sandbox_mode')`
   - Adds `X-Sandbox-Mode: true` header when in sandbox

5. **BusinessLayout** (`layouts/BusinessLayout.tsx`)
   - Wrapped with `SandboxProvider`
   - Shows `SandboxBanner` when in test mode

6. **TopBar** (`components/TopBar.tsx`)
   - Includes `SandboxToggle` component

### Configuration

1. **CORS** (`config/settings/local.py:75-78`)
   - `x-sandbox-mode` added to `CORS_ALLOW_HEADERS`

2. **Middleware Order** (`config/settings/multitenancy.py:89-122`)
   - SandboxModeMiddleware MUST come AFTER SessionMiddleware

## Database Schemas

Each tenant has two schemas:
- `{tenant_name}` - Live data (e.g., `demo`)
- `{tenant_name}_sandbox` - Test data (e.g., `demo_sandbox`)

Schemas created via: `python manage.py create_sandbox_schemas`

## What's Isolated

| Data Type | Isolation Method |
|-----------|------------------|
| Appointments/Events | Schema switching (automatic) |
| Resources | Schema switching (automatic) |
| Services | Schema switching (automatic) |
| Payments | Schema switching (automatic) |
| Notifications | Schema switching (automatic) |
| Communication | Schema switching (automatic) |
| Customers | `is_sandbox` field on User model |
| Staff Members | `is_sandbox` field on User model |
| Tickets (CUSTOMER/STAFF_REQUEST/INTERNAL) | `is_sandbox` field on Ticket model |
| Tickets (PLATFORM) | NOT isolated (always live - platform support) |
| Business Settings (Tenant) | NOT isolated (shared between modes) |

## Key Files Modified

### Backend
- `core/models.py` - Tenant sandbox fields
- `core/middleware.py` - SandboxModeMiddleware
- `smoothschedule/users/models.py` - User.is_sandbox field
- `smoothschedule/users/api_views.py` - accept_invitation_view sets is_sandbox
- `schedule/views.py` - CustomerViewSet and StaffViewSet sandbox filtering
- `schedule/api_views.py` - sandbox_status_view, sandbox_toggle_view
- `tickets/models.py` - Ticket.is_sandbox field
- `tickets/views.py` - TicketViewSet sandbox filtering
- `public_api/models.py` - APIToken.is_sandbox
- `public_api/views.py` - PublicCustomerViewSet sandbox filtering
- `config/settings/local.py` - CORS headers
- `config/settings/multitenancy.py` - Middleware order, tickets in SHARED_APPS

### Frontend
- `src/api/sandbox.ts` - API functions
- `src/api/client.ts` - X-Sandbox-Mode header
- `src/hooks/useSandbox.ts` - React Query hooks
- `src/contexts/SandboxContext.tsx` - Context provider
- `src/components/SandboxToggle.tsx` - Toggle UI
- `src/components/SandboxBanner.tsx` - Warning banner
- `src/components/TopBar.tsx` - Added toggle
- `src/layouts/BusinessLayout.tsx` - Provider + banner
- `src/i18n/locales/en.json` - Translations

## Migrations
```bash
# Migrations for User.is_sandbox and Ticket.is_sandbox fields
cd /home/poduck/Desktop/smoothschedule2/smoothschedule
docker compose -f docker-compose.local.yml exec django python manage.py migrate
```

## Current State
- ✅ Sandbox mode toggle works
- ✅ CORS configured for X-Sandbox-Mode header
- ✅ Customer isolation by is_sandbox field implemented
- ✅ Staff isolation by is_sandbox field implemented
- ✅ Ticket isolation by is_sandbox field implemented (except PLATFORM tickets)
- ✅ Appointments/Events/Resources/Services automatically isolated via schema switching
- ✅ Existing users are `is_sandbox=False` (live)
- ✅ Existing tickets are `is_sandbox=False` (live)
- ✅ Test mode shows empty data (clean sandbox)
