Files
smoothschedule/frontend/CLAUDE.md
poduck 8c52d6a275 refactor: Extract reusable UI components and add TDD documentation
- Add comprehensive TDD documentation to CLAUDE.md with coverage requirements and examples
- Extract reusable UI components to frontend/src/components/ui/ (Modal, FormInput, Button, Alert, etc.)
- Add shared constants (schedulePresets) and utility hooks (useCrudMutation, useFormValidation)
- Update frontend/CLAUDE.md with component documentation and usage examples
- Refactor CreateTaskModal to use shared components and constants
- Fix test assertions to be more robust and accurate across all test files

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-10 15:27:27 -05:00

9.5 KiB

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:

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

// Modal with form
<Modal isOpen={isOpen} onClose={onClose} title="Edit Resource" size="lg">
  <FormInput
    label="Name"
    value={name}
    onChange={(e) => setName(e.target.value)}
    error={errors.name}
    required
  />
  <FormSelect
    label="Type"
    value={type}
    onChange={(e) => setType(e.target.value)}
    options={[
      { value: 'STAFF', label: 'Staff' },
      { value: 'ROOM', label: 'Room' },
    ]}
  />
</Modal>

// Alert messages
{error && <ErrorMessage message={error} />}
{success && <SuccessMessage message="Saved successfully!" />}

// Tabs
<TabGroup
  tabs={[
    { id: 'details', label: 'Details' },
    { id: 'schedule', label: 'Schedule' },
  ]}
  activeTab={activeTab}
  onChange={setActiveTab}
/>

Utility Hooks

useCrudMutation

Factory hook for CRUD mutations with React Query:

import { useCrudMutation, createCrudHooks } from '../hooks/useCrudMutation';

// Simple usage
const createResource = useCrudMutation<Resource, CreateResourceData>({
  endpoint: '/resources',
  method: 'POST',
  invalidateKeys: [['resources']],
});

// Create all CRUD hooks at once
const { useCreate, useUpdate, useDelete } = createCrudHooks<Resource>('/resources', 'resources');

useFormValidation

Schema-based form validation:

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

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

Tokens are stored in cookies with domain=.lvh.me to enable cross-subdomain access:

// 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:

// 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

# Install dependencies
npm install

# Start dev server
npm run dev

# Access at http://platform.lvh.me:5173

Running Tests

# 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:

    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