- 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>
746 lines
18 KiB
Markdown
746 lines
18 KiB
Markdown
# 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.
|
|
|
|
```tsx
|
|
// 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.
|
|
|
|
```typescript
|
|
// 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.
|
|
|
|
```tsx
|
|
// 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.
|
|
|
|
```tsx
|
|
// 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.
|
|
|
|
```tsx
|
|
// 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.
|
|
|
|
```tsx
|
|
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.
|
|
|
|
```tsx
|
|
// 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.
|
|
|
|
```tsx
|
|
// 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.
|
|
|
|
```tsx
|
|
// 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.
|
|
|
|
```tsx
|
|
// 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.
|
|
|
|
```typescript
|
|
// 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.
|
|
|
|
```typescript
|
|
// 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.
|
|
|
|
```typescript
|
|
// 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.
|
|
|
|
```typescript
|
|
// 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.
|
|
|
|
```typescript
|
|
// 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.
|
|
|
|
```tsx
|
|
// 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.
|
|
|
|
```tsx
|
|
// 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.
|
|
|
|
```tsx
|
|
// 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.
|
|
|
|
```tsx
|
|
// 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.
|
|
|
|
```typescript
|
|
// 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.
|
|
|
|
```typescript
|
|
// 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.** |