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.
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:
- Using a soft login link provided by Voyado inside their newsletter or promotion emails.
- 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:
- NONEXISTINGCUSTOMER: The customer does not exist in either Norce nor Voyado.
- PREEXISTING_CUSTOMER: The customer exists in both Norce and Voyado. No further action needed.
- 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.
- 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.
- 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:
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:
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:
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:
// 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.
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
Note: the UI between some of the states can be controlled with the return values from useSoftLoginStatus.
Lookup chart
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:
- https://gitlab.jetshop.se/flight/voyado/-/blob/master/src/components/Auth/ExternalLookupField.js
- https://gitlab.jetshop.se/flight/voyado/-/blob/master/src/components/Auth/LogInPage.js
- https://gitlab.jetshop.se/flight/voyado/-/blob/master/src/components/Auth/Signup/SignUpPage.js
- https://gitlab.jetshop.se/flight/voyado/-/blob/master/src/components/Shop.js