Architecture Overview

WI-CARPOOL follows a modern, scalable architecture built on React with TypeScript, implementing clean architecture principles and separation of concerns. The application is designed as a Progressive Web App (PWA) with offline capabilities and mobile-first responsive design.

Architecture Pattern: The application follows a layered architecture with clear separation between presentation, business logic, and data access layers.

High-Level Architecture

System Architecture Diagram

┌─────────────────────────────────────────────────────────┐
│                    Client Layer                         │
├─────────────────────────────────────────────────────────┤
│  React Components (Pages, UI Components, Layouts)      │
│  ├─ Authentication Components                           │
│  ├─ Dashboard Components                                │
│  ├─ Ride Management Components                          │
│  ├─ Payment Components                                  │
│  └─ Chat/Messaging Components                           │
├─────────────────────────────────────────────────────────┤
│                  State Management                       │
│  ├─ React Query (Server State)                         │
│  ├─ Context API (Global State)                         │
│  └─ Local State (Component State)                      │
├─────────────────────────────────────────────────────────┤
│                  Business Logic                         │
│  ├─ Custom Hooks                                        │
│  ├─ Utility Functions                                   │
│  └─ Service Layer                                       │
├─────────────────────────────────────────────────────────┤
│                   Data Layer                            │
│  ├─ API Services                                        │
│  ├─ HTTP Client (Axios)                                │
│  └─ Local Storage                                       │
├─────────────────────────────────────────────────────────┤
│                External Services                        │
│  ├─ Backend API (pool.prattle.me)                      │
│  ├─ Payment Gateways                                    │
│  ├─ Google Maps API                                     │
│  └─ Push Notification Services                          │
└─────────────────────────────────────────────────────────┘

Component Architecture

Component Hierarchy

The application follows a hierarchical component structure with clear parent-child relationships:

Component Tree Structure

App
├── ThemeProvider
│   └── QueryClientProvider
│       └── AuthProvider
│           └── Router
│               ├── PublicRoutes
│               │   ├── HomePage
│               │   ├── SignIn/SignUp
│               │   └── LandingPages
│               └── ProtectedRoutes
│                   ├── PassengerDashboard
│                   │   ├── DashboardLayout
│                   │   ├── StatsCards
│                   │   ├── RidesList
│                   │   └── RecentActivity
│                   ├── DriverDashboard
│                   │   ├── DashboardLayout
│                   │   ├── EarningsChart
│                   │   ├── TripManagement
│                   │   └── VehicleManagement
│                   └── SharedComponents
│                       ├── Chat
│                       ├── Profile
│                       ├── Notifications
│                       └── Settings

Component Categories

📄 Page Components

Top-level route components that represent entire pages or screens in the application.

🎨 UI Components

Reusable, atomic UI components built with Shadcn/ui and Radix UI primitives.

🔧 Feature Components

Business logic components that handle specific features like ride booking or payment processing.

📐 Layout Components

Structural components that define the overall layout and navigation structure.

Data Flow Architecture

Unidirectional Data Flow

WI-CARPOOL implements a unidirectional data flow pattern for predictable state management:

Data Flow Pattern

User Action → Component → Hook/Service → API Call → State Update → UI Re-render

Example: Booking a Ride
1. User clicks "Book Ride" button
2. BookingComponent handles click event
3. useBookRide hook is triggered
4. rideService.bookRide() makes API call
5. React Query updates cache and state
6. UI components re-render with new data

State Management Strategy

State Type Management Solution Use Cases Examples
Server State React Query API data, caching, synchronization User profiles, ride lists, payments
Global Client State React Context Authentication, theme, language Auth context, currency context
Local Component State useState/useReducer Form inputs, UI toggles, local flags Form validation, modal states
URL State React Router Navigation, deep linking Current page, search parameters

Routing Architecture

Route Structure

Application Routes (src/App.tsx)

const AppRoutes = () => (
  
    {/* Public Routes */}
    } />
    } />
    } />
    } />
    } />
    
    {/* Protected Routes */}
    }>
      {/* Passenger Routes */}
      } />
      } />
      } />
      } />
      } />
      
      {/* Driver Routes */}
      } />
      } />
      } />
      } />
      
      {/* Shared Routes */}
      } />
      } />
    
    
    {/* Catch-all route */}
    } />
  
);

Route Protection Strategy

Protected Route Implementation

// src/components/ProtectedRoute.tsx
const ProtectedRoute = () => {
  const { user, isLoading } = useAuth();
  const location = useLocation();

  if (isLoading) {
    return 
Loading...
; } if (!user) { // Redirect to signin with return url return ; } return ; }; // Role-based route protection const RoleProtectedRoute = ({ allowedRoles }: { allowedRoles: string[] }) => { const { user } = useAuth(); if (!allowedRoles.includes(user?.role)) { return ; } return ; };

API Integration Architecture

Service Layer Pattern

The application uses a service layer to encapsulate all API interactions:

Service Layer Structure

// Base service pattern
export class BaseService {
  protected async request(config: AxiosRequestConfig): Promise {
    try {
      const response = await axiosInstance(config);
      return response.data;
    } catch (error) {
      throw this.handleError(error);
    }
  }

  private handleError(error: any): Error {
    // Centralized error handling logic
    if (error.response?.status === 401) {
      // Handle unauthorized access
      authService.logout();
    }
    return new Error(error.response?.data?.message || 'An error occurred');
  }
}

// Specific service implementation
export class RideService extends BaseService {
  async createRide(rideData: CreateRideRequest): Promise {
    return this.request({
      method: 'POST',
      url: '/rides',
      data: rideData
    });
  }

  async getRides(page: number = 1): Promise {
    return this.request({
      method: 'GET',
      url: `/rides?page=${page}`
    });
  }
}

Request/Response Interceptors

HTTP Client Configuration

// Request interceptor for authentication
axiosInstance.interceptors.request.use(
  (config) => {
    const token = localStorage.getItem('userToken');
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    
    // Add request ID for tracking
    config.headers['X-Request-ID'] = generateRequestId();
    
    return config;
  },
  (error) => Promise.reject(error)
);

// Response interceptor for error handling
axiosInstance.interceptors.response.use(
  (response) => {
    // Log successful requests in development
    if (process.env.NODE_ENV === 'development') {
      console.log(`API Success: ${response.config.method?.toUpperCase()} ${response.config.url}`);
    }
    return response;
  },
  (error) => {
    // Global error handling
    if (error.response?.status === 401) {
      // Token expired or invalid
      store.dispatch(logout());
      window.location.href = '/signin';
    }
    
    return Promise.reject(error);
  }
);

Security Architecture

Authentication Flow

JWT Token Management

// Token storage and management
class TokenManager {
  private static readonly TOKEN_KEY = 'wicarpool_token';
  private static readonly REFRESH_TOKEN_KEY = 'wicarpool_refresh_token';

  static setTokens(accessToken: string, refreshToken: string): void {
    localStorage.setItem(this.TOKEN_KEY, accessToken);
    localStorage.setItem(this.REFRESH_TOKEN_KEY, refreshToken);
  }

  static getAccessToken(): string | null {
    return localStorage.getItem(this.TOKEN_KEY);
  }

  static getRefreshToken(): string | null {
    return localStorage.getItem(this.REFRESH_TOKEN_KEY);
  }

  static clearTokens(): void {
    localStorage.removeItem(this.TOKEN_KEY);
    localStorage.removeItem(this.REFRESH_TOKEN_KEY);
  }

  static isTokenExpired(token: string): boolean {
    try {
      const payload = JSON.parse(atob(token.split('.')[1]));
      return payload.exp < Date.now() / 1000;
    } catch {
      return true;
    }
  }
}

// Automatic token refresh
const refreshTokenIfNeeded = async (): Promise => {
  const accessToken = TokenManager.getAccessToken();
  const refreshToken = TokenManager.getRefreshToken();

  if (accessToken && TokenManager.isTokenExpired(accessToken) && refreshToken) {
    try {
      const response = await authService.refreshToken(refreshToken);
      TokenManager.setTokens(response.accessToken, response.refreshToken);
    } catch (error) {
      TokenManager.clearTokens();
      window.location.href = '/signin';
    }
  }
};

Data Validation

Input Validation with Zod

import { z } from 'zod';

// Schema definitions
export const userRegistrationSchema = z.object({
  firstName: z.string().min(2, 'First name must be at least 2 characters'),
  lastName: z.string().min(2, 'Last name must be at least 2 characters'),
  email: z.string().email('Invalid email address'),
  phoneNumber: z.string().regex(/^\+?[1-9]\d{1,14}$/, 'Invalid phone number'),
  password: z.string()
    .min(8, 'Password must be at least 8 characters')
    .regex(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/, 'Password must contain uppercase, lowercase, and number'),
});

export const rideCreationSchema = z.object({
  pickupLocation: z.string().min(1, 'Pickup location is required'),
  dropLocation: z.string().min(1, 'Drop location is required'),
  departDate: z.string().datetime('Invalid date format'),
  pricePerPerson: z.number().min(0.01, 'Price must be greater than 0'),
  availableSeats: z.number().min(1).max(8, 'Seats must be between 1 and 8'),
});

// Usage in components
const useValidatedForm = (schema: z.ZodSchema) => {
  const form = useForm({
    resolver: zodResolver(schema),
  });

  return form;
};

Performance Architecture

Code Splitting Strategy

Route-based Code Splitting

// Lazy loading components
import { lazy, Suspense } from 'react';

// Page-level splitting
const PassengerDashboard = lazy(() => import('@/pages/passenger/Dashboard'));
const DriverDashboard = lazy(() => import('@/pages/driver/Dashboard'));
const Profile = lazy(() => import('@/pages/Profile'));

// Feature-based splitting
const PaymentComponents = lazy(() => import('@/components/payment'));
const ChatComponents = lazy(() => import('@/components/chat'));

// Component wrapper with suspense
const LazyComponent = ({ children }: { children: React.ReactNode }) => (
  Loading...
}> {children} ); // Route configuration const AppRoutes = () => ( } /> );

Bundle Optimization

Vite Build Configuration

// vite.config.ts
export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          // Vendor chunk for stable dependencies
          vendor: ['react', 'react-dom'],
          
          // Router chunk for navigation
          router: ['react-router-dom'],
          
          // UI chunk for component library
          ui: ['@radix-ui/react-dialog', '@radix-ui/react-dropdown-menu'],
          
          // Data chunk for state management
          data: ['@tanstack/react-query', 'axios'],
          
          // Utils chunk for utilities
          utils: ['date-fns', 'clsx', 'tailwind-merge'],
        },
      },
    },
    
    // Optimize assets
    assetsInlineLimit: 4096,
    cssCodeSplit: true,
    
    // Enable source maps for production debugging
    sourcemap: process.env.NODE_ENV === 'development',
  },
});

Caching Strategy

Data Type Cache Duration Strategy Invalidation
User Profile 5 minutes Background refetch Profile update
Ride Lists 1 minute Stale while revalidate New booking/cancellation
Static Data 1 hour Cache first Manual/version change
Chat Messages 30 seconds Real-time updates New message

Testing Architecture

Testing Strategy

🧪 Unit Tests

Individual component and function testing with Jest and React Testing Library

🔗 Integration Tests

Component interaction and API integration testing with MSW

🌐 E2E Tests

Full user journey testing with Cypress or Playwright

🎯 Visual Tests

Component snapshot testing and visual regression testing

Test Structure Example

// Component test structure
describe('PassengerDashboard', () => {
  beforeEach(() => {
    // Setup test environment
    setupMSWHandlers();
    renderWithProviders();
  });

  it('displays user profile information', async () => {
    await waitFor(() => {
      expect(screen.getByText('Welcome back, John Doe')).toBeInTheDocument();
    });
  });

  it('loads and displays upcoming rides', async () => {
    const rideCards = await screen.findAllByTestId('ride-card');
    expect(rideCards).toHaveLength(3);
  });

  it('handles ride booking flow', async () => {
    const bookButton = screen.getByRole('button', { name: /book ride/i });
    fireEvent.click(bookButton);
    
    await waitFor(() => {
      expect(screen.getByText('Booking confirmed')).toBeInTheDocument();
    });
  });
});

Deployment Architecture

Build Pipeline

CI/CD Pipeline Structure

# Build and deployment workflow
1. Code Push to Repository
   ↓
2. Automated Tests (Unit, Integration)
   ↓
3. Build Application (npm run build)
   ↓
4. Static Analysis (ESLint, TypeScript)
   ↓
5. Security Scanning
   ↓
6. Build Docker Image (if containerized)
   ↓
7. Deploy to Staging Environment
   ↓
8. E2E Tests on Staging
   ↓
9. Deploy to Production
   ↓
10. Health Checks and Monitoring

Environment Strategy

Environment Purpose Deployment Data Source
Development Local development npm run dev Local/Mock APIs
Staging Testing and QA Auto-deploy from main Staging API
Production Live application Manual deployment Production API

Scalability Considerations

Horizontal Scaling

  • CDN Distribution: Static assets served from global CDN
  • Load Balancing: Multiple application instances behind load balancer
  • Cache Layers: Redis for session storage and caching
  • Database Scaling: Read replicas and connection pooling

Performance Optimization

  • Code Splitting: Lazy loading of route components
  • Image Optimization: WebP format with fallbacks
  • Bundle Analysis: Regular bundle size monitoring
  • Service Worker: Offline functionality and caching

Monitoring and Observability

Error Tracking and Analytics

// Error boundary for React components
class ErrorBoundary extends Component {
  componentDidCatch(error: Error, errorInfo: ErrorInfo) {
    // Log to error tracking service
    errorTracker.captureException(error, {
      extra: errorInfo,
      tags: {
        component: 'react',
        version: process.env.REACT_APP_VERSION,
      },
    });
  }
}

// Performance monitoring
const performanceObserver = new PerformanceObserver((list) => {
  list.getEntries().forEach((entry) => {
    if (entry.entryType === 'navigation') {
      analytics.track('page_load_time', {
        duration: entry.duration,
        page: window.location.pathname,
      });
    }
  });
});

performanceObserver.observe({ entryTypes: ['navigation'] });

Next Steps