Wizard code examples
Here are some examples of how we can use the the createWizard()
builder function to create a workflow of pages.
Code samples can be copied to clipboard or wrapped - hover over one to see the buttons.
Very simple example
This example shows the most basic functionality of the wizard - it helps put steps in order and helps you navigate between them.
Here is some sample step configuration for this:
wizard-config.ts
import { createWizard, WizardSteps } from '@krakentech/blueprint-onboarding';
export enum StepName {
Step1 = 'step1',
Step2 = 'step2',
HiddenStep = 'hiddenstep',
Step3 = 'step3',
Success = 'success',
}
export const route1 = 'page1';
export const route2 = 'page2';
export const route3 = 'page3';
export const hiddenStepRoute = 'hidden-step';
export const successRoute = 'success';
export const steps = [
{
name: StepName.Step1,
pathname: route1,
stepConfig: {
title: 'Step 1',
},
},
{
name: StepName.Step2,
pathname: route2,
stepConfig: {
title: 'Step 2',
},
},
{
name: StepName.HiddenStep,
pathname: hiddenStepRoute,
},
{
name: StepName.Step3,
pathname: route3,
stepConfig: {
title: 'Step 3',
},
},
{
name: StepName.Success,
pathname: successRoute,
},
] as const satisfies WizardSteps<StepName>;
export const simpleWizardConfig = {
storageKey: 'simple-wizard',
defaultFormValues: {},
stepNames: StepName,
};
export const {
useWizard: useSimpleWizard,
WizardProvider: SimpleWizardProvider,
} = createWizard(simpleWizardConfig);
Some notes on the above set up:
- You can provide steps with no config if you don't want them to show up in the stepper. The
stepperProps.activeStep
will be 0 on those steps.
wizard-layout.tsx
Then you'll need to pass a routing function to the provider. This example uses NextJS and a pageLayout
import { SimpleWizardProvider, steps } from '@your-app/wizard-config';
import { useRouter } from 'next/router';
import { FC, PropsWithChildren, ReactElement } from 'react';
const SimpleWizardLayout: FC<PropsWithChildren> = ({ children }) => {
const router = useRouter();
return (
<SimpleWizardProvider
pathname={router.asPath?.split('?')[0]}
routingFn={router.push}
steps={steps}
>
{children}
</SimpleWizardProvider>
);
};
export const getSimpleWizardLayout = (page: ReactElement) => (
<SimpleWizardLayout>{page}</SimpleWizardLayout>
);
page-1.tsx
This is some simple page content that invokes the wizard utilities, and adds the provider via a call to getLayout
(NextJS)
import { Button, Typography, Stepper } from '@krakentech/coral';
import { useSimpleWizard } from '@your-app/wizard-config';
import { getSimpleWizardLayout } from '@your-app/wizard-layout';
export const Page1 = () => {
const { toNextStep, stepperProps } = useSimpleWizard();
return (
<>
<Stepper {...stepperProps} />
<Typography variant="h1">Page 1</Typography>
<Container maxWidth={200}>
<Button fullWidth onClick={() => toNextStep()}>
Next
</Button>
</Container>
</>
);
};
Page1.getLayout = getSimpleWizardLayout;
Simple example with some form inputs
This example extends the basic wizard a little - it adds some form inputs to the pages and displays their values on the final screen.
From wizard config for form inputs
Add this to wizard-config.ts
import { createWizard, WizardSteps } from '@krakentech/blueprint-onboarding';
export const route1 = 'page1';
export const route2 = 'page2';
export const route3 = 'confirmation';
export enum StepName {
Step1 = 'step1',
Step2 = 'step2',
Confirmation = 'confirmation',
}
// pass this into SimpleFormWizardProvider
const getSteps = ({ affiliateCode }: SimpleFormValues) =>
[
{
name: StepName.Step1,
pathname: route1,
queryParams: affiliateCode
? {
affiliateCode: affiliateCode,
}
: undefined,
stepConfig: {
title: 'Step 1',
},
},
{
name: StepName.Step2,
pathname: route2,
stepConfig: {
title: 'Step 2',
},
},
{
name: StepName.Confirmation,
pathname: route3,
stepConfig: {
title: 'Confirmation',
},
},
] as const satisfies WizardSteps<StepName>;
export enum SimpleFormKey {
// Step1
Name = 'name',
AffiliateCode = 'affiliateCode',
// Step2
Email = 'email',
}
export type SimpleFormValues = {
[SimpleFormKey.Name]: string;
[SimpleFormKey.Email]: string;
[SimpleFormKey.AffiliateCode]: string;
};
const defaultFormValues: SimpleFormValues = {
// Supply
[SimpleFormKey.Name]: '',
[SimpleFormKey.Email]: '',
[SimpleFormKey.AffiliateCode]: '',
};
export const {
useWizard: useSimpleFormWizard,
WizardProvider: SimpleFormWizardProvider,
WizardFormHydrator: SimpleFormWizardFormHydrator,
} = createWizard({
storageKey: 'simple-form-sandbox',
defaultFormValues,
stepNames: StepName,
});
Then add this to the page-1.tsx
template
import { Form, Formik } from 'formik';
import { Stack, Typography } from '@krakentech/coral';
import { FormikSubmitButton, FormikTextField } from '@krakentech/coral-formik';
import {
SimpleFormKey,
SimpleFormWizardFormHydrator,
useSimpleFormWizard,
} from '@your-app/wizard-config.ts';
const Page1 = ({ affiliateCode }: { affiliateCode: string }) => {
const { toNextStep, formValues, setFormValues } = useSimpleFormWizard();
return (
<Formik
initialValues={formValues}
onSubmit={async (values) => {
const newValues =
typeof affiliateCode === 'string'
? { ...values, affiliateCode }
: values;
setFormValues(newValues);
await toNextStep();
}}
>
{() => (
<Form>
<SimpleFormWizardFormHydrator />
<Stack direction="vertical" gap="xl" alignItems="center">
<Typography variant="h3" component="h1">
Step 1
</Typography>
<FormikTextField
name={SimpleFormKey.Name}
label="Name"
/>
<FormikSubmitButton>Next Step</FormikSubmitButton>
</Stack>
</Form>
)}
</Formik>
);
};
Example onboarding process
This is a fuller example for gathering customer data to onboarding them for electricity supply. If you choose the first tariff option, we add another step to collect a meter reading.
Parent steps and dynamic steps
Building on the previous two examples, here are the changes to the configuration to make the steps based on a choice in the form.
These steps also use the parentName
attribute to indicate that you should only see one child step of the parent. Steps which are defined as substeps, i.e., steps which have a parentName
, will not appear in the stepper.
import {
WizardSteps,
} from '@krakentech/blueprint-onboarding';
...
const basicSteps = [
{
name: StepName.Supply,
pathname: supplyPathname,
stepConfig: {
title: 'Supply',
},
},
{
name: StepName.TariffSelection,
pathname: tariffSelectionPathname,
stepConfig: {
title: 'Tariff Selection',
},
},
{
name: StepName.DetailsPersonal,
pathname: personalDetailsPathname,
stepConfig: {
title: 'Details',
},
},
{
name: StepName.DetailsGeneral,
parentName: StepName.DetailsPersonal,
pathname: generalDetailsPathname,
},
{
name: StepName.DetailsPayment,
parentName: StepName.DetailsPersonal,
pathname: paymentDetailsPathname,
},
{
name: StepName.Readings,
pathname: readingsPathname,
stepConfig: {
title: 'Readings',
},
},
{
name: StepName.Confirmation,
pathname: confirmationPathname,
stepConfig: {
title: 'Confirmation',
},
},
] as const satisfies WizardSteps<StepName>;
const defaultSteps = (formValues: OnboardingFormValues) =>
[
{
name: StepName.Supply,
pathname: supplyPathname,
queryParams: formValues.affiliateCode
? {
affiliateCode: formValues.affiliateCode,
}
: undefined,
stepConfig: {
title: 'Supply',
},
},
{
name: StepName.TariffSelection,
pathname: tariffSelectionPathname,
stepConfig: {
title: 'Tariff Selection',
},
},
{
name: StepName.DetailsPersonal,
pathname: personalDetailsPathname,
stepConfig: {
title: 'Details',
},
},
{
name: StepName.DetailsGeneral,
parentName: StepName.DetailsPersonal,
pathname: generalDetailsPathname,
},
{
name: StepName.DetailsPayment,
parentName: StepName.DetailsPersonal,
pathname: paymentDetailsPathname,
},
{
name: StepName.DetailsPaymentAlt,
parentName: StepName.DetailsPersonal,
pathname: paymentAltDetailsPathname,
},
{
name: StepName.Confirmation,
pathname: confirmationPathname,
stepConfig: {
title: 'Confirmation',
},
},
] as const satisfies WizardSteps<StepName>;
// ... pass this into WizardProvider (see below) ...
export const steps = (formValues: OnboardingFormValues) => {
if (formValues.tariffName === 'standard_fixed') {
return basicSteps;
}
return defaultSteps(formValues);
};
// initialise your wizard
export const {
useWizard: useOnboardingWizard,
WizardProvider: OnboardingWizardProvider,
WizardFormHydrator: OnboardingWizardFormHydrator,
} = createWizard({
storageKey: 'onboarding-sandbox',
defaultFormValues,
stepNames: StepName,
storage: storageConfig,
parseFormValues,
serializeFormValues,
});
// ... then pass your step function into your WizardProvider (possibly in another file with access to your router function) ...
export const OnboardingProvider = ({
children,
pathname,
push,
}: {
children: ReactNode | undefined;
pathname: string;
push: (url: UrlObject) => Promise<void | boolean> | void | boolean;
}) => {
return (
<OnboardingWizardProvider
pathname={pathname}
routingFn={push}
steps={steps}
>
{children}
</OnboardingWizardProvider>
);
};
Storing values in local storage to resume a journey
If you want to allow users to resume a journey from a link, one option is to store values in local storage.
For that we need to pass allowArbitraryStart: true
and storage
to createWizard
, and ensure we clear the storage using resetFormValues
.
import { createWizard, StorageConfig } from '@krakentech/blueprint-onboarding';
...
export const storageConfig: StorageConfig = {
read: (storageKey) => {
const json = localStorage.getItem(storageKey);
if (json === null) {
return null;
}
return JSON.parse(json);
},
write: (storageKey, values) => {
localStorage.setItem(storageKey, JSON.stringify(values));
},
};
...
export const {
useWizard: useSimpleFormWizard,
WizardProvider: SimpleFormWizardProvider,
WizardFormHydrator: SimpleFormWizardFormHydrator,
} = createWizard({
storageKey: 'simple-form-sandbox',
defaultFormValues,
stepNames: StepName,
allowArbitraryStart: true,
storage: storageConfig,
});