Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/iDevRanjan/lws-ra-b4-assignment-five/llms.txt

Use this file to discover all available pages before exploring further.

LWS Job Portal’s navigation is built on React Router v7’s Data API — the modern createBrowserRouter approach that co-locates data loading with route definitions. Every protected section of the app is guarded at the router layer rather than inside components, so a user who tampers with client-side state still hits a redirect before any protected page renders. An authMiddleware function runs before every route transition, rehydrating authentication context from the server so guards always have fresh, verified data.

Router Setup

The router is created with createBrowserRouter in src/routes/AppRoutes.jsx and exposed through a single <RouterProvider> in the AppRoutes component. The root route renders <MainLayout> (shared Header + Footer) and registers the middleware and hydration fallback at the top level.
// src/routes/AppRoutes.jsx (structure)
const router = createBrowserRouter([
    {
        path: "/",
        element: <MainLayout />,
        middleware: [authMiddleware],
        hydrateFallbackElement: <RootFallback />,
        children: [
            {
                errorElement: <ErrorElement />,
                children: [ /* all page routes */ ],
            },
            {
                path: "*",
                element: <ErrorElement />,
            },
        ],
    },
]);

export default function AppRoutes() {
    return <RouterProvider router={router} />;
}

Route Hierarchy

/ (MainLayout)
├── middleware: [authMiddleware]
├── hydrateFallbackElement: <RootFallback />

└── (pathless error boundary — errorElement: <ErrorElement />)
    ├── /                     → <QueryObjectProvider><Home /></QueryObjectProvider>
    ├── /jobs/:jobSlug        → <JobDetails />         loader: getJobBySlugLoader
    ├── /companies/:companySlug → <CompanyProfile />   loader: getCompanyBySlugLoader

    ├── <PublicRoutes />      loader: publicLoader
    │   ├── /login            → <Login />
    │   ├── /jobseeker-register → <JobSeekerRegister />
    │   └── /company-register → <CompanyRegister />

    ├── <RoleBasedRoute allowedRole="USER" />   loader: roleBasedLoader
    │   ├── /jobseeker-dashboard    → <JobSeekerDashboard />
    │   ├── /jobseeker-applications → <JobSeekerApplications />
    │   ├── /jobseeker-profile      → <JobSeekerProfile />
    │   └── /edit-jobseeker-profile → <EditJobSeekerProfile />

    └── <RoleBasedRoute allowedRole="COMPANY" />  loader: roleBasedLoader
        ├── /company-dashboard → <CompanyDashboard />  loader: getCompanyDashboardStatsLoader
        ├── /manage-jobs       → <ManageJobs />
        ├── /manage-jobs/create → <CreateAndEditJob key="create" />
        ├── /manage-jobs/edit/:jobSlug → <CreateAndEditJob key="edit" />  loader: editJobLoader
        ├── /applicants        → <Applicants />
        ├── /applicants/:applicantId → <ApplicantProfile />  loader: getApplicantProfileLoader
        └── /company-settings  → <CompanySettings />

└── * (catch-all — outside error boundary)  → <ErrorElement />

Auth Middleware

authMiddleware is a React Router v7 middleware function that runs before any route in the tree renders. It reads the pub-sub store, verifies the user’s session by fetching their profile via queryClient.ensureQueryData, and populates a context object that loaders can read downstream.
// src/services/authMiddleware.js
export async function authMiddleware({ context }, next) {
    const authData = store.getState().authData;

    let authMiddlewareData = {
        isLoggedin: false,
        email: undefined,
        role: undefined,
    };

    if (authData.isLoggedin) {
        try {
            const response = await queryClient.ensureQueryData(
                getClientProfileQueryOption(authData),
            );

            if (response.success) {
                authMiddlewareData = {
                    isLoggedin: true,
                    email: response.data.email,
                    role: response.data.role,
                };
            }
        } catch (error) {
            console.error("Middleware Auth Error:", error.message);
        }
    }

    context.set(authContext, authMiddlewareData);

    return next();
}
If the profile fetch fails (e.g., expired token, network error), authMiddlewareData remains { isLoggedin: false }. This means the middleware gracefully degrades — a broken token results in an unauthenticated state rather than a crash.
The authContext key (createContext(null) in src/context/index.js) is how middleware data travels to loaders:
// src/context/index.js
export const QueryObjectContext = createContext(null);
export const authContext = createContext(null);

Route Guards

PublicRoutes — redirect authenticated users

PublicRoutes prevents already-logged-in users from accessing /login, /jobseeker-register, and /company-register. The publicLoader reads middleware context and passes it as loader data; the component renders <ExistingClient> when a session is already active.
// src/routes/PublicRoutes.jsx
export default function PublicRoutes() {
    const { isLoggedin, email } = useLoaderData();

    if (isLoggedin) return <ExestingClient email={email} />;

    return <Outlet />;
}
// publicLoader in src/services/routerLoaders.js
export async function publicLoader({ context }) {
    const authMiddlewareData = context.get(authContext);
    return { ...authMiddlewareData };
}

RoleBasedRoute — enforce user role

RoleBasedRoute receives an allowedRole prop ("USER" or "COMPANY") and compares it against the role returned by the loader. Unauthenticated users are redirected to /login inside the loader itself — before the component even mounts.
// src/routes/RoleBasedRoute.jsx
export default function RoleBasedRoute({ allowedRole }) {
    const { role } = useLoaderData();

    if (role !== allowedRole) {
        return <Unauthorized />;
    }

    return <Outlet />;
}
// roleBasedLoader in src/services/routerLoaders.js
export async function roleBasedLoader({ context }) {
    const authMiddlewareData = context.get(authContext);

    if (!authMiddlewareData.isLoggedin) throw redirect("/login");

    return { ...authMiddlewareData };
}
1

Middleware runs first

authMiddleware fires before any loader, populates context with { isLoggedin, email, role }.
2

roleBasedLoader checks authentication

If isLoggedin is false, the loader throws redirect("/login") — the component tree is never rendered.
3

RoleBasedRoute checks authorization

If the user is logged in but has the wrong role (e.g., a USER trying to access /company-dashboard), the <Unauthorized /> page is rendered in place of the outlet.
Role enforcement happens twice: once in the loader (unauthenticated redirect) and once in the component (role mismatch). This double-checking ensures the guard holds even if middleware context is missing or incomplete.

Router Loaders

Router loaders in src/services/routerLoaders.js use queryClient.ensureQueryData — React Query’s “fetch if not cached, otherwise return cached” primitive. This means navigating back to a page you’ve already visited is instant.
export async function getJobBySlugLoader({ params }) {
    if (!params.jobSlug) throw new Error("No job slug provided");

    await queryClient.ensureQueryData(getJobBySlugQueryOption(params.jobSlug));
}
Prefetches the job detail before <JobDetails> renders. If the slug returns a 404, the thrown error activates <ErrorElement> inside the MainLayout.
export async function getCompanyBySlugLoader({ params }) {
    if (!params.companySlug) throw new Error("No company slug provided");

    await queryClient.ensureQueryData(
        getCompanyBySlugQueryOption(params.companySlug),
    );
}
Same pattern for public company profile pages.
export async function editJobLoader({ params }) {
    if (!params.jobSlug)
        throw new Error("No job slug provided for job editing");

    const response = await queryClient.ensureQueryData(
        getJobBySlugQueryOption(params.jobSlug),
    );

    return { jobData: response.data, isEditMode: Boolean(params.jobSlug) };
}
The only loader that returns data instead of just prefetching. <CreateAndEditJob> reads jobData and isEditMode via useLoaderData() to pre-fill the form.
export async function getCompanyDashboardStatsLoader() {
    const authData = store.getState().authData;

    if (authData.role === "COMPANY")
        await queryClient.ensureQueryData(
            getCompanyDashboardStatsQueryOption(),
        );
}
Reads the store directly (no context needed) because this loader only runs inside the COMPANY role-based group where authentication is already guaranteed.
export async function getApplicantProfileLoader({ params }) {
    if (!params.applicantId) throw new Error("No applicant id provided");

    await queryClient.ensureQueryData(
        getApplicantProfileQueryOption(params.applicantId),
    );
}
Prefetches the job-seeker profile before the company’s <ApplicantProfile> page renders.

Error Handling

LWS Job Portal uses two complementary mechanisms to catch errors without breaking the MainLayout chrome.

errorElement: <ErrorElement />

A pathless route wrapping all page routes carries errorElement: <ErrorElement />. Whenever a loader throws or a component crashes inside this group, React Router renders <ErrorElement> in place of the failed subtree — the Header and Footer remain visible.
{
    errorElement: <ErrorElement />,
    children: [
        { index: true, element: <...Home /> },
        { path: "jobs/:jobSlug", loader: getJobBySlugLoader, element: <JobDetails /> },
        // ...
    ],
}

path: "*" catch-all

errorElement only activates when React Router has already matched a route and something goes wrong inside it. A completely unknown URL (e.g., /this-does-not-exist) never matches any child route, so the pathless error boundary is never entered. The catch-all route handles this case:
{
    path: "*",
    element: <ErrorElement />,
}

errorElement

Fires when a matched route’s loader throws or a component crashes at runtime. Renders inside MainLayout with the full header and footer.

path: *

Fires when no route matches the URL (404 navigation). Also renders <ErrorElement> inside MainLayout, giving a consistent 404 page.

hydrateFallbackElement

hydrateFallbackElement: <RootFallback /> is shown during the initial client hydration pass while the root loader (middleware) is still resolving. Once middleware completes, the fallback is replaced by the real page.
{
    path: "/",
    element: <MainLayout />,
    middleware: [authMiddleware],
    hydrateFallbackElement: <RootFallback />,
    children: [ /* ... */ ],
}
<RootFallback> should be a lightweight skeleton that matches the dimensions of <MainLayout> to prevent layout shift when the real content loads.

Build docs developers (and LLMs) love