Vue Storefront is now Alokai! Learn More
Register

SAP Commerce Cloud: B2B Register

SAP B2B Registration module consists set of steps that an entity has to follow in order to access or obtain account credentials for purchasing in commerce.

Features

SAP B2B Registration module alters already available registration flow. In B2B process works differently, there is no possibility to set up password, but only send request for an account.

  • Modified registration form for B2B commerce.
  • Modal with success message once registration request is sent.

Installation

Add the module files

To install the module, you need an enterprise license and credentials. Contact your Customer Support Manager if you're already a customer. If you're not a customer yet, contact Alokai Sales Team.

From the root of your project run the following command:

npx @vsf-enterprise/storefront-cli add-module register-b2b -e sapcc-b2b

Follow the instructions in the command line to complete the installation. To make sure the installation is finished, go to the apps/storefront-middleware/sf-modules folder and check if there's a folder named register-b2b inside.

Frontend Implementation

1. Modify register form and replace success modal

Modify register-form.tsx form component with the following changes:

  • Remove password and all checkbox fields.
  • Add message textarea form field with counter logic.
  • Replace <SuccessModal /> component with the <SuccessB2BRegisterModal />.
  • Modify useMutation hook to handle new registration flow for B2B.
// storefront-unified-nextjs/app/[locale]/(auth)/register/components/register-form.tsx
'use client';

import { SfButton, SfCheckbox, SfIconError, SfInput, SfLink } from '@storefront-ui/react'; import { SfIconError, SfInput, SfTextarea } from '@storefront-ui/react'; import { useMutation, useQueryClient } from '@tanstack/react-query';
import { isSdkRequestError, isSpecificSdkHttpError } from '@vue-storefront/sdk';
import Image from 'next/image'; import { useTranslations } from 'next-intl';

import Alert from '@/components/ui/alert';
import Form, { FormHelperText, FormLabel, FormSubmit } from '@/components/ui/form'; import Form, { FormLabel, FormSubmit } from '@/components/ui/form'; import Modal from '@/components/ui/modal'; import PasswordInput from '@/components/ui/password-input'; import { Link } from '@/config/navigation'; import { resolveFormData } from '@/helpers/form-data';
import { useSdk } from '@/sdk/alokai-context';
import type { RegisterCustomerArgs } from '@/types'; import { SuccessB2BRegisterModal } from '@sf-modules/register-b2b'; import { type ChangeEvent, useState } from 'react'; import type { B2BRegisterCustomerArgs } from '@/types'; 
export default function RegisterForm() {
  const sdk = useSdk();
  const t = useTranslations('RegisterForm');
  const queryClient = useQueryClient();
  const { error, isPending, isSuccess, mutate } = useMutation({
    meta: {
      skipErrorNotification: isSdkRequestError,
    },
    mutationFn: async (data: RegisterCustomerArgs) => sdk.unified.registerCustomer(data),     mutationFn: async (data: B2BRegisterCustomerArgs) => sdk.b2bRegister.registerCustomer(data),     onSuccess(data) {
      queryClient.setQueryData(['customer'], data);
      queryClient.invalidateQueries({ queryKey: ['cart'] });
    },
    retry: false,
  });
  const [messageValue, setMessageValue] = useState('');
  const isUserAlreadyExistsError = error?.message.includes('User already exists');
  const characterLimit = 1000;
  const charsCount = characterLimit - messageValue.length;

  const onMessageChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
    const { value } = event.target;
    setMessageValue(value);
  };

  return (
    <>
      <Form
        className="flex flex-col gap-6 rounded-md border-neutral-200 px-0 md:border md:p-6"
        onSubmit={(event) => {
          event.preventDefault();
          mutate(resolveFormData<B2BRegisterCustomerArgs>(event.currentTarget));
        }}
      >
        // ...
        <div className="flex flex-col gap-4">
          // ...
          <label>
            <FormLabel>{t('password')} *</FormLabel>
            <PasswordInput
              autoComplete="current-password"
              data-testid="password-input"
              name="password"
              required
              size="lg"
            />
            <FormHelperText>{t('passwordHint')}</FormHelperText>
          </label>
        </div>
        <div className="flex flex-col gap-4">
          <label className="grid cursor-pointer grid-cols-[24px_auto] gap-x-2">
            <SfCheckbox className="m-[3px]" data-testid="terms-checkbox" name="terms" required />
            <FormLabel className="!text-base !font-normal">
              {t.rich('termsAndConditions', {
                terms: (chunks) => (
                  <SfLink as={Link} href="#" variant="primary">
                    {chunks}
                  </SfLink>
                ),
              })}
            </FormLabel>
          </label>
          <label className="grid cursor-pointer grid-cols-[24px_auto] gap-x-2">
            <SfCheckbox className="m-[3px]" data-testid="newsletter-checkbox" name="newsletter" />
            <FormLabel className="!text-base !font-normal">{t('newsletter')}</FormLabel>
            <FormLabel>{t('message')}</FormLabel>
            <SfTextarea
              className="block min-h-[96px] w-full"
              value={messageValue}
              onInput={onMessageChange}
              maxLength={characterLimit}
              placeholder={t('messagePlaceholder')}
              name="message"
            />
            <p className="mt-0.5 text-right text-neutral-500 typography-hint-xs">{charsCount}</p>
            {isUserAlreadyExistsError && (
              <p className="mt-0.5 font-medium text-negative-700 typography-hint-xs">{t('userAlreadyExists')}</p>
            )}
          </label>
        </div>

        <p className="text-neutral-500 typography-text-sm">{t('markRequired')}</p>

        <FormSubmit data-testid="submit-button" pending={isPending} size="lg">
          {t('submit')}
        </FormSubmit>
      </Form>
      {isSuccess && <SuccessModal />}      {isSuccess && <SuccessB2BRegisterModal />}    </>
  );
}
function SuccessModal() {
  const t = useTranslations('RegisterForm.successModal');

  return (
    <Modal className="max-w-[480px] p-6 md:p-10">
      <Image
        alt="My account"
        className="mx-auto mb-6"
        height={192}
        src="/images/my-account.svg"
        unoptimized
        width={192}
      />
      <h2 className="mb-4 text-center font-headings text-2xl font-semibold">{t('heading')}</h2>
      <div className="mb-6 rounded-md border border-neutral-200 bg-neutral-100 p-4 text-base">
        {t.rich('paragraph', {
          myAccount: (chunks) => (
            <SfLink as={Link} href="/my-account" variant="primary">
              {chunks}
            </SfLink>
          ),
        })}
      </div>
      <SfButton as={Link} className="flex w-full" href="/">
        {t('button')}
      </SfButton>
    </Modal>
  );
}

2. Add internationalization messages

Update translations for components.

// storefront-unified-nextjs/lang/en/base.json

  // ...
  "RegisterForm": {
    "message": "Message (optional)",     "messagePlaceholder": "Additional information regarding the intended account",     "userAlreadyExists": "User already exists",     // ...
  },
  "RegisterPage": {
    "heading": "Create Account",     "metaTitle": "Create Account",     "heading": "Send Request",     "metaTitle": "Send Request",     // ...
  }
  // ...
// storefront-unified-nextjs/lang/de/base.json

  // ...
  "RegisterForm": {
    "message": "Nachricht (optional)",     "messagePlaceholder": "Zusätzliche Informationen zum beabsichtigten Konto",     "userAlreadyExists": "Benutzer existiert bereits",     // ...
  },
  "RegisterPage": {
    "heading": "Benutzerkonto erstellen",     "metaTitle": "Benutzerkonto erstellen",     "heading": "Anfrage senden",     "metaTitle": "Anfrage senden",     // ...
  }
  // ...

The last step is providing the newly added translations to the Register page. To achieve that, you need to add the RegisterB2b key to the NextIntlClientProvider component in the register page.

// storefront-unified-nextjs/app/[locale]/(auth)/register/page.tsx

import { pick } from 'lodash-es';
import { NextIntlClientProvider } from 'next-intl';
import { getMessages, getTranslations } from 'next-intl/server';

import RegisterForm from './components/register-form';

// ...

export default async function CheckoutPage() {
  const t = useTranslations('RegisterPage');
  const messages = useMessages();

  return (
    // ...
    <NextIntlClientProvider messages={pick(messages, [
      'RegisterForm',
      'RegisterB2b'      ])}>
      <RegisterForm />
    </NextIntlClientProvider>
  );
}

With these steps, your Register-b2b feature is now effectively integrated with SAP Commerce Cloud. You've just provided a practical solution for your customers to register into your Commerce!