# Frontend Architecture Redesign ## Overview This document outlines a production-ready frontend architecture for the ProfytAI Compliance Management Platform, addressing scalability, maintainability, and developer experience. ## Design Principles 1. **Feature-Based Organization**: Group related code by feature/domain 2. **Separation of Concerns**: Clear boundaries between layers 3. **Type Safety**: Full TypeScript coverage 4. **Reusability**: Shared components and utilities 5. **Testability**: Easy to test components and logic 6. **Performance**: Code splitting, lazy loading, optimization 7. **Developer Experience**: Clear structure, good DX --- ## New Directory Structure ``` fe0/ ├── src/ │ ├── app/ # App-level configuration │ │ ├── providers/ # Global providers │ │ │ ├── QueryProvider.tsx │ │ │ ├── AuthProvider.tsx │ │ │ ├── ThemeProvider.tsx │ │ │ └── ErrorBoundary.tsx │ │ ├── router/ # Routing configuration │ │ │ ├── index.tsx │ │ │ ├── routes.tsx │ │ │ └── guards.tsx │ │ └── store/ # Global state (if needed) │ │ └── index.ts │ │ │ ├── features/ # Feature-based modules │ │ ├── auth/ │ │ │ ├── components/ │ │ │ │ ├── LoginForm.tsx │ │ │ │ └── SignUpForm.tsx │ │ │ ├── hooks/ │ │ │ │ ├── useAuth.ts │ │ │ │ └── useLogin.ts │ │ │ ├── services/ │ │ │ │ └── authService.ts │ │ │ ├── types/ │ │ │ │ └── auth.types.ts │ │ │ └── index.ts │ │ │ │ │ ├── workflows/ │ │ │ ├── components/ │ │ │ │ ├── WorkflowList.tsx │ │ │ │ ├── WorkflowCard.tsx │ │ │ │ ├── WorkflowForm.tsx │ │ │ │ └── WorkflowItem.tsx │ │ │ ├── hooks/ │ │ │ │ ├── useWorkflows.ts │ │ │ │ ├── useWorkflow.ts │ │ │ │ └── useCreateWorkflow.ts │ │ │ ├── services/ │ │ │ │ └── workflowService.ts │ │ │ ├── types/ │ │ │ │ └── workflow.types.ts │ │ │ └── index.ts │ │ │ │ │ ├── documents/ │ │ │ ├── components/ │ │ │ ├── hooks/ │ │ │ ├── services/ │ │ │ └── types/ │ │ │ │ │ ├── compliance/ │ │ │ ├── components/ │ │ │ ├── hooks/ │ │ │ ├── services/ │ │ │ └── types/ │ │ │ │ │ └── dashboard/ │ │ ├── components/ │ │ ├── hooks/ │ │ └── types/ │ │ │ ├── shared/ # Shared code │ │ ├── api/ # API client │ │ │ ├── client.ts # Axios/Fetch client │ │ │ ├── interceptors.ts # Request/Response interceptors │ │ │ ├── endpoints.ts # API endpoint constants │ │ │ └── types.ts # API response types │ │ │ │ │ ├── components/ # Shared UI components │ │ │ ├── ui/ # shadcn components (existing) │ │ │ ├── layout/ │ │ │ │ ├── Header.tsx │ │ │ │ ├── Sidebar.tsx │ │ │ │ └── Footer.tsx │ │ │ ├── feedback/ │ │ │ │ ├── LoadingSpinner.tsx │ │ │ │ ├── ErrorMessage.tsx │ │ │ │ └── EmptyState.tsx │ │ │ └── forms/ │ │ │ └── FormField.tsx │ │ │ │ │ ├── hooks/ # Shared hooks │ │ │ ├── useApi.ts │ │ │ ├── useDebounce.ts │ │ │ ├── useLocalStorage.ts │ │ │ └── useMediaQuery.ts │ │ │ │ │ ├── utils/ # Utility functions │ │ │ ├── format.ts │ │ │ ├── validation.ts │ │ │ ├── date.ts │ │ │ └── constants.ts │ │ │ │ │ ├── types/ # Shared TypeScript types │ │ │ ├── api.types.ts │ │ │ ├── common.types.ts │ │ │ └── index.ts │ │ │ │ │ └── config/ # Configuration │ │ ├── env.ts # Environment variables │ │ ├── constants.ts # App constants │ │ └── routes.ts # Route constants │ │ │ ├── pages/ # Page components │ │ ├── public/ │ │ │ ├── HomePage.tsx │ │ │ ├── LoginPage.tsx │ │ │ └── NotFoundPage.tsx │ │ └── protected/ │ │ ├── DashboardPage.tsx │ │ └── WorkflowsPage.tsx │ │ │ ├── layouts/ # Layout components │ │ ├── PublicLayout.tsx │ │ ├── DashboardLayout.tsx │ │ └── AuthLayout.tsx │ │ │ ├── assets/ # Static assets │ │ ├── images/ │ │ ├── icons/ │ │ └── fonts/ │ │ │ ├── styles/ # Global styles │ │ ├── globals.css │ │ └── themes.css │ │ │ ├── App.tsx # Root component │ └── main.tsx # Entry point │ ├── public/ # Public assets ├── tests/ # Test files │ ├── unit/ │ ├── integration/ │ ├── e2e/ │ └── setup.ts │ ├── .env.example ├── .env.development ├── .env.production ├── vite.config.ts ├── tsconfig.json └── package.json ``` --- ## Key Architectural Components ### 1. API Client Layer **Purpose**: Centralized HTTP client with interceptors, error handling, and type safety. **Features**: - Request/Response interceptors - Automatic token refresh - Error handling - Type-safe endpoints - Request cancellation - Retry logic ### 2. Service Layer **Purpose**: Business logic and API communication abstraction. **Features**: - Type-safe API calls - Data transformation - Error handling - Caching strategies ### 3. Feature Modules **Purpose**: Self-contained feature modules with all related code. **Structure**: - Components (UI) - Hooks (React hooks) - Services (API calls) - Types (TypeScript types) - Utils (Feature-specific utilities) ### 4. Shared Layer **Purpose**: Reusable code across features. **Includes**: - UI components - Utilities - Hooks - Types - Configuration ### 5. Routing **Purpose**: Centralized route configuration with guards. **Features**: - Route guards (auth, permissions) - Lazy loading - Code splitting - Route constants --- ## Implementation Examples ### Example 1: API Client ```typescript // shared/api/client.ts import axios, { AxiosInstance, AxiosError, InternalAxiosRequestConfig } from 'axios'; import { env } from '@/shared/config/env'; import { getAccessToken, clearAccessToken, setAccessToken } from '@/shared/utils/token'; class ApiClient { private client: AxiosInstance; constructor() { this.client = axios.create({ baseURL: env.API_URL, timeout: 30000, headers: { 'Content-Type': 'application/json', }, }); this.setupInterceptors(); } private setupInterceptors(): void { // Request interceptor this.client.interceptors.request.use( (config: InternalAxiosRequestConfig) => { const token = getAccessToken(); if (token && config.headers) { config.headers.Authorization = `Bearer ${token}`; } return config; }, (error) => Promise.reject(error) ); // Response interceptor this.client.interceptors.response.use( (response) => response, async (error: AxiosError) => { const originalRequest = error.config as InternalAxiosRequestConfig & { _retry?: boolean }; // Handle 401 - Unauthorized if (error.response?.status === 401 && !originalRequest._retry) { originalRequest._retry = true; try { const newToken = await this.refreshToken(); if (newToken && originalRequest.headers) { originalRequest.headers.Authorization = `Bearer ${newToken}`; return this.client(originalRequest); } } catch (refreshError) { clearAccessToken(); window.location.href = '/login'; return Promise.reject(refreshError); } } // Handle other errors return Promise.reject(this.handleError(error)); } ); } private async refreshToken(): Promise { try { const response = await axios.post(`${env.API_URL}/auth/refresh`, {}, { withCredentials: true, }); const { accessToken } = response.data; setAccessToken(accessToken); return accessToken; } catch (error) { return null; } } private handleError(error: AxiosError): ApiError { if (error.response) { // Server responded with error return { message: error.response.data?.message || 'An error occurred', status: error.response.status, data: error.response.data, }; } else if (error.request) { // Request made but no response return { message: 'Network error. Please check your connection.', status: 0, }; } else { // Error setting up request return { message: error.message || 'An unexpected error occurred', status: 0, }; } } public get(url: string, config?: any): Promise { return this.client.get(url, config).then((res) => res.data); } public post(url: string, data?: any, config?: any): Promise { return this.client.post(url, data, config).then((res) => res.data); } public put(url: string, data?: any, config?: any): Promise { return this.client.put(url, data, config).then((res) => res.data); } public delete(url: string, config?: any): Promise { return this.client.delete(url, config).then((res) => res.data); } public patch(url: string, data?: any, config?: any): Promise { return this.client.patch(url, data, config).then((res) => res.data); } } export const apiClient = new ApiClient(); export type ApiError = { message: string; status: number; data?: any; }; ``` ### Example 2: Service Layer ```typescript // features/workflows/services/workflowService.ts import { apiClient } from '@/shared/api/client'; import { Workflow, CreateWorkflowDto, UpdateWorkflowItemDto } from '../types/workflow.types'; import { ApiResponse, PaginatedResponse } from '@/shared/types/api.types'; export const workflowService = { async getAll(params?: { skip?: number; limit?: number; projectName?: string; }): Promise> { return apiClient.get>('/api/v1/workflows', { params }); }, async getById(id: string): Promise { return apiClient.get(`/api/v1/workflows/${id}`); }, async create(data: CreateWorkflowDto): Promise { return apiClient.post('/api/v1/workflows', data); }, async updateItem( workflowId: string, data: UpdateWorkflowItemDto ): Promise { return apiClient.put( `/api/v1/workflows/${workflowId}/items`, data ); }, async advancePhase(workflowId: string): Promise { return apiClient.post( `/api/v1/workflows/${workflowId}/advance` ); }, async delete(id: string): Promise { return apiClient.delete(`/api/v1/workflows/${id}`); }, }; ``` ### Example 3: Custom Hooks ```typescript // features/workflows/hooks/useWorkflows.ts import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { workflowService } from '../services/workflowService'; import { CreateWorkflowDto, UpdateWorkflowItemDto } from '../types/workflow.types'; import { toast } from 'sonner'; export const useWorkflows = (params?: { skip?: number; limit?: number; projectName?: string; }) => { return useQuery({ queryKey: ['workflows', params], queryFn: () => workflowService.getAll(params), }); }; export const useWorkflow = (id: string) => { return useQuery({ queryKey: ['workflow', id], queryFn: () => workflowService.getById(id), enabled: !!id, }); }; export const useCreateWorkflow = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: (data: CreateWorkflowDto) => workflowService.create(data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['workflows'] }); toast.success('Workflow created successfully'); }, onError: (error: any) => { toast.error(error.message || 'Failed to create workflow'); }, }); }; export const useUpdateWorkflowItem = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ workflowId, data }: { workflowId: string; data: UpdateWorkflowItemDto }) => workflowService.updateItem(workflowId, data), onSuccess: (_, variables) => { queryClient.invalidateQueries({ queryKey: ['workflow', variables.workflowId] }); queryClient.invalidateQueries({ queryKey: ['workflows'] }); toast.success('Workflow item updated'); }, onError: (error: any) => { toast.error(error.message || 'Failed to update workflow item'); }, }); }; ``` ### Example 4: Environment Configuration ```typescript // shared/config/env.ts interface EnvConfig { API_URL: string; WS_URL?: string; ENVIRONMENT: 'development' | 'staging' | 'production'; ENABLE_DEV_TOOLS: boolean; } const getEnvVar = (key: string, defaultValue?: string): string => { const value = import.meta.env[key]; if (!value && !defaultValue) { throw new Error(`Missing required environment variable: ${key}`); } return value || defaultValue || ''; }; export const env: EnvConfig = { API_URL: getEnvVar('VITE_API_URL', 'http://localhost:4402'), WS_URL: getEnvVar('VITE_WS_URL'), ENVIRONMENT: (getEnvVar('VITE_ENV', 'development') as EnvConfig['ENVIRONMENT']), ENABLE_DEV_TOOLS: getEnvVar('VITE_ENABLE_DEV_TOOLS', 'false') === 'true', }; export const isDevelopment = env.ENVIRONMENT === 'development'; export const isProduction = env.ENVIRONMENT === 'production'; ``` ### Example 5: Route Configuration ```typescript // app/router/routes.tsx import { lazy } from 'react'; import { RouteObject } from 'react-router-dom'; import { PublicLayout } from '@/layouts/PublicLayout'; import { DashboardLayout } from '@/layouts/DashboardLayout'; import { ProtectedRoute } from '@/app/router/guards'; // Lazy load pages for code splitting const HomePage = lazy(() => import('@/pages/public/HomePage')); const LoginPage = lazy(() => import('@/pages/public/LoginPage')); const DashboardPage = lazy(() => import('@/pages/protected/DashboardPage')); const WorkflowsPage = lazy(() => import('@/pages/protected/WorkflowsPage')); const NotFoundPage = lazy(() => import('@/pages/public/NotFoundPage')); export const routes: RouteObject[] = [ { path: '/', element: , children: [ { index: true, element: }, { path: 'login', element: }, { path: 'about', element:
About
}, ], }, { path: '/dashboard', element: ( ), children: [ { index: true, element: }, { path: 'workflows', element: ( ), }, ], }, { path: '*', element: }, ]; ``` ### Example 6: Error Boundary ```typescript // app/providers/ErrorBoundary.tsx import React, { Component, ErrorInfo, ReactNode } from 'react'; import { Button } from '@/shared/components/ui/button'; import { AlertTriangle } from 'lucide-react'; interface Props { children: ReactNode; fallback?: ReactNode; } interface State { hasError: boolean; error: Error | null; } export class ErrorBoundary extends Component { public state: State = { hasError: false, error: null, }; public static getDerivedStateFromError(error: Error): State { return { hasError: true, error }; } public componentDidCatch(error: Error, errorInfo: ErrorInfo) { console.error('Uncaught error:', error, errorInfo); // Log to error reporting service } private handleReset = () => { this.setState({ hasError: false, error: null }); }; public render() { if (this.state.hasError) { if (this.props.fallback) { return this.props.fallback; } return (

Something went wrong

{this.state.error?.message || 'An unexpected error occurred'}

); } return this.props.children; } } ``` --- ## Benefits 1. **Scalability**: Easy to add new features 2. **Maintainability**: Clear structure, easy to find code 3. **Type Safety**: Full TypeScript coverage 4. **Testability**: Easy to test components and logic 5. **Performance**: Code splitting, lazy loading 6. **Developer Experience**: Clear patterns, good DX 7. **Reusability**: Shared components and utilities --- ## Migration Strategy ### Phase 1: Foundation (Week 1) 1. Set up new directory structure 2. Create API client 3. Set up environment configuration 4. Create shared utilities ### Phase 2: Features (Week 2-3) 1. Migrate auth feature 2. Migrate workflows feature 3. Migrate documents feature 4. Migrate compliance feature ### Phase 3: Refinement (Week 4) 1. Add error boundaries 2. Improve error handling 3. Add loading states 4. Optimize performance ### Phase 4: Testing & Polish (Week 5) 1. Write tests 2. Add documentation 3. Performance optimization 4. Final review --- ## Next Steps 1. Review and approve architecture 2. Create detailed implementation plan 3. Set up project structure 4. Begin Phase 1 implementation