snowballtools-base/qwrk-docs/project-docs/frontend/ROUTING_IMPLEMENTATION.md
icld e1c2a8ce2c refactor: consolidate project documentation and routing strategy
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
2025-03-02 06:14:24 -08:00

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:

  1. Route Guards: Implement route guards for protected routes.
  2. Route Metadata: Add metadata to routes for SEO and analytics.
  3. Route Transitions: Add transitions between routes for a smoother user experience.
  4. Route Code Generation: Create a script to generate route files from a configuration file.
  5. Route Testing: Add tests for route components and configurations.