Broadleaf Microservices
  • v1.0.0-latest-prod

Quote React Components

Components provided by @broadleaf/commerce-quote-react.

Overview

The following are the primary components:

  • BrowseQuotes used for a new "My Quotes" list view for a customer to view all their quotes or for a CSR to view all quote requests.

    • This is used by a new page: /pages/my-account/quotes/index.tsx

  • QuoteDetails that is used to display the details of a quote.

    • This is used by a new page: /pages/my-account/quotes/[id].tsx

  • ConfigContextProvider used to set up common configuration for the components:

    • Key properties

      • cartContextUri: URI that maps to the root cart view context. Defaults to /cart

      • checkoutContextUri: URI that maps to the root checkout view context. Defaults to /checkout

      • customerScope: Security scope(s) for customers to test access or get access tokens with

      • csrScope: Security scope(s) for CSR to test access or get access tokens with

      • gatewayHost: The URL host of the gateway application

      • preview: Optional. Preview options to include in requests when in sandbox-preview mode

      • productRoutePrefix: Value to prefix to product URLs if any such as /product

      • quoteContextUri: URI that maps to the root quote view context. Defaults to /my-account/quotes

      • quoteBrowsePageSize: Size of the page of quotes

  • ComponentRegistry: Similar to the admin concept where components can be registered by name to replace default internal components like buttons, links, and icons

  • ComponentRenderer: Component responsible for rendering named components. It consults the ComponentRegistry for any overrides for the defaults.

Example Browse Quote Page

import { FC, useMemo } from 'react';
import dynamic from 'next/dynamic';
import { useRouter } from 'next/router';
import { DefaultCartType } from '@broadleaf/commerce-cart';
import type { ConfigState } from '@broadleaf/commerce-quote-react';
import {
BrowseQuotes,
ConfigContextProvider,
} from '@broadleaf/commerce-quote-react';

import { getAuthUserScope } from '@app/auth/contexts';
import { useCartContext } from '@app/cart/contexts';
import { useIsQuoteEnabled } from '@app/cart/hooks';
import { usePreviewOptions, useQuoteClient } from '@app/common/contexts';
import { GATEWAY_HOST, PRODUCT_URL_PREFIX } from '@app/common/utils';
import { CSR_SCOPE } from '@app/csr/utils';
import { AccountMenu } from '@app/my-account/components';

import messages from 'some/where';

const MyQuotes: FC = () => {
  const quoteClient = useQuoteClient();
  const { cart, setCart } = useCartContext();
  const preview = usePreviewOptions();
  const { query, replace } = useRouter();
  const state = useMemo<ConfigState>(
    () => ({
      cartContextUri: '/cart',
      checkoutContextUri: '/checkout',
      customerScope: getAuthUserScope(),
      csrScope: CSR_SCOPE,
      gatewayHost: GATEWAY_HOST,
      preview,
      productRoutePrefix: PRODUCT_URL_PREFIX,
      quoteContextUri: '/my-account/quotes',
      quoteBrowsePageSize: 10,
    }),
    [preview]
  );
  const [isAllowQuotes, isFetching] = useIsQuoteEnabled();

  if (!isAllowQuotes && !isFetching) {
    replace('/my-account');
    return null;
  }

  if (query.id) {
    replace('/my-account/quotes/' + query.id);
    return null;
  }

  return (
    <ConfigContextProvider state={state}>
      <BrowseQuotes
        AccountMenu={AccountMenu}
        activeQuoteId={
          cart?.type === DefaultCartType.QUOTE ? cart.id : undefined
        }
        handleNotAuthenticated={() => {
          replace('/my-account/sign-in');
        }}
        messages={messages}
        quoteClient={quoteClient}
        setCart={setCart}
      />
    </ConfigContextProvider>
  );
};

Example Quote Details Page

import { FC, useMemo } from 'react';
import dynamic from 'next/dynamic';
import { useRouter } from 'next/router';
import get from 'lodash/get';
import type { ConfigState } from '@broadleaf/commerce-quote-react';
import {
  ConfigContextProvider,
  QuoteDetails,
} from '@broadleaf/commerce-quote-react';

import { getAuthUserScope } from '@app/auth/contexts';
import { useCartContext } from '@app/cart/contexts';
import { useIsQuoteEnabled } from '@app/cart/hooks';
import { usePreviewOptions, useQuoteClient } from '@app/common/contexts';
import { GATEWAY_HOST, PRODUCT_URL_PREFIX } from '@app/common/utils';
import { CSR_SCOPE } from '@app/csr/utils';
import { AccountMenu } from '@app/my-account/components';

import messages from 'some/where';

const MyQuoteDetails: FC = () => {
  const { query, replace } = useRouter();
  const id = get(query, 'id');
  const quoteClient = useQuoteClient();
  const { cart, setCart } = useCartContext();
  const preview = usePreviewOptions();
  const state = useMemo<ConfigState>(
    () => ({
      cartContextUri: '/cart',
      checkoutContextUri: '/checkout',
      customerScope: getAuthUserScope(),
      csrScope: CSR_SCOPE,
      gatewayHost: GATEWAY_HOST,
      preview,
      productRoutePrefix: PRODUCT_URL_PREFIX,
      quoteContextUri: '/my-account/quotes',
      quoteBrowsePageSize: 10,
    }),
    [preview]
  );
  const [isAllowQuotes, isFetching] = useIsQuoteEnabled();

  if (!isAllowQuotes && !isFetching) {
    replace('/my-account');
    return null;
  }

  return (
    <ConfigContextProvider state={state}>
      <QuoteDetails
        AccountMenu={AccountMenu}
        activeQuoteId={cart?.id}
        handleNotAuthenticated={() => {
          replace('/my-account/sign-in');
        }}
        messages={messages}
        quoteClient={quoteClient}
        resolveQuoteId={() => id}
        setCart={setCart}
      />
    </ConfigContextProvider>
  );
};

ComponentRegistry Example

Registering a component
import { ComponentRegistry } from '@broadleaf/commerce-quote-react';

const MyButton = () => {
  return <button onClick={() => {}} type="button">My Custom Button</button>;
}

ComponentRegistry.registerComponent('BUTTON', MyButton);

ComponentRenderer Example

import type { FC } from 'react';
import { ComponentRenderer } from '@broadleaf/commerce-quote-react';

const MyComponent: FC = () => {
  // can passthrough other props that go directly to the underlying component
  return (
    <ComponentRenderer
      name="BUTTON"
      fallback={Button}
    >
      {children}
    </ComponentRenderer>
  );
};