Lab - Add Header Links

Plan: B2B Developer

Lesson 6 of 25 · 30 min

Introduction

Let’s take the first steps toward enhancing the B2B-enabled storefront with the mini-app you previously set up to integrate with the Buyer Portal.

In this lab, you will:

  • Add custom Buyer Portal links to the storefront header
  • Add role-specific checks for whether to render a link

Setup

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

npm run dev

Remember!

Script Manager in your store control panel must contain the appropriate script to load the mini-app from your local environment on the appropriate port.

Revisit the initial setup lab for full details on setting up your environment.

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.

Header Links Lab Snapshot

Injecting React components

Our mini-app is being injected via the entry point script main.tsx. For our initial test implementation, a typical pattern is being used to create an HTML element and render a root React component into it:

main.tsx
const b2bRoot = document.createElement('div');
body.appendChild(b2bRoot);
createRoot(b2bRoot).render(
<StrictMode>
<TestApp />
</StrictMode>,
);

As we flesh the app out with real content, we’ll essentially be doing the same thing at multiple entry points in the DOM. Our app doesn’t have a central “App” root but will render a root React component into each DOM location where we want to inject our content.

The lab boilerplate code includes the createRootElement utility function to simplify the injection of the root elements. This relies on configuration in src/utils/dom/config.ts:

config.ts
export const componentDomData: Record<string, ComponentDomEntry> = {
headerLinks: {
selector: 'ul.navUser-section',
rel: 'child',
position: 'before',
element: 'li',
className: 'navUser-item',
},
...
};

For unique keys corresponding with the components we’ll be building, this config specifies a DOM selector and how the injected element is related to it, as well as the type and classname of the injected root element.

For the HeaderLinks component we’ll be building in this lab, this config specifies that its root element should be injected as a child of the ul.navUser-section element, before any existing children, and that the root element should be an li element with the classname navUser-item.

This configuration assumes the BigCommerce default Cornerstone theme for Stencil. Feel free to adjust this config if the DOM in your theme is different.

An empty HeaderLinks component already exists in the boilerplate code. Let’s start by injecting it in main.tsx.

  1. Open main.tsx and remove the existing test implementation.
main.tsx
/* Remove `TestApp`
const TestApp = () => {
const b2b = useB2B();
return null;
}
const body = document.querySelector('body');
if (body) {
const b2bRoot = document.createElement('div');
body.appendChild(b2bRoot);
createRoot(b2bRoot).render(
<StrictMode>
<TestApp />
</StrictMode>,
);
}
*/
  1. Add appropriate imports and the basic component injection.
main.tsx
import HeaderLinks from './components/HeaderLinks';
import { createRootElement } from './utils/dom';
const createHeaderLinks = () => {
const root = createRootElement('headerLinks');
if (root) {
createRoot(root).render(
<StrictMode>
<HeaderLinks />
</StrictMode>,
);
}
};
createHeaderLinks();

Remember that the selector this injection is based on assumes the BigCommerce default Cornerstone theme for Stencil.

Navigate to your B2B storefront. While nothing will be rendered on the page yet, the rendering of the React root is working correctly if you see the error “Uncaught Error: HeaderLinks component not implemented” in your browser developer tools console.

Our HeaderLinks component will be making use of the window.b2b.utils.openPage method. We need to update the interface used in our hook, so that Typescript understands the details of openPage.

  1. Open src/hooks/useB2B.ts and update the definition of B2BSdk.
useB2B.ts
export interface B2BSdk {
utils: {
openPage: (id: string) => void;
user?: B2BUser;
}
}
  1. Open src/components/HeaderLinks/index.tsx, remove the error message, and add the useB2B hook.
HeaderLinks/index.tsx
function HeaderLinks() {
/* Remove this
throw new Error('HeaderLinks component not implemented');
*/
const b2b = useB2B();
if (!b2b) return null;
return (
<>
</>
);
}

Remember, the useB2B hook returns the window.b2b object as soon as it’s available. When this occurs, the updated state value means HeaderLinks will re-render.

  1. Add the button elements for the links.
HeaderLinks/index.tsx
function HeaderLinks() {
...
return (
<>
<button className="b2b-nav" onClick={() => b2b.utils.openPage('COMPANY_ORDERS')}>Orders</button>
<button className="b2b-nav" onClick={() => b2b.utils.openPage('INVOICE')}>Invoices</button>
</>
);
}

Navigate to your B2B storefront. You should now see the “Orders” and “Invoices” links in the header.

Header links

Make sure you’re logged in as a company user and try out the links. The Buyer Portal should open to the appropriate page.

Step 3: Add Role Checks

Currently, the custom links render on the page regardless of the user’s permissions, or even whether they’re logged in at all. Let’s add an appropriate inspection of the current user’s role.

Once again, we’re making use of a new method from b2b.utils (the getProfile method of b2b.utils.user) and need to update the appropriate interface.

  1. Open src/hooks/useB2B.ts and update the definition of B2BUser.
useB2B.ts
export interface B2BUser {
getProfile: () => {
role: number;
}
}

We’ll be using the current user’s role to determine whether they should see the “Orders” and “Invoices” links. Unfortunately, we don’t have a way of retrieving the enumeration values that correspond with different roles, so we’ll define these manually in our app. (These values can be found in the Buyer Portal source code.)

We’ll allow users with Admin and Senior Buyer roles to see the links, as well as a few special roles for which we’re not diving into the details here.

  1. Open src/components/HeaderLinks/index.tsx and add allowedRoles.
HeaderLinks/index.tsx
import './HeaderLinks.css';
// Define role codes that are allowed to see the header links
const allowedRoles = [
0, // ADMIN
1, // SENIOR_BUYER
3, // SUPER_ADMIN
4, // SUPER_ADMIN_BEFORE_AGENCY
5, // CUSTOM_ROLE
];
function HeaderLinks() {
...
}
  1. Add a state value to track whether the user has the appropriate role, as well as a side effect to check the user’s role when b2b is available.
HeaderLinks/index.tsx
function HeaderLinks() {
const [userIsValid, setUserIsValid] = useState(false);
const b2b = useB2B();
/* Remove this
if (!b2b) return null;
*/
useEffect(() => {
if (!b2b) return;
// Get the B2B user profile
const userProfile = b2b.utils.user ? b2b.utils.user.getProfile() : null;
if (userProfile && allowedRoles.includes(userProfile.role)) {
setUserIsValid(true);
}
}, [b2b]);
return (
...
);
}

Note that we no longer need the original if (!b2b) check after useB2B, since the side effect will now handle this.

  1. Add a conditional to the JSX.
HeaderLinks/index.tsx
function HeaderLinks() {
...
return (
userIsValid && (
<>
<button ...>Orders</button>
<button ...>Invoices</button>
</>
)
);
}

Now try alternating between logging in as a user with the Junior Buyer role and a user with a more elevated role. The rendering of the links should only occur for users with the appropriate role.

Full Exercise Code

Taking It Further

Practice the end-to-end process of injecting a new component at a different entry point and integrating that component with B2B data. This will involve updating src/utils/dom/config.ts and main.tsx in addition to the details of your new component. Don’t forget to update the B2BSdk interface for any new B2B object data you’re interacting with. As a starter idea for data that could be incorporated into a global component, consider that b2b.utils.quote contains the method getCurrent, which will return the details of the B2B quote in progress.