Voyado

This module provides a set of hooks needed to control the UI on both activation and/or signup flow using Voyado. For the integration to work, the customer must exist with shared externalid in both Norce and Voyado.

The module requires Flight to be on at least version 5.14.0.

Copy
Copied
yarn add @jetshop/flight-voyado

There are three possible states the end customer may end up in, each containing up to five sub-states, the UI for all these states is covered by this module. To get to any of these states, the end customer has two options:

  1. Using a soft login link provided by Voyado inside their newsletter or promotion emails.
  2. Using the lookup field provided from inside the store. Like so: https://voyado.jetshop.se/se/login

From both of these, the API is returning a current customer status. There are five statuses to be returned:

  1. NONEXISTINGCUSTOMER: The customer does not exist in either Norce nor Voyado.
  2. PREEXISTING_CUSTOMER: The customer exists in both Norce and Voyado. No further action needed.
  3. ACTIVATION_REQUIRED: The customer exists in Voyado, but not in Norce. The account needs activation in order for us to create the customer in Norce too.
  4. ADDITIONALUSERDATA_REQUIRED: The customer exists in Voyado, but there are fields in Norce that are required from admin in order to fully create the customer. The API is returning the field values that are present in Voyado.
  5. COUNTRYNOTVALID: The customer is trying to activate account connected to another country that is not available in current market, the API is returning the customers countryCode.

If the customer ends up in ACTIVATION_REQUIRED, Norce sends an email to the customer on how to set their password. This email is edited by Norce Product Support.

If the customer used a soft login link and the customer tries to edit their account details, the API will return with an error. To visually show this to the customer, you could use the useStatusLogin hook provided from the module described below.

useVoyadoLookup

This hook takes a single options object as an argument, with the following keys:

Argument Type Default Description
activateOnLookup Boolean False Controlls whether the end customer should manually activate the account after the lookup.
signInOnActivation Boolean False Controlls whether the end customer should manually login after previously said activation.
personLookupConfigured Boolean False Controlls whether the store is configured to use lookup provided by Voyado.
manualPersonLookup Boolean False If personlookup should be a manual step. Set this to true if the key differs from external lookup

Note: the hook may be initialized with none, all or some of the keys.

The hook is returning a set of self explanatory booleans to control the UI. Something like:

Copy
Copied
import LogInFormProvider from '@jetshop/ui/Auth/LogInFormProvider';
import { useVoyadoLookup } from '@jetshop/flight-voyado';

const { ...voyado } = useVoyadoLookup({
  activateOnLookup: true,
  signInOnActivation: false,
});

if (voyado.isPreExistingCustomer) {
  return (
    <LogInFormProvider redirect="my-pages" initialEmail={voyado?.customer?.emailAddress?.masked}>
      {({ globalError }) => (
        <>
          <input id="email" type="email" name="email" />
          <input type="password" name="password" />

          {globalError && <span>{globalError}</span>}

          <button type="submit">Login</button>
        </>
      )
    </LogInFormProvider>
  );
}

If you'd like to show something to indicate that the account is being activated, you could do something like:

Copy
Copied
import { useVoyadoLookup } from "@jetshop/flight-voyado";
import useAuth from "@jetshop/core/components/AuthContext/useAuth";

const { customer, ...voyado } = useVoyadoLookup({
  activateOnLookup: false,
  signInOnActivation: false,
});

if (voyado.isInActivation) {
  const { isActivationRequired, isActivationPending, isActivationSuccess } =
    voyado;
  return (
    <>
      <header>
        {isActivationRequired &&
          t(
            "You exist as an member, click to activate your account. We will send you an email with a link on how to set your password."
          )}
        {isActivationSuccess &&
          t("We have successfully activated your account.")}
        {isActivationPending && t("Activating account")}
      </header>
      <button
        disabled={isActivationPending}
        loading={isActivationPending}
        onClick={() => {
          if (isActivationRequired) {
            voyado.activate();
          } else {
            logIn(voyado.customer.token);
          }
        }}
      >
        {isActivationRequired && t("Activate")}
        {isActivationPending && t("Activating")}
        {isActivationSuccess && t("Login")}
      </button>
    </>
  );
}

The hook is also returning all functions needed to call the lookup, to retry the lookup and to call the activation mutation. Like so:

Copy
Copied
import { useVoyadoLookup } from "@jetshop/flight-voyado";

const { activate, ...voyado } = useVoyadoLookup({
  activateOnLookup: false,
  signInOnActivation: true,
});

if (voyado.isActivationRequired) {
  return <button onClick={activate}>Activate my account please dear</button>;
}

Note: To avoid impossible states, the functions can only be called on a few different states. IE: The activate function has no effect when the API has returned AdditionUserDataRequired, or when the activation is pending etcetc.

In addition to this, the hook is also returning the customer data for you to pre-fill (with masked data) the form with if requested.

In the example store, we're saving the potential customer to location.state, like so:

Copy
Copied
// SignInPage:
import { Redirect } from 'react-router';
import { useVoyadoLookup } from '@jetshop/flight-voyado';

function ExternalLookupField() {
  const { ...voyado } = useVoyadoLookup({
    activateOnLookup: true,
    signInOnActivation: true,
  });

  if (voyado.IsAdditionalDataRequired) {
    return <Redirect to={{ pathname: '/signup', state: { ...voyado } }} />;
  }
}

// SignupPage:
import SignupFormProvider from '@jetshop/core/components/Auth/SignupFormContainer';
import { useLocation } from 'react-router-dom';

function SignupPage() {
  const { state } = useLocation();
  return (
    <div>
    {state?.isAdditionDataRequired && (
      <h4>Hey, looks like we're missing some key information of you.</h4>
      <h5>Please fill in the rest of the form and you're good to go.</h5>
    )}
    <SignupFormProvider lookupData={state?.customer}>{/* rest of form */}</SignupFormProvider>
    </div>
  )
}

Then, on signup, you could grab it using the useLocation hook provided by react-router-dom. SignupFormProvider is handling all prefilling for you as long as you pass the data along to it. If you'd like to manipulate the data before prefilling, for example, the email should be left out of the prefilling, just delete it before passing it along to the SignupFormProvider. Using location.state is recommended if you'd like to reuse the component, since the SoftLogin also adds the potential customer inside state and redirects to whatever route provided to it.

VoyadoProvider

In order to use either of the hooks, the VoyadoProvider must be added to the Shop. Preferably somewhat high up. The provider takes an options object with the following keys:

Key Type Default Description
loginPath String '/login' Route to redirect to if the customer exist, and manually should login.
signupPath String '/signup' Route to redirect to if the customer does not exist.
loginOnActivation Boolean True This controlls whether the end customer should be logged in when clicking a soft login link.
redirectOnLookupFailure Boolean false This controlls whether the end customer should be redirected to signupPath if lookup status is NONEXISTINGCUSTOMER or ADDITIONALUSERDATA_REQUIRED
activateOnSoftLogin Boolean true This controlls whether the end customer should be activated upon clicking a soft login link (if activation is required).

Note: the provider may be initialized with none, all or some of the keys.

useLoginStatus

If the customer used the soft login functionality to login, the account details cannot be edited. You can use this hook to visually show that the form is disabled and do an actual login with the return values from this hook. Once this is submitted and success status returned, the form is no longer disabled.

Copy
Copied
import customerProfileQuery from './customerProfileQuery.gql';
import LoginMutation from '../Auth/LoginMutation.gql';
import { useCustomerQuery } from '@jetshop/core/components/Auth/useCustomer';
import { useLoginStatus } from '@jetshop/flight-voyado';

  const { customer } = useCustomerQuery({
    query: customerProfileQuery
  });

const { isSoftLoggedIn, authorize } = useLoginStatus({
  loginMutation: LoginMutation
})

<div>
{isSoftLoggedIn ? (
  <form
    onSubmit={event => {
      event.preventDefault();
      const { password } = event.target;
        authorize({
          email: customer.email,
          password
        });
  }}
  >
    <h5>You need to sign in to edit your details.</h5>
    <label htmlFor="password">Password:</label>
    <input type="email" name="email" id="email" defaultValue={customer.email} />
    <input type="password" name="password" id="password" />
    <button type="submit">Login</button>
  </form>
) : (
    <button onClick={() => formState.editDetails(true)}>
      Update information
    </button>
)}
</div>

This hook takes a single options object as an argument, with the following key:

Argument Type Required Description
loginMutation DocumentNode True The login mutation. Can be different per marketid, which is why it isn't included in the module


\

Softlogin chart

Calls loginExternalCustomer
No
Yes
No
Yes
NON_EXISTING_CUSTOMER
PREEXISTING_CUSTOMER
ACTIVATION_REQUIRED
Sends email with information
on how to set password
ADDITIONAL_USER_DATA_REQUIRED
Customer clicks email link with hash
Was customer logged in?
Calls activateExternalCustomerByToken
Customer exist in both Jetshop and Voyado.
Customer found?
Customer not found.
If configured, redirect to signup page
Checks customer status
Logged in
Activates customer.
Customer activated
Populates signup form
with masked data from Voyado

Note: the UI between some of the states can be controlled with the return values from useSoftLoginStatus.

Lookup chart

Calls externalCustomerLookup
NON_EXISTING_CUSTOMER
If configured, calls personLookup
No
Yes
PREEXISTING_CUSTOMER
ACTIVATION_REQUIRED
Calls activateExternalCustomerById
No
Yes, sends email with information
on how to set password
ADDITIONAL_USER_DATA_REQUIRED
COUNTRY_NOT_VALID
Customer fills LookupField
Checks customer status
Returns no customer data
Customer data found
Customer manually
fills required data
Prefill signup form
with personLookup data
Customer signed up in
both Norce and Voyado.
Can login.
Returns status
Can login
Returns status
masked email
external id
Success
Present error message
Customer activated.
Soft logged in
Returns masked customer data
Returns countryCode
Present error message
Populates signup form
with masked data
Customer manually fills
missing required data
Customer created.
Can login.

Note: the UI between some of the states can be controlled with the return values from useVoyadoLookup.

A complete Voyado setup is available at https://gitlab.jetshop.se/flight/voyado, with the following key Voyado components:
Copyright © Norce 2023. All right reserved.