Files
blackcanyontickets/reactrebuild0825/docs/architecture.md
dzinesco f777ef760b docs: add comprehensive Phase 2 documentation
- Create detailed README.md with quick start and demo accounts
- Add complete UI primitives documentation with examples
- Document architecture patterns and design decisions
- Update REBUILD_PLAN.md marking Phase 2 as complete
- Include component usage guides and testing documentation
- Document accessibility compliance and performance considerations

Documentation provides complete developer onboarding experience
with practical examples and architectural guidance.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-16 12:46:03 -06:00

18 KiB

Architecture Documentation

Comprehensive guide to the Black Canyon Tickets React rebuild architecture, design patterns, and technical decisions.

Project Structure Overview

src/
├── components/
│   ├── ui/                    # Design system primitives
│   ├── layout/                # Application layout system
│   ├── auth/                  # Authentication components
│   ├── loading/               # Loading states and skeletons
│   ├── errors/                # Error boundaries and fallbacks
│   ├── events/                # Event domain components
│   ├── tickets/               # Ticketing domain components
│   ├── checkout/              # Purchase flow components
│   ├── billing/               # Payment and fee components
│   └── scanning/              # QR scanning components
├── pages/                     # Route-level components
├── contexts/                  # React Context providers
├── hooks/                     # Custom React hooks
├── types/                     # TypeScript type definitions
├── design-tokens/             # Design system configuration
├── styles/                    # CSS files and utilities
└── utils/                     # Utility functions

Architectural Principles

1. Component Composition

Philosophy: Build complex UIs by composing smaller, focused components rather than creating monolithic components.

// Bad: Monolithic component
function EventPage({ eventId }) {
  return (
    <div className="event-page">
      <header>...</header>
      <nav>...</nav>
      <main>
        <div className="event-details">...</div>
        <div className="ticket-selection">...</div>
        <div className="purchase-form">...</div>
      </main>
      <footer>...</footer>
    </div>
  );
}

// Good: Composed from smaller components
function EventPage({ eventId }) {
  return (
    <AppLayout>
      <EventDetails eventId={eventId} />
      <TicketSelection eventId={eventId} />
      <PurchaseFlow eventId={eventId} />
    </AppLayout>
  );
}

2. Design Token System

Philosophy: Centralize design decisions in a token system that enables consistent theming and maintainable styles.

design-tokens/
├── base.json              # Core design tokens
└── themes/
    ├── light.json         # Light theme overrides
    └── dark.json          # Dark theme overrides

Token Categories:

  • Colors: Semantic color system (primary, surface, text, border)
  • Typography: Font sizes, line heights, font families
  • Spacing: Consistent spacing scale (1-20)
  • Border Radius: Corner radius values (sm, md, lg, xl, 2xl)
  • Shadows: Elevation system with multiple levels

3. Type-Driven Development

Philosophy: Use TypeScript's type system to catch errors early and provide excellent developer experience.

// Comprehensive type definitions
interface Event {
  id: string;
  title: string;
  description: string;
  date: string;
  venue: string;
  organization: Organization;
  ticketTypes: TicketType[];
  status: EventStatus;
}

// Union types for controlled values
type EventStatus = 'draft' | 'published' | 'active' | 'completed' | 'cancelled';
type UserRole = 'user' | 'admin' | 'super_admin';

// Strict component props
interface EventCardProps {
  event: Event;
  showActions?: boolean;
  onEdit?: (event: Event) => void;
  onDelete?: (eventId: string) => void;
}

Design Patterns

1. Compound Components

Use Case: Complex components with multiple related parts that work together.

// Card component with sub-components
function Card({ children, variant = 'default', ...props }) {
  return (
    <div className={cardVariants[variant]} {...props}>
      {children}
    </div>
  );
}

Card.Header = function CardHeader({ children, className = '', ...props }) {
  return (
    <div className={`card-header ${className}`} {...props}>
      {children}
    </div>
  );
};

Card.Content = function CardContent({ children, className = '', ...props }) {
  return (
    <div className={`card-content ${className}`} {...props}>
      {children}
    </div>
  );
};

// Usage
<Card variant="elevated">
  <Card.Header>
    <h3>Event Details</h3>
  </Card.Header>
  <Card.Content>
    <EventInfo event={event} />
  </Card.Content>
</Card>

2. Render Props Pattern

Use Case: Sharing stateful logic between components while maintaining flexibility in rendering.

// ProtectedRoute component using render props
function ProtectedRoute({ 
  permission, 
  fallback, 
  children 
}: ProtectedRouteProps) {
  const { user, hasPermission } = useAuth();
  
  if (!user) {
    return <Navigate to="/login" />;
  }
  
  if (permission && !hasPermission(permission)) {
    return fallback || <AccessDenied />;
  }
  
  return <>{children}</>;
}

// Usage
<ProtectedRoute 
  permission="admin" 
  fallback={<AdminAccessRequired />}
>
  <AdminDashboard />
</ProtectedRoute>

3. Custom Hook Pattern

Use Case: Extracting and reusing stateful logic across components.

// useAuth hook encapsulates authentication logic
function useAuth() {
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState(true);
  
  const login = useCallback(async (credentials: LoginCredentials) => {
    setLoading(true);
    try {
      const user = await authService.login(credentials);
      setUser(user);
      return { success: true, user };
    } catch (error) {
      return { success: false, error: error.message };
    } finally {
      setLoading(false);
    }
  }, []);
  
  const logout = useCallback(() => {
    setUser(null);
    authService.logout();
  }, []);
  
  const hasPermission = useCallback((permission: Permission) => {
    return user?.permissions.includes(permission) ?? false;
  }, [user]);
  
  return {
    user,
    loading,
    login,
    logout,
    hasPermission,
    isAuthenticated: !!user,
  };
}

State Management Strategy

1. Component State (useState)

Use For: Local component state that doesn't need to be shared.

function TicketSelector({ ticketType }) {
  const [quantity, setQuantity] = useState(0);
  const [loading, setLoading] = useState(false);
  
  return (
    <div>
      <QuantitySelector value={quantity} onChange={setQuantity} />
      <Button loading={loading} onClick={handlePurchase}>
        Add to Cart
      </Button>
    </div>
  );
}

2. Context State (React Context)

Use For: Application-wide state that needs to be shared across many components.

// Theme context for global theme management
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);

function ThemeProvider({ children }: { children: React.ReactNode }) {
  const [theme, setTheme] = useState<Theme>('dark');
  
  const toggleTheme = useCallback(() => {
    setTheme(prev => prev === 'light' ? 'dark' : 'light');
  }, []);
  
  return (
    <ThemeContext.Provider value={{ theme, setTheme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

// Auth context for user authentication state
const AuthContext = createContext<AuthContextType | undefined>(undefined);

function AuthProvider({ children }: { children: React.ReactNode }) {
  const auth = useAuth(); // Custom hook with auth logic
  
  return (
    <AuthContext.Provider value={auth}>
      {children}
    </AuthContext.Provider>
  );
}

3. URL State (React Router)

Use For: State that should be reflected in the URL for bookmarking and sharing.

// Search and filter state in URL parameters
function EventsPage() {
  const [searchParams, setSearchParams] = useSearchParams();
  
  const search = searchParams.get('search') || '';
  const category = searchParams.get('category') || 'all';
  
  const updateSearch = (newSearch: string) => {
    setSearchParams(prev => {
      prev.set('search', newSearch);
      return prev;
    });
  };
  
  return (
    <div>
      <SearchInput value={search} onChange={updateSearch} />
      <CategoryFilter value={category} onChange={updateCategory} />
      <EventList search={search} category={category} />
    </div>
  );
}

Error Handling Architecture

1. Error Boundaries

Strategy: Catch React component errors and provide graceful fallbacks.

// App-level error boundary
class AppErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
  constructor(props: ErrorBoundaryProps) {
    super(props);
    this.state = { hasError: false, error: null };
  }
  
  static getDerivedStateFromError(error: Error): ErrorBoundaryState {
    return { hasError: true, error };
  }
  
  componentDidCatch(error: Error, errorInfo: ErrorInfo) {
    console.error('Error caught by boundary:', error, errorInfo);
    // Report to error tracking service
  }
  
  render() {
    if (this.state.hasError) {
      return this.props.fallback || <ErrorFallback error={this.state.error} />;
    }
    
    return this.props.children;
  }
}

// Usage
<AppErrorBoundary fallback={<GlobalErrorFallback />}>
  <App />
</AppErrorBoundary>

2. Loading States

Strategy: Provide consistent loading experiences across the application.

// Suspense for route-level loading
function App() {
  return (
    <Router>
      <Routes>
        <Route path="/" element={
          <Suspense fallback={<RouteSuspense />}>
            <HomePage />
          </Suspense>
        } />
      </Routes>
    </Router>
  );
}

// Component-level loading with Skeleton
function EventCard({ eventId }: { eventId: string }) {
  const { event, loading, error } = useEvent(eventId);
  
  if (loading) return <EventCardSkeleton />;
  if (error) return <EventCardError error={error} />;
  if (!event) return <EventNotFound />;
  
  return <EventCardContent event={event} />;
}

Authentication Architecture

1. Mock Authentication System

Design: Simulates real authentication without external dependencies.

// Mock auth service
class MockAuthService {
  private static users: User[] = [
    {
      id: '1',
      email: 'demo@blackcanyontickets.com',
      role: 'user',
      permissions: ['events:read', 'tickets:purchase']
    },
    {
      id: '2', 
      email: 'admin@blackcanyontickets.com',
      role: 'admin',
      permissions: ['events:read', 'events:write', 'users:read']
    }
  ];
  
  async login(credentials: LoginCredentials): Promise<User> {
    const user = this.users.find(u => u.email === credentials.email);
    if (!user) throw new Error('Invalid credentials');
    
    // Simulate API delay
    await new Promise(resolve => setTimeout(resolve, 1000));
    
    // Store in localStorage for persistence
    localStorage.setItem('auth_user', JSON.stringify(user));
    return user;
  }
  
  logout(): void {
    localStorage.removeItem('auth_user');
  }
  
  getCurrentUser(): User | null {
    const stored = localStorage.getItem('auth_user');
    return stored ? JSON.parse(stored) : null;
  }
}

2. Permission System

Design: Role-based access control with granular permissions.

// Permission definitions
type Permission = 
  | 'events:read' | 'events:write' | 'events:delete'
  | 'tickets:read' | 'tickets:purchase' | 'tickets:scan'
  | 'users:read' | 'users:write'
  | 'analytics:read' | 'settings:write';

// Role definitions
const ROLE_PERMISSIONS: Record<UserRole, Permission[]> = {
  user: [
    'events:read',
    'tickets:read',
    'tickets:purchase'
  ],
  admin: [
    'events:read', 'events:write',
    'tickets:read', 'tickets:scan',
    'users:read',
    'analytics:read'
  ],
  super_admin: [
    'events:read', 'events:write', 'events:delete',
    'tickets:read', 'tickets:scan',
    'users:read', 'users:write',
    'analytics:read',
    'settings:write'
  ]
};

Component Testing Strategy

1. Unit Testing

Focus: Individual component behavior and props handling.

// Button component tests
import { render, screen, fireEvent } from '@testing-library/react';
import { Button } from '@/components/ui/Button';

describe('Button Component', () => {
  test('renders with correct variant styles', () => {
    render(<Button variant="primary">Click me</Button>);
    const button = screen.getByRole('button');
    expect(button).toHaveClass('bg-primary');
  });
  
  test('handles click events', () => {
    const handleClick = jest.fn();
    render(<Button onClick={handleClick}>Click me</Button>);
    
    fireEvent.click(screen.getByRole('button'));
    expect(handleClick).toHaveBeenCalledTimes(1);
  });
  
  test('displays loading state', () => {
    render(<Button loading>Loading</Button>);
    expect(screen.getByRole('button')).toBeDisabled();
    expect(screen.getByText('Loading')).toBeInTheDocument();
  });
});

2. Integration Testing with Playwright

Focus: End-to-end user workflows and cross-component interactions.

// Authentication flow test
import { test, expect } from '@playwright/test';

test('user can log in and access dashboard', async ({ page }) => {
  await page.goto('/login');
  
  // Fill login form
  await page.fill('[data-testid="email-input"]', 'demo@blackcanyontickets.com');
  await page.fill('[data-testid="password-input"]', 'demo123');
  
  // Submit and verify redirect
  await page.click('[data-testid="login-button"]');
  await expect(page).toHaveURL('/dashboard');
  
  // Verify user is authenticated
  await expect(page.getByText('Welcome back')).toBeVisible();
});

3. Visual Regression Testing

Focus: Ensure UI changes don't break visual design.

// Visual tests with Playwright
test('homepage renders correctly', async ({ page }) => {
  await page.goto('/');
  await expect(page).toHaveScreenshot('homepage.png');
});

test('login form in both themes', async ({ page }) => {
  // Test light theme
  await page.goto('/login');
  await page.getByTestId('theme-toggle').click(); // Switch to light
  await expect(page.getByTestId('login-form')).toHaveScreenshot('login-light.png');
  
  // Test dark theme  
  await page.getByTestId('theme-toggle').click(); // Switch to dark
  await expect(page.getByTestId('login-form')).toHaveScreenshot('login-dark.png');
});

Performance Architecture

1. Code Splitting

Strategy: Split code at route boundaries and for large dependencies.

// Route-based code splitting
const HomePage = lazy(() => import('@/pages/HomePage'));
const DashboardPage = lazy(() => import('@/pages/DashboardPage'));
const EventsPage = lazy(() => import('@/pages/EventsPage'));

function App() {
  return (
    <Router>
      <Routes>
        <Route path="/" element={
          <Suspense fallback={<RouteSuspense />}>
            <HomePage />
          </Suspense>
        } />
        <Route path="/dashboard" element={
          <Suspense fallback={<RouteSuspense />}>
            <DashboardPage />
          </Suspense>
        } />
      </Routes>
    </Router>
  );
}

2. Component Optimization

Strategy: Use React.memo and useMemo to prevent unnecessary re-renders.

// Memoized component to prevent re-renders
const EventCard = memo(function EventCard({ event, onEdit }: EventCardProps) {
  const formattedDate = useMemo(() => {
    return formatDate(event.date);
  }, [event.date]);
  
  return (
    <Card>
      <h3>{event.title}</h3>
      <p>{formattedDate}</p>
      <Button onClick={() => onEdit?.(event)}>Edit</Button>
    </Card>
  );
});

// Optimized list rendering
function EventList({ events }: { events: Event[] }) {
  const sortedEvents = useMemo(() => {
    return [...events].sort((a, b) => 
      new Date(a.date).getTime() - new Date(b.date).getTime()
    );
  }, [events]);
  
  return (
    <div>
      {sortedEvents.map(event => (
        <EventCard key={event.id} event={event} />
      ))}
    </div>
  );
}

Accessibility Architecture

1. Semantic HTML Foundation

Strategy: Use semantic HTML elements that provide built-in accessibility.

// Good: Semantic structure
function EventForm() {
  return (
    <form onSubmit={handleSubmit}>
      <fieldset>
        <legend>Event Details</legend>
        
        <label htmlFor="title">Event Title</label>
        <input id="title" type="text" required />
        
        <label htmlFor="description">Description</label>
        <textarea id="description" />
      </fieldset>
      
      <button type="submit">Create Event</button>
    </form>
  );
}

2. ARIA Enhancement

Strategy: Enhance semantic HTML with ARIA attributes where needed.

// Complex component with ARIA
function Select({ options, value, onChange, label }: SelectProps) {
  const [isOpen, setIsOpen] = useState(false);
  const [focusedIndex, setFocusedIndex] = useState(-1);
  
  return (
    <div className="select-container">
      <label id="select-label">{label}</label>
      
      <button
        type="button"
        aria-labelledby="select-label"
        aria-expanded={isOpen}
        aria-haspopup="listbox"
        onClick={() => setIsOpen(!isOpen)}
      >
        {value || 'Select option'}
      </button>
      
      {isOpen && (
        <ul role="listbox" aria-labelledby="select-label">
          {options.map((option, index) => (
            <li
              key={option.value}
              role="option"
              aria-selected={value === option.value}
              className={focusedIndex === index ? 'focused' : ''}
              onClick={() => onChange(option.value)}
            >
              {option.label}
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

Build and Deployment Architecture

1. Vite Configuration

Strategy: Optimize builds for production with proper chunk splitting.

// vite.config.ts
export default defineConfig({
  plugins: [react()],
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['react', 'react-dom'],
          router: ['react-router-dom'],
          ui: ['lucide-react']
        }
      }
    }
  },
  css: {
    postcss: {
      plugins: [tailwindcss, autoprefixer]
    }
  }
});

2. Environment Configuration

Strategy: Support multiple environments with appropriate configurations.

// Environment-specific configuration
const config = {
  development: {
    apiUrl: 'http://localhost:3001',
    enableDevTools: true,
    logLevel: 'debug'
  },
  production: {
    apiUrl: 'https://api.blackcanyontickets.com',
    enableDevTools: false,
    logLevel: 'error'
  }
};

export default config[process.env.NODE_ENV || 'development'];

Architecture designed with CrispyGoat principles - scalable, maintainable, and developer-friendly.