System Architecture
Comprehensive overview of WI-CARPOOL's architecture, design patterns, and system components
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.
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...
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'] });