# SmoothSchedule Frontend Development Guide ## Project Overview This is the React frontend for SmoothSchedule, a multi-tenant scheduling platform. **See also:** `/home/poduck/Desktop/smoothschedule2/smoothschedule/CLAUDE.md` for backend documentation. ## Key Paths ``` /home/poduck/Desktop/smoothschedule2/ ├── frontend/ # This React frontend │ ├── src/ │ │ ├── api/client.ts # Axios API client │ │ ├── components/ # Feature components │ │ │ └── ui/ # Reusable UI components (see below) │ │ ├── constants/ # Shared constants │ │ │ └── schedulePresets.ts # Schedule/cron presets │ │ ├── hooks/ # React Query hooks (useResources, useAuth, etc.) │ │ ├── pages/ # Page components │ │ ├── types.ts # TypeScript interfaces │ │ ├── i18n/locales/en.json # English translations │ │ └── utils/cookies.ts # Cookie utilities │ ├── .env.development # Frontend env vars │ └── vite.config.ts # Vite configuration │ └── smoothschedule/ # Django backend (runs in Docker!) ├── docker-compose.local.yml # Docker config ├── .envs/.local/ # Backend env vars ├── config/settings/ # Django settings └── smoothschedule/ ├── schedule/ # Core scheduling app └── users/ # User management ``` ## Reusable UI Components All reusable UI components are in `src/components/ui/`. Import from the barrel file: ```typescript import { Modal, FormInput, Button, Alert } from '../components/ui'; ``` ### Available Components | Component | Description | |-----------|-------------| | **Modal** | Reusable modal dialog with header, body, footer | | **ModalFooter** | Standardized modal footer with buttons | | **FormInput** | Text input with label, error, hint support | | **FormSelect** | Select dropdown with label, error support | | **FormTextarea** | Textarea with label, error support | | **FormCurrencyInput** | ATM-style currency input (cents) | | **CurrencyInput** | Raw currency input component | | **Button** | Button with variants, loading state, icons | | **SubmitButton** | Pre-configured submit button | | **Alert** | Alert banner (error, success, warning, info) | | **ErrorMessage** | Error alert shorthand | | **SuccessMessage** | Success alert shorthand | | **TabGroup** | Tab navigation (default, pills, underline) | | **StepIndicator** | Multi-step wizard indicator | | **LoadingSpinner** | Loading spinner with variants | | **PageLoading** | Full page loading state | | **Card** | Card container with header/body/footer | | **EmptyState** | Empty state placeholder | | **Badge** | Status badges | ### Usage Examples ```typescript // Modal with form setName(e.target.value)} error={errors.name} required /> setType(e.target.value)} options={[ { value: 'STAFF', label: 'Staff' }, { value: 'ROOM', label: 'Room' }, ]} /> // Alert messages {error && } {success && } // Tabs ``` ## Utility Hooks ### useCrudMutation Factory hook for CRUD mutations with React Query: ```typescript import { useCrudMutation, createCrudHooks } from '../hooks/useCrudMutation'; // Simple usage const createResource = useCrudMutation({ endpoint: '/resources', method: 'POST', invalidateKeys: [['resources']], }); // Create all CRUD hooks at once const { useCreate, useUpdate, useDelete } = createCrudHooks('/resources', 'resources'); ``` ### useFormValidation Schema-based form validation: ```typescript import { useFormValidation, required, email, minLength } from '../hooks/useFormValidation'; const schema = { email: [required('Email is required'), email('Invalid email')], password: [required(), minLength(8, 'Min 8 characters')], }; const { errors, validateForm, isValid } = useFormValidation(schema); const handleSubmit = () => { if (validateForm(formData)) { // Submit } }; ``` ## Constants ### Schedule Presets ```typescript import { SCHEDULE_PRESETS, TRIGGER_OPTIONS, OFFSET_PRESETS } from '../constants/schedulePresets'; ``` ## Local Development Domain Setup ### Why lvh.me instead of localhost? This project uses **lvh.me** for local development instead of `localhost` due to cookie domain restrictions in RFC 6265. **The Problem with localhost:** - Browsers reject cookies with `domain=.localhost` for security reasons - `localhost` is treated as a special-use domain where domain cookies don't work - Cannot share cookies across subdomains like `platform.localhost` and `business.localhost` **The Solution - lvh.me:** - `lvh.me` is a public DNS service that resolves all `*.lvh.me` domains to `127.0.0.1` - Browsers allow setting cookies with `domain=.lvh.me` - Cookies are accessible across all subdomains (platform.lvh.me, business1.lvh.me, etc.) - No /etc/hosts configuration needed - it just works! ### Development URLs Use these URLs for local development: - **Base domain:** `http://lvh.me:5173` - **Platform dashboard:** `http://platform.lvh.me:5173` - **Business subdomains:** `http://{subdomain}.lvh.me:5173` ### Multi-Tenant Architecture The application uses subdomain-based multi-tenancy: 1. **Platform Users** (superuser, platform_manager, platform_support) - Access the app at `http://platform.lvh.me:5173` - See platform dashboard and administrative features 2. **Business Users** (owner, manager, staff, resource) - Access the app at `http://{business_subdomain}.lvh.me:5173` - See business-specific dashboard and features ### Cookie-Based Authentication Tokens are stored in cookies with `domain=.lvh.me` to enable cross-subdomain access: ```typescript // Set cookie accessible across all *.lvh.me subdomains setCookie('access_token', token, 7); // domain=.lvh.me // Cookie is accessible on: // - platform.lvh.me:5173 // - business1.lvh.me:5173 // - business2.lvh.me:5173 // etc. ``` **Key Files:** - `/src/utils/cookies.ts` - Cookie utilities with cross-subdomain support - `/src/hooks/useAuth.ts` - Authentication hooks using cookies - `/src/api/client.ts` - API client with cookie-based auth ### Login Flow 1. User navigates to `http://platform.lvh.me:5173` 2. If not authenticated, shows login page 3. User enters credentials and submits 4. Backend validates and returns JWT tokens + user data 5. Tokens stored in cookies with `domain=.lvh.me` 6. User data stored in React Query cache 7. App checks user role: - Platform users: Stay on platform.lvh.me - Business users: Redirect to {business_subdomain}.lvh.me 8. Cookies accessible on target subdomain - user sees dashboard ### Testing with Playwright Tests use lvh.me for proper subdomain testing: ```typescript // Start on platform subdomain await page.goto('http://platform.lvh.me:5173'); // Login sets cookies with domain=.lvh.me await page.getByPlaceholder(/username/i).fill('poduck'); await page.getByPlaceholder(/password/i).fill('starry12'); await page.getByRole('button', { name: /sign in/i }).click(); // Cookies accessible, dashboard loads await expect(page.getByRole('heading', { name: /platform dashboard/i })).toBeVisible(); ``` ### Running the Development Server ```bash # Install dependencies npm install # Start dev server npm run dev # Access at http://platform.lvh.me:5173 ``` ### Running Tests ```bash # Run all tests npm test # Run E2E tests npx playwright test # Run specific test file npx playwright test login-flow.spec.ts # Run with UI npx playwright test --ui ``` ## Production Deployment In production, replace `lvh.me` with your actual domain: 1. Update `src/utils/cookies.ts`: ```typescript const domain = window.location.hostname.includes('yourdomain.com') ? '.yourdomain.com' : window.location.hostname; ``` 2. Configure DNS: - `platform.yourdomain.com` → Your server IP - `*.yourdomain.com` → Your server IP (wildcard for business subdomains) 3. SSL certificates: - Get wildcard certificate for `*.yourdomain.com` - Or use Let's Encrypt with wildcard support ## Troubleshooting ### Cookies not working? - Make sure you're using `lvh.me`, not `localhost` - Check browser DevTools → Application → Cookies - Verify `domain=.lvh.me` is set on cookies - Clear cookies and try again ### Redirect issues? - Check `/src/pages/LoginPage.tsx` redirect logic - Verify user role and business_subdomain in response - Check browser console for navigation errors ### Can't access lvh.me? - Verify internet connection (lvh.me requires DNS lookup) - Try `ping lvh.me` - should resolve to `127.0.0.1` - Alternative: Use `127.0.0.1.nip.io` (similar service) ## References - [RFC 6265 - HTTP State Management Mechanism](https://datatracker.ietf.org/doc/html/rfc6265) - [lvh.me DNS service](http://lvh.me) - [Cookie Domain Attribute Rules](https://stackoverflow.com/questions/1062963/how-do-browser-cookie-domains-work)