> This page is for Developer.

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.bigcommerce.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.bigcommerce.com/_mcp/server.

# Lab - Add an Overview Page

Plan: B2B Developer

Lesson 12 of 25 · 30 min

## Introduction

In this exercise, you'll add a brand new "Overview" page to the Buyer Portal, and in the process you'll practice routing setup, permissions configuration, and navigation between pages.

### In this lab, you will:

* Add simple route permissions configuration
* Configure a new page route
* Make content conditional on permissions
* Navigate in response to user interaction

## Setup

This exercise continues where you left off previously. Make sure the dev server is started in your Buyer Portal project:

```shell showLineNumbers={false}
yarn dev
```

> **Info**
>
> **Remember!**
>
> These exercises will proceed from a project you previously set up from the lab repository, which contains necessary boilerplate code.
>
> Script Manager in your store control panel must contain appropriate header/footer scripts to load the Buyer Portal from your local environment on the appropriate port.
>
> Revisit the initial setup lab if necessary to review the details.

### Exercise Reference Copy

If it's helpful to compare your own code as you go, you can clone or download the completed version of this labs as a reference.

[Overview Page Lab Snapshot](https://github.com/bigcommerce-edu/lab-b2b-buyer-portal/tree/e-route-post)

## Step 1: Configure the Route

> **Info**
>
> Remember, all file paths referenced in these exercises are relative to `apps/storefront/src` in your Buyer Portal project.

The project you've copied already has a very basic component for the page you'll be setting up, at `pages/Overview/index.tsx`. Before the Overview page will show up in the frontend, you must map this component to a route and provide several other pieces of configuration for that route.

The route configuration in question will make reference to permissions, so we'll need to set those up first.

1. **Open** `shared/routes/config.ts`. This file contains route-related permissions configuration.
2. **Modify** `legacyPermissions` to add a new item for Overview.

**`config.ts`**

```ts {3-12} showLineNumbers={false} title="config.ts"
const legacyPermissions = {
  dashboardPermissions: [...],
  overviewPermissions: [
    CustomerRole.SUPER_ADMIN,
    CustomerRole.SUPER_ADMIN_BEFORE_AGENCY,
    CustomerRole.ADMIN,
    CustomerRole.SENIOR_BUYER,
    CustomerRole.JUNIOR_BUYER,
    CustomerRole.CUSTOM_ROLE,
    CustomerRole.B2C,
    CustomerRole.GUEST,
  ],
  ordersPermissions: [
    CustomerRole.SUPER_ADMIN,
    ...
  ],
  ...
};
```

The order of the items in this config object does not strictly matter. Notice that the array's values are supplied by a `CustomerRole` enumeration. Our intention is that the Overview page is accessible by all users, so we've mimicked `ordersPermissions`, which lists all possible roles.

3. **Modify** `newPermissions` to similarly add a line for Overview.

**`config.ts`**

```ts {2} showLineNumbers={false} title="config.ts"
const newPermissions = {
  overviewPermissionCodes: b2bPermissionsMap.getShoppingListPermission,
  ordersPermissionCodes: b2bPermissionsMap.getOrderPermission,
  ...
};
```

Again, we are mimicking the loosest restrictions with these permissions, so in this case we have duplicated `getShoppingListPermission`.

### On Permissions

`config.ts` contains two different types of permissions:

* **Role-based permissions** are based on a user's role, such as Senior Buyer, Junior Buyer, or custom roles. `legacyPermissions` maps various roles to a route key.
* **Permission "codes"** are more granular, based on the specific permissions that are granted to roles in the B2B Edition backend. `newPermissions` maps a permission code to a route key.

You may notice that the values in `newPermissions` reference values from a `b2bPermissionsMap` containing the various permission strings (such as "get\_shopping\_lists", "create\_quote", "get\_invoice\_detail", etc). The built-in values here correspond with the permissions that exist in B2B Edition by default and are assigned to the various built-in roles.

We might consider defining our own custom permission with the [B2B Edition API](/developer/api-reference/rest/b2b/management/company/company-roles-and-permissions/post-stores-companies-permissions) and incorporating that permission's code into the map. To keep things simple, we've instead re-used an existing permission (`getShoppingListPermission` or "get\_shopping\_lists"). Users in our store who have the "get shopping lists" permission will also be able to access the Overview page.

### Routing Config

Now that we have permissions, we can wire up the new route by modifying a couple of key files.

4. **Open** `shared/routeList.ts`, which defines all routes with their basic config.
5. **Update** `routeList` with a new Overview item.

**`routeList.ts`**

```ts {6-15} showLineNumbers={false} title="routeList.ts"
export const routeList: (BuyerPortalRoute | RouteItem)[] = [
  {
    path: '/dashboard',
    ...
  },
  {
    path: '/overview',
    name: 'Overview',
    wsKey: 'router-overview',
    isMenuItem: true,
    permissions: overviewPermissions,
    permissionCodes: overviewPermissionCodes,
    isTokenLogin: true,
    idLang: 'global.navMenu.overview',
  },
  {
    path: '/orders',
    ...
  },
  ...
];
```

This config includes the following:

* `path` defines the hash portion of the URL path that will correspond with the page.
* `isMenuItem` declares that this page should appear in the nav menu.
* `permissions` and `permissionCodes` apply the permissions config we prepared.
* `isTokenLogin` declares that a user must be logged in to access this page.
* `idLang` identifies the locale string key used to look up the locale-specific page title. (The "Overview" page title has already been included in the locale strings in `lib/lang/locales/en.json`.)

The only thing we haven't taken care of with this config is actually mapping the "/overview" route to the page component. This is handled in a separate file.

6. **Open** `shared/routes/index.tsx` and **add** a "lazy" import of the correct component, as well as the required config in `routesMap`.

**`routes/index.tsx`**

```tsx {3, 8} showLineNumbers={false} title="routes/index.tsx"
...

const Overview = lazy(() => import('@/pages/Overview'));
const AccountSetting = lazy(() => import('@/pages/AccountSetting'));
...

const routesMap: Record<string, LazyExoticComponent<(props: PageProps) => ReactElement>> = {
  '/overview': Overview,
  '/dashboard': Dashboard,
  ...
};
```

We've actually completed all the config required for our page to be displayed. One last change we'll make is to add a new constant to provide a clean and consistent way to reference this route (such as with `window.b2b.utils.openPage`).

7. **Open** `constants/index.ts` and **add** an `OVERVIEW` constant to `HeadlessRoutes`.

**`constants/index.ts`**

```ts {4} showLineNumbers={false} title="constants/index.ts"
export enum HeadlessRoutes {
  ...
  DASHBOARD = '/dashboard',
  OVERVIEW = '/overview',
  ORDERS = '/orders',
  ...
}
```

**Any company user should now see the "Overview" item in the Buyer Portal nav menu. The page is currently very bare, containing only placeholders for the sections we'll be building.**

![Overview page with placeholders](/_fern-img/2cb5d3445272dd7e9e7614ae5e85166e805972ec17bab726c3993fdc250b96fc.webp)

### What About React Router?

You might have noticed that the code we've added didn't directly deal with React Router. The Buyer Portal manages information about the various routes via the configuration we've seen, and we don't need to worry about updating the code that iterates over this config to create the routes themselves.

Here's a brief overview of the wiring involved:

* `App.tsx` wraps the entire layout in `HashRouter` from the `react-router-dom` package.
* `components/layout/B3RenderRouter.tsx` handles the heavy lifting:
  * The `Routes` and `Route` components from `react-router-dom` are used to declare the routes.
  * `getAllowedRoutes` is used to fetch the config of all routes the user has permissions for.
  * The list of routes is looped, with a `Route` component rendered for each.

> **Info**
>
> [**View the Code**](https://github.com/bigcommerce-edu/lab-b2b-buyer-portal/compare/e-route-01a-pre...e-route-01b-post?diff=split)

## Step 2: Change the Default Page

Our remaining steps for this exercise won't be as involved as the router config. First, an "Overview" page probably makes the most sense as the default page when a user first opens the Buyer Portal.

The logic for determining this landing page is contained in a utility function.

1. **Open** `utils/b3CheckPermissions/b2bPermissionPath.ts` and **add** a conditional to the top of `b2bJumpPath`.

**`b2bPermissionPath.ts`**

```ts {2-4} showLineNumbers={false} title="b2bPermissionPath.ts"
export const b2bJumpPath = (role: number): string => {
  if (hasPermission('getShoppingListPermission')) {
    return PATH_ROUTES.OVERVIEW;
  }

  if (role === CustomerRole.SUPER_ADMIN) {
    ...
  }

  ...
};
```

The very first condition in the utility function will now return the "Overview" route as the default route for any user with the shopping list permission (the same permission we tied our route config to).

Realistically, this condition should apply to any of the built-in roles, but we'll leave most of the function's remaining logic in place, since we've tied our own condition explicitly to a permission code. The only clean-up we need to do is to get rid of the existing condition relying on the shopping list permission; this conditional is redundant now and will never be reached.

2. **Remove** the `hasPermission('getShoppingListPermission')` conditional block later in the function.

**`b2bPermissionPath.ts`**

```ts {4-8} showLineNumbers={false} title="b2bPermissionPath.ts"
export const b2bJumpPath = (role: number): string => {
  ...

  /* REMOVE these lines
  if (hasPermission('getShoppingListPermission')) {
    return PATH_ROUTES.SHOPPING_LISTS;
  }
  */

  return PATH_ROUTES.ACCOUNT_SETTINGS;
};
```

**Try closing the Buyer Portal and then re-opening it with the "Account" nav link. You should find that Overview is presented immediately.**

> **Info**
>
> [**View the Code**](https://github.com/bigcommerce-edu/lab-b2b-buyer-portal/compare/e-route-02-pre...e-route-02-post?diff=split)

## Step 3: Restrict Content by Permissions

Remember that we've configured our route permissions such that anyone allowed to view shopping lists can access the Overview page itself.

As indicated by the page's current placeholders, however, part of the page's content will include information about recent orders. Our shopping list permission does *not* guarantee that a given user is allowed to view orders! What we need in this case is to check the user's permissions within our component logic to make this content conditional.

1. **Open** `pages/Overview/index.tsx`, which contains the main page component.
2. **Add** the following immediately before the returned JSX.

**`Overview/index.tsx`**

```tsx {4} showLineNumbers={false} title="Overview/index.tsx"
export default function Overview() {
  ...

  const { getOrderPermission } = useAppSelector(rolePermissionSelector);

  return (
    ...
  );
}
```

The Buyer Portal resolves the current user's permissions into global state, and `rolePermissionSelector` reads them back out. The selector returns an object with a boolean for every permission in `b2bPermissionsMap`, so destructuring `getOrderPermission` gives us a simple flag for "may this user view company orders?"

> **Info**
>
> `useAppSelector` is the Buyer Portal's hook for reading global state managed by **Redux**. We're using it here because it's the pattern the portal itself uses for permission checks, but don't worry about the details yet — we'll cover Redux, selectors, and global state in depth in a later module.
>
> Because the value comes from global state, this check is also *reactive*: if the user's permissions change, the page re-renders automatically.

3. In the component's JSX, **wrap** the "Recent Orders" placeholder in a conditional.

**`Overview/index.tsx`**

```tsx {11, 17} showLineNumbers={false} title="Overview/index.tsx"
export default function Overview() {
  ...

  return (
    <>
      <Grid
        ...
      >
        ...

        {getOrderPermission && (
        <Grid
          ...
        >
          <h3>Recent Orders Placeholder</h3>
        </Grid>
        )}
      </Grid>
    </>
  );
}
```

**Try logging into your storefront as a user with the "Junior Buyer" role. With the new permission check in place, the "Recent Orders" placeholder should no longer appear.**

Don't forget to log in again as a user with full admin permissions before subsequent labs!

> **Info**
>
> [**View the Code**](https://github.com/bigcommerce-edu/lab-b2b-buyer-portal/compare/e-route-03-pre...e-route-03-post?diff=split)

## Step 4: Navigate Between Pages

In our last step for this exercise, we'll add a link to another Buyer Portal page. Our Overview page will show only limited information about recent orders (and later other data). We'll provide a link taking users straight to the main orders page.

Because the Buyer Portal is a React Router application, in-app navigation is handled with React Router's [`useNavigate`](https://reactrouter.com/6.30.3/hooks/use-navigate) hook. The hook returns a `navigate` function that accepts the Buyer Portal path to move to.

1. **Open** `pages/Overview/index.tsx` and **call** the `useNavigate` hook.

**`Overview/index.tsx`**

```tsx {3} showLineNumbers={false} title="Overview/index.tsx"
export default function Overview() {
  const b3Lang = useB3Lang();
  const navigate = useNavigate();

  ...
}
```

2. **Add** a `Button` component that calls `navigate`.

**`Overview/index.tsx`**

```tsx {14-16} showLineNumbers={false} title="Overview/index.tsx"
export default function Overview() {
  ...

  return (
    <>
      <Grid
        ...
      >
        ...

        {getOrderPermission && (
        <Grid
          ...
        >
          <h3>Recent Orders Placeholder</h3>
          <Button onClick={() => navigate(HeadlessRoutes.COMPANY_ORDERS)}>
            {b3Lang('overview.allOrders')}
          </Button>
        </Grid>
        )}
      </Grid>
    </>
  );
}
```

Note the argument passed to `navigate`:

* We're making use of a `HeadlessRoutes` constant for the target page. We can also manually pass relative paths (such as if the path contains a dynamic slug), but relying on these constants when possible is the safest method.
* Because this is client-side routing, only the affected page components re-render. No request is made to the server.

**Visit the Overview page again and verify that your "All Orders" link functions.**

![All Orders link](/_fern-img/7acf70f81538104b775f0f5ab0a8b2db4978ef5f332f5e42f163fc11fec18cd3.webp)

> **Info**
>
> While not the focus of this exercise, the following are notable about our code:
>
> * The `Button` component, and the `Grid` component already present in the page, are part of the Material UI library, which we'll explore next.
> * The `b3Lang` function renders a localized string based on the JSON files in `lib/lang/locales`.

> **Info**
>
> [**View the Code**](https://github.com/bigcommerce-edu/lab-b2b-buyer-portal/compare/e-route-04-pre...e-route-04-post?diff=split)

## Full Exercise Code

> **Info**
>
> [**Full Exercise Code**](https://github.com/bigcommerce-edu/lab-b2b-buyer-portal/compare/e-route-pre...e-route-post?diff=split)

## Taking It Further

* Utilize the B2B REST Management API to [create a new permission](/developer/api-reference/rest/b2b/management/company/company-roles-and-permissions/post-stores-companies-permissions) called "overview". Then add this permission code to the master list and make the appropriate changes to incorporate this custom permission. Create a custom role with this permission in the control panel to test out your logic.
* Imagine a piece of React state that might be useful for all pages to access. Identify the appropriate place to initialize that state value and pass it as a prop to all route components.

## Resources

* [B2B REST API - Create Permissions](/developer/api-reference/rest/b2b/management/company/company-roles-and-permissions/post-stores-companies-permissions)
* [`useNavigate`](https://reactrouter.com/6.30.3/hooks/use-navigate)