Sign up

Allows a user to register as a customer.

The customer will enter their billing address during the signup process. The customer may select their country from a select menu, which is derived from the countries defined in the channel that the customer is viewing the store from. The menu defaults to the currently-selected country channel. The billing fields in the signup form and their validation rules are defined by the countrySettings (Channel.settings.countrySettings.privateCustomerFields in the API).

SignupFormProvider

This Provider contains all of the validation and form setup logic (using Formik). The component must wrap the components that make use of the signup form hooks.

Example usage:

Copy
Copied
function SignupPage() {
  return (
    <SignupFormProvider>
      {/* Hooks are used inside SignupForm, or anywhere below it in the tree */}
      <SignupForm>
    </SignupFormProvider>
  )
}

Hooks

useAddressFields

This hooks returns an object containing the following:

countries: Country[] An array of countries to display in the signup form. These countries are determined by the currently selected channel. Each Country has a code, id, and name.

fields: CustomerField[] An array of address fields that must be displayed as part of the signup form, determined by the selected Country. Each field has an id, label, placeholder, and required (boolean), as well as an inputProps object that can be spread on each input.

setCountryByCode(countryCode: string): void Used to re-initialise the signup form when the country is changed. Must receive a valid country code.

Example usage:

Copy
Copied
const { fields, setCountryByCode, countries } = useAddressFields();

return (
  <>
    {
      // display fields
      fields.map((field) => (
        <Input {...field.inputProps} />
      ))
    }

    <select
      name="country"
      id="country"
      onChange={(e) => setCountryByCode(e.currentTarget.value)}
    >
      {countries.map((country) => (
        <option key={country.code} value={country.code}>
          {country.name}
        </option>
      ))}
    </select>
  </>
);

useLoginFields

This hook returns an object containing the fields that are used to identify the user when logging in.

fields: CustomerField[] An array containing the email and password fields. Each field is an object containing an id, label, placeholder, type (email|password), and required (boolean), as well as an inputProps object that can be spread on each input field.

Example usage:

Copy
Copied
const { fields } = useLoginFields();

return fields.map((field) => {
  return <Input {...field.inputProps} />;
});

useSignupForm

This hook is useful for submitting the form and displaying any validation errors that may be returned by the API.

It returns an object containing:

isSignupError: boolean Returns true if the form failed to submit.

validationDetails: undefined | string[] An array of strings containing both field and validation error. Will be undefined if no validation errors exist.

isSubmitting: boolean Whether or not the form is in the process of being submitted. Useful for disabling the submit button to prevent double submission.

handleSubmit(): void Can be called to submit the signup form. Note that this is not necessary if your signup form contains a button with type="submit".

Example usage

Copy
Copied
const { isSubmitting } = useSignupForm();

return (
  <div>
    <button type="submit" disabled={isSubmitting}>{isSubmitting ? 'Submitting…' : 'Sign up'}</buton>
  </div>
)

Non default SignupInputs

The following input fields is not a part of the API response for the current country SignupInput, meaning the Flight framework won't render these fields by default.

  • priceListAccessCode
  • dynamicContent
  • preferences
  • externalAttributes

You can however still use this as a part of your signup mutation by adding a custom input inside the SignupFormProvider. For this to work the input name need to match the field name in SignupInput, and if you don't want the input state controll to change between renders you need to add the additional values inside the additionalInitialValues object. Have a look in StoreAPI schema for more information on exact types.

priceListAccessCode

Example usage:

Copy
Copied
function SignUpPage() {
  return (
    <SignupFormProvider additionalInitialValues={{ priceListAccessCode: "" }}>
      <FormikInput name="priceListAccessCode" />
    </SignupFormProvider>
  );
}

preferences

Example usage:

Copy
Copied
import Checkbox from "@jetshop/ui/Checkbox";
import { Field } from "formik";

function FormikCheckbox({ name, label, ...props }) {
  return (
    <Field name={name}>
      {({ field }) => (
        <Checkbox {...props} {...field} name={name} label={label} />
      )}
    </Field>
  );
}

function SignUpPage() {
  return (
    <SignupFormProvider
      additionalInitialValues={{
        preferences: {
          type: {
            acceptsEmail: false,
          },
        },
      }}
    >
      <FormikCheckbox
        name="preferences.type.acceptsEmail"
        label="Do you want newsletters?"
      />
    </SignupFormProvider>
  );
}

externalAttributes

When using a customer loyalty system you may want to add external attributes to each customer, which is useful for segmentation later on. This requires the attributes to be available in for example Voyado and added in Norce by Product Support. The variable accepts an array of objects like so:

Copy
Copied
[
  {
    "name": "string",
    "value": "string"
  }
]

The value should be a string, stringified JSON object or array, the expected type of this is defined by Voyado and client. In order to meet the requirements you need to provide an additional formatter (there will always be one used internally in the framework too) to the SignupFormProvider component. Like so:

NB: You most likely don't want to manipulate any other values than externalAttributes, make sure to pass the other values as is.

Copy
Copied
import { Field } from "formik";
import Checkbox from "@jetshop/ui/Checkbox";

function formatSignupInput(values) {
  const externalAttributes = Object.entries(values?.externalAttributes).map(
    ([name, value]) => {
      if (name === "my_cool_string_value") {
        return {
          name,
          value,
        };
      } else {
        return {
          name,
          value: JSON.stringify(value),
        };
      }
    }
  );

  return {
    ...values,
    externalAttributes,
  };
}

const interests = [
  {
    name: "externalAttributes.interest",
    value: "X",
  },
  {
    name: "externalAttributes.interest",
    value: "Y",
  },
  {
    name: "externalAttributes.interest",
    value: "Z",
  },
];

function SignUpPage() {
  return (
    <SignupFormProvider
      additionalFormatter={formatSignupInput}
      additionalInitialValues={{
        externalAttributes: {
          my_cool_string_value: "",
          interest: [],
        },
      }}
    >
      {/* Rest of form */}
      <FormikInput name="externalAttributes.interest" />
      {interests.map(({ name, value }) => (
        <Field name={name} key={value}>
          {({ field }) => (
            <Checkbox {...field} name={name} value={value} label={value} />
          )}
        </Field>
      ))}
      {/* Rest of form */}
    </SignupFormProvider>
  );
}

In this small example Formik is handling all the logic of adding and removing values from the array.

dynamicContent

This is useful for adding any content on each customer that may be presented in the store after login. If you need more data than a string, you may want to stringify a JSON object, like so:

Copy
Copied
import { Field } from "formik";

function formatSignupInput(values) {
  const dynamicContent = JSON.stringify({
    preferredBrand: values.dynamicContent.preferredBrand,
    some_cool_meta_data: values.dynamicContent.some_cool_meta_data,
    created: Date.now().toString(),
  });

  return { ...values, dynamicContent };
}

function SignUpPage() {
  return (
    <SignupFormProvider
      additionalFormatter={formatSignupInput}
      additionalInitialValues={{
        dynamicContent: {
          preferredBrand: "",
          some_cool_meta_data: "",
          created: "",
        },
      }}
    >
      {/* Rest of form */}
      <FormikInput
        name="dynamicContent.preferredBrand"
        label="I would prefer to get dressed in this cool brand"
      />

      <FormikInput
        name="dynamicContent.some_cool_meta_data"
        label="Cool meta data"
      />
      {/* Rest of form */}
    </SignupFormProvider>
  );
}

If a string is enough, have a look at priceListAccessCode implementation and change the input name accordingly.

useSignupValidation

This hook is included in the template for easy customization.

It returns a translated string with validation details.

Example usage:

Copy
Copied
const { validationDetails, isSignupError, isSubmitting } = useSignupForm();

const validationMessage = useSignupValidation(validationDetails);

return (
  <div>
    {isSignupError && <div>{validationMessage}</div>}
    <button type="submit" disabled={isSubmitting}>
      {isSubmitting ? "Submitting…" : "Sign up"}
    </button>
  </div>
);

Enable more fields for validation

If needed the fields object (useSignupValidation.js) can extended to include more form fields to validate.

Available fields are the same ones used as SignUpInput but with PascalCased keys (EmailAddress, PostalCode, MobilePhone etc).

Available validations errors are

Unknown,
Required,
BadFormat,
InvalidValue,
MaxLength,
UniqueValueAlreadyExists,
AgeValueInvalid,
InvalidEmailAddress,
InvalidPhoneNumber,
InvalidSocialSecurityNumber,
NotEditable

but commonly used are Required, InvalidValues and UniqueValueAlreadyExists. If no field or validation is specified it returns a generic validation error.

Copy
Copied
import t from "@jetshop/intl";

const fields = {
  InvalidValue: {
    Pid: t("PID is invalid"),
    EmailAddress: t("Email address is invalid."),
  },
  UniqueValueAlreadyExists: {
    Pid: t("This PID is already registered"),
    EmailAddress: t("Your email address is already registered."),
  },
  Required: {
    Pid: t("PID is required"),
    EmailAddress: t("Email address is required."),
  },
};
Copyright © Norce 2023. All right reserved.