This commit introduces a comprehensive documentation strategy for the project, focusing on: - Centralizing routing configuration - Adding detailed documentation for frontend architecture - Creating standards for component documentation - Implementing a feature building process template - Removing legacy documentation files Key changes include: - Added routing strategy and implementation documents - Created project-wide documentation standards - Introduced new documentation structure in qwrk-docs - Removed redundant README and documentation files - Enhanced routing and layout documentation
12 KiB
Routing Implementation Plan
This document outlines the step-by-step implementation plan for consolidating the routing in the frontend package. The goal is to implement the new routing strategy without changing any existing functionality.
Prerequisites
Before starting the implementation, verify the following dependencies:
# Check React Router version (should be v6.4+)
yarn list react-router-dom
If the React Router version is below v6.4, update it:
yarn add react-router-dom@latest
Implementation Steps
1. Create Directory Structure
Create the necessary directories for the new routing structure:
mkdir -p packages/frontend/src/routes
mkdir -p packages/frontend/src/components/error
2. Create Error Boundary Component
Create a reusable error boundary component:
// packages/frontend/src/components/error/ErrorBoundary.tsx
import { useRouteError, isRouteErrorResponse, useNavigate } from 'react-router-dom';
import { Button } from '@/components/ui/button';
export function ErrorBoundary() {
const error = useRouteError();
const navigate = useNavigate();
// Handle different types of errors
let errorMessage = 'An unexpected error occurred';
let statusCode = 500;
if (isRouteErrorResponse(error)) {
statusCode = error.status;
errorMessage = error.statusText || errorMessage;
} else if (error instanceof Error) {
errorMessage = error.message;
}
return (
<div className='flex flex-col items-center justify-center min-h-screen p-4'>
<h1 className='text-4xl font-bold mb-4'>Something went wrong</h1>
<p className='text-xl mb-6'>{errorMessage}</p>
<div className='flex gap-4'>
<Button onClick={() => navigate(-1)}>Go Back</Button>
<Button onClick={() => navigate('/')}>Go Home</Button>
</div>
</div>
);
}
3. Create NotFound Component
Create a component for handling 404 errors:
// packages/frontend/src/components/error/NotFound.tsx
import { Link } from 'react-router-dom';
import { Button } from '@/components/ui/button';
export function NotFound() {
return (
<div className='flex flex-col items-center justify-center min-h-screen p-4'>
<h1 className='text-4xl font-bold mb-4'>404 - Page Not Found</h1>
<p className='text-xl mb-6'>The page you are looking for does not exist.</p>
<Button asChild>
<Link to='/'>Go Home</Link>
</Button>
</div>
);
}
4. Create Route Types
Define TypeScript types for route objects:
// packages/frontend/src/types/route.ts
import { RouteObject } from 'react-router-dom';
export interface AppRouteObject extends RouteObject {
auth?: boolean; // Whether the route requires authentication
title?: string; // Page title
children?: AppRouteObject[]; // Nested routes
}
5. Create Route Configuration
Create the centralized route configuration:
// packages/frontend/src/routes/index.tsx
import { createBrowserRouter, RouteObject } from 'react-router-dom';
import { lazy, Suspense } from 'react';
import { ErrorBoundary } from '@/components/error/ErrorBoundary';
import { NotFound } from '@/components/error/NotFound';
import { LoadingOverlay } from '@/components/loading/loading-overlay';
import { AppRouteObject } from '@/types/route';
// Lazy load components
const Index = lazy(() => import('@/pages/index'));
const AuthPage = lazy(() => import('@/pages/AuthPage'));
const BuyPrepaidService = lazy(() => import('@/pages/BuyPrepaidService'));
const DashboardLayout = lazy(() => import('@/layouts/DashboardLayout'));
const ProjectSearchLayout = lazy(() => import('@/layouts/ProjectSearch'));
const ProjectsScreen = lazy(() => import('@/pages/org-slug/ProjectsScreen'));
const Settings = lazy(() => import('@/pages/org-slug/Settings'));
// Import route definitions from existing files
import {
projectsRoutesWithoutSearch,
projectsRoutesWithSearch,
} from '@/pages/org-slug/projects/project-routes';
// Route definitions
const routes: AppRouteObject[] = [
{
path: ':orgSlug',
element: (
<Suspense fallback={<LoadingOverlay />}>
<DashboardLayout />
</Suspense>
),
errorElement: <ErrorBoundary />,
children: [
{
element: (
<Suspense fallback={<LoadingOverlay />}>
<ProjectSearchLayout />
</Suspense>
),
children: [
{
path: '',
element: (
<Suspense fallback={<LoadingOverlay />}>
<ProjectsScreen />
</Suspense>
),
},
{
path: 'projects',
children: projectsRoutesWithSearch,
},
],
},
{
path: 'settings',
element: (
<Suspense fallback={<LoadingOverlay />}>
<Settings />
</Suspense>
),
},
{
path: 'projects',
children: projectsRoutesWithoutSearch,
},
],
},
{
path: '/',
element: (
<Suspense fallback={<LoadingOverlay />}>
<Index />
</Suspense>
),
errorElement: <ErrorBoundary />,
},
{
path: '/login',
element: (
<Suspense fallback={<LoadingOverlay />}>
<AuthPage />
</Suspense>
),
errorElement: <ErrorBoundary />,
},
{
path: '/buy-prepaid-service',
element: (
<Suspense fallback={<LoadingOverlay />}>
<BuyPrepaidService />
</Suspense>
),
errorElement: <ErrorBoundary />,
},
{
path: '*',
element: <NotFound />,
},
];
// Create and export the router
export const router = createBrowserRouter(routes);
6. Update App.tsx
Update the App.tsx file to use the centralized router:
// packages/frontend/src/App.tsx
import { ThemeProvider } from 'next-themes';
import { useEffect } from 'react';
import { RouterProvider } from 'react-router-dom';
import { router } from './routes';
import { BASE_URL } from './utils/constants';
/**
* Main application component.
* Sets up routing, authentication, and theme provider.
* @returns {JSX.Element} The rendered application.
*/
function App() {
// Hacky way of checking session
// TODO: Handle redirect backs
useEffect(() => {
fetch(`${BASE_URL}/auth/session`, {
credentials: 'include',
}).then(res => {
const path = window.location.pathname;
console.log(res);
if (res.status !== 200) {
localStorage.clear();
if (path !== '/login') {
window.location.pathname = '/login';
}
} else {
if (path === '/login') {
window.location.pathname = '/';
}
}
});
}, []);
return (
<ThemeProvider attribute='class' defaultTheme='system' enableSystem>
<RouterProvider router={router} fallbackElement={<LoadingOverlay solid />} />
</ThemeProvider>
);
}
export default App;
7. Update Nested Route Files
Update the nested route files to use the new error boundary and loading overlay components. For example:
// packages/frontend/src/pages/org-slug/projects/project-routes.tsx
import { lazy, Suspense } from 'react';
import { LoadingOverlay } from '@/components/loading/loading-overlay';
import { ErrorBoundary } from '@/components/error/ErrorBoundary';
// Lazy load components
const CreateProjectLayout = lazy(() => import('./create/CreateProjectLayout'));
const Id = lazy(() => import('./Id'));
const AddDomain = lazy(() => import('./id/settings/domains/add'));
// Import route definitions
import { createProjectRoutes } from './create/create-project-routes';
import { projectTabRoutes } from './id/routes';
import { addDomainRoutes } from './id/settings/domains/add/routes';
export const projectsRoutesWithoutSearch = [
{
path: 'create',
element: (
<Suspense fallback={<LoadingOverlay />}>
<CreateProjectLayout />
</Suspense>
),
errorElement: <ErrorBoundary />,
children: createProjectRoutes,
},
{
path: ':id/settings/domains/add',
element: (
<Suspense fallback={<LoadingOverlay />}>
<AddDomain />
</Suspense>
),
errorElement: <ErrorBoundary />,
children: addDomainRoutes,
},
];
export const projectsRoutesWithSearch = [
{
path: ':id',
element: (
<Suspense fallback={<LoadingOverlay />}>
<Id />
</Suspense>
),
errorElement: <ErrorBoundary />,
children: projectTabRoutes,
},
];
8. Test the Implementation
Test the implementation to ensure that all routes work as expected:
# Start the development server
yarn dev
Test the following scenarios:
- Navigate to all existing routes
- Test error handling by intentionally causing errors
- Test 404 handling by navigating to non-existent routes
- Test loading states by throttling the network in the browser developer tools
9. Refactor to Use the lazy
Property (Optional)
Once the initial implementation is working, refactor the route definitions to use the lazy
property instead of manually wrapping components in Suspense:
// packages/frontend/src/routes/index.tsx
import { createBrowserRouter } from 'react-router-dom';
import { ErrorBoundary } from '@/components/error/ErrorBoundary';
import { NotFound } from '@/components/error/NotFound';
import { AppRouteObject } from '@/types/route';
// Route definitions
const routes: AppRouteObject[] = [
{
path: ':orgSlug',
lazy: () => import('@/layouts/DashboardLayout'),
errorElement: <ErrorBoundary />,
children: [
{
lazy: () => import('@/layouts/ProjectSearch'),
children: [
{
path: '',
lazy: () => import('@/pages/org-slug/ProjectsScreen'),
},
{
path: 'projects',
children: projectsRoutesWithSearch,
},
],
},
{
path: 'settings',
lazy: () => import('@/pages/org-slug/Settings'),
},
{
path: 'projects',
children: projectsRoutesWithoutSearch,
},
],
},
{
path: '/',
lazy: () => import('@/pages/index'),
errorElement: <ErrorBoundary />,
},
{
path: '/login',
lazy: () => import('@/pages/AuthPage'),
errorElement: <ErrorBoundary />,
},
{
path: '/buy-prepaid-service',
lazy: () => import('@/pages/BuyPrepaidService'),
errorElement: <ErrorBoundary />,
},
{
path: '*',
element: <NotFound />,
},
];
// Create and export the router
export const router = createBrowserRouter(routes);
Implementation Timeline
Step | Description | Estimated Time |
---|---|---|
1 | Create directory structure | 5 minutes |
2 | Create error boundary component | 15 minutes |
3 | Create NotFound component | 15 minutes |
4 | Create route types | 10 minutes |
5 | Create route configuration | 30 minutes |
6 | Update App.tsx | 10 minutes |
7 | Update nested route files | 30 minutes |
8 | Test the implementation | 30 minutes |
9 | Refactor to use the lazy property (optional) |
30 minutes |
Total | 2-3 hours |
Rollback Plan
In case of issues, the implementation can be rolled back by reverting to the original App.tsx file:
# Revert App.tsx
git checkout -- packages/frontend/src/App.tsx
Future Improvements
After the initial implementation, consider the following improvements:
- Route Guards: Implement route guards for protected routes.
- Route Metadata: Add metadata to routes for SEO and analytics.
- Route Transitions: Add transitions between routes for a smoother user experience.
- Route Code Generation: Create a script to generate route files from a configuration file.
- Route Testing: Add tests for route components and configurations.