@jetshop/flight-voyado
This module provides a set of hooks needed to control the UI on both activation and/or signup flow using Voyado. For the complete integration to work, the customer must exist in both Jetshop and Voyado.
The module requires Flight to be on at least version 5.3.0.
There are three possible states the end customer may end up in, each containing up to five sub-states, all 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 / promo 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:
- NON_EXISTING_CUSTOMER: The customer does not exist in either Jetshop nor Voyado.
- PREEXISTING_CUSTOMER: The customer exists in both Jetshop and Voyado. No further action needed.
- ACTIVATION_REQUIRED: The customer exists in Voyado, but not in Jetshop. The account needs activation in order for us to create the customer in Jetshop too.
- ADDITIONAL_USER_DATA_REQUIRED: The customer exists in Voyado, but there are fields in Jetshop that are required from admin in order to fully create the customer. The API is returning the field values that are present in Voyado.
- COUNTRY_NOT_VALID: The customer is trying to activate account connected to another country, the API is returning the customers countryCode.
If the customer ends up in ACTIVATION_REQUIRED, we'll be sending an email to the customer on how to set their password. This email is edited by Jetshop 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
Arguments
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 controll 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 inside 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 places the potential customer inside state and redirects to whatever route provided to it.
useSoftLoginStatus
This hook does not take any arguments. Instead it's merely returning the state from softlogin, all reminding of the statuses returned by useVoyadoLookup hook. If placed for example inside the header, it can be used to show something to visualize the background processes when activating a customer etcetc.
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:
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 NON_EXISTING_CUSTOMER or ADDITIONAL_USER_DATA_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
graph TD
A[Customer clicks email link with hash] -- Calls loginExternalCustomer --> B(Was customer logged in?)
B -- No --> F(Calls activateExternalCustomerByToken)
B -- Yes --> D(Customer exist in both Jetshop and Voyado.)
F --> E(Customer found?)
E -- No --> I
E -- Yes --> H(Checks customer status)
H -- NON_EXISTING_CUSTOMER --> I(Customer not found. <br> If configured, redirect to signup page)
H -- PREEXISTING_CUSTOMER --> J(Logged in)
H -- ACTIVATION_REQUIRED --> K(Activates customer. <br> )
K -- Sends email with information <br> on how to set password --> M(Customer activated)
H -- ADDITIONAL_USER_DATA_REQUIRED --> L(Populates signup form <br> with masked data from Voyado)
Note: the UI between some of the states can be controlled with the return values from useSoftLoginStatus.
Lookup chart
graph TD
A[Customer fills LookupField] -- Calls externalCustomerLookup --> B(Checks customer status)
B -- NON_EXISTING_CUSTOMER --> C(Returns no customer data)
C -- If configured, calls personLookup --> E(Customer data found)
E -- No -->F(Customer manually <br> fills required data)
E -- Yes -->G(Prefill signup form <br> with personLookup data)
F --> H
G --> H(Customer signed up in <br> both Jetshop and Voyado. <br> Can login.)
B -- PREEXISTING_CUSTOMER --> I(Returns status)
I --> J(Can login)
B -- ACTIVATION_REQUIRED --> K(Returns status <br> masked email <br> external id)
K -- Calls activateExternalCustomerById --> L(Success)
L -- No --> M(Present error message)
L -- Yes, sends email with information <br> on how to set password --> N(Customer activated. <br> Soft logged in)
B -- ADDITIONAL_USER_DATA_REQUIRED --> O(Returns masked customer data)
B -- COUNTRY_NOT_VALID --> QX(Returns countryCode)
QX --> QX2(Present error message)
O --> P(Populates signup form <br> with masked data)
P --> Q(Customer manually fills <br>missing required data)
Q --> Q1(Customer created. <br> Can login.)
Note: the UI between some of the states can be controlled with the return values from useVoyadoLookup.
https://gitlab.jetshop.se/flight/voyado, with the following key Voyado components:
A complete Voyado setup is available at- 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