Skip to content

Commit

Permalink
yourgov: Make steps mutable by decoupling components from array order
Browse files Browse the repository at this point in the history
  • Loading branch information
ChrisLaneAU committed Jan 22, 2025
1 parent 6c7351c commit e4591a3
Show file tree
Hide file tree
Showing 48 changed files with 1,400 additions and 1,033 deletions.
5 changes: 5 additions & 0 deletions .changeset/yellow-rings-carry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@ag.ds-next/yourgov': minor
---

yourgov: Edit application from Review and Submit step as a substep, rather than being dropped back into the form-step flow.
11 changes: 0 additions & 11 deletions yourgov/components/FormMobileFoodVendorPermit/FormState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
GlobalStaffState,
type FormState as StepsFormState,
} from './steps/FormState';
import { type StepNumber } from './steps/FormProvider';

export type Completion = {
completed: boolean;
Expand All @@ -24,16 +23,6 @@ export const defaultFormState: DeepPartial<FormState> = {
steps: defaultStepsFormState,
};

export type FormStep<StepNum extends StepNumber = StepNumber> = {
label: string;
href: string;
formStateKey: StepNum;
items?: Array<{
href: string;
label: string;
}>;
};

/**
* React Form does not apply the errors types correctly to fields stored as objects.
* This type treats those object fields as simple boolean fields as a workaround.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { Stack } from '@ag.ds-next/react/stack';
import { TextLinkExternal } from '@ag.ds-next/react/text-link';
import { zodString } from '../../lib/zodUtils';
import { useGlobalForm } from './GlobalFormProvider';
import { stepsData } from './steps/stepsData';

const formSchema = z.object({
businessType: zodString('Business type is required'),
Expand Down Expand Up @@ -41,8 +42,7 @@ export function GettingStartedForm() {
type: data.businessType,
});
router.push({
pathname:
'/app/permits/apply-for-new-permit/mobile-food-vendor-permit/steps/step-1',
pathname: stepsData[0].href, // Always start a new application on the first step
});
}

Expand Down
28 changes: 19 additions & 9 deletions yourgov/components/FormMobileFoodVendorPermit/StepActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ import { useTernaryState } from '@ag.ds-next/react/core';
import { Divider } from '@ag.ds-next/react/divider';
import { useGlobalForm } from './GlobalFormProvider';

export function StepActions({ submitText = 'Save and continue' }) {
export function StepActions({
hasSaveAndExit = true,
submitText = 'Save and continue',
}) {
const [isModalOpen, openModal, closeModal] = useTernaryState(false);

const { isSubmittingStep, saveAndExit, isSavingBeforeExiting, cancel } =
Expand All @@ -17,27 +20,34 @@ export function StepActions({ submitText = 'Save and continue' }) {
<Fragment>
<Stack gap={3}>
<Divider />

<ButtonGroup>
<Button loading={isSubmittingStep} type="submit" variant="primary">
{submitText}
</Button>
<Button
loading={isSavingBeforeExiting}
onClick={saveAndExit}
type="submit"
variant="secondary"
>
Save and exit
</Button>

{hasSaveAndExit && (
<Button
loading={isSavingBeforeExiting}
onClick={saveAndExit}
type="submit"
variant="secondary"
>
Save and exit
</Button>
)}

<Button onClick={openModal} type="button" variant="tertiary">
Cancel
</Button>
</ButtonGroup>
</Stack>

<Modal
actions={
<ButtonGroup>
<Button onClick={cancel}>Yes, cancel</Button>

<Button onClick={closeModal} variant="secondary">
No, take me back
</Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { Button } from '@ag.ds-next/react/button';
import { Stack } from '@ag.ds-next/react/stack';
import { DeleteIcon, UploadIcon } from '@ag.ds-next/react/icon';
import { StatusBadge } from '@ag.ds-next/react/status-badge';
import { Document } from './steps/FormStep9';
import { Document } from './steps/StepUploadDocumentsForm';

type UploadFileTableProps = {
/** The id of the element to assign the table an accessible name. */
Expand Down
29 changes: 15 additions & 14 deletions yourgov/components/FormMobileFoodVendorPermit/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@ export { GlobalFormProvider, useGlobalForm } from './GlobalFormProvider';
// Getting started forms
export { GettingStartedForm } from './GettingStartedForm';

export { FormProvider, formSteps } from './steps/FormProvider';
export { FormStep1 } from './steps/FormStep1';
export { FormStep1ChangeDetails } from './steps/FormStep1ChangeDetails';
export { FormStep2 } from './steps/FormStep2';
export { FormStep3 } from './steps/FormStep3';
export { FormStep4 } from './steps/FormStep4';
export { FormStep5 } from './steps/FormStep5';
export { FormStep6 } from './steps/FormStep6';
export { FormStep7 } from './steps/FormStep7';
export { FormStep7AddEmployee } from './steps/FormStep7AddEmployee';
export { FormStep8 } from './steps/FormStep8';
export { FormStep9 } from './steps/FormStep9';
export { FormStep10 } from './steps/FormStep10';
export { FormStep10Review } from './steps/FormStep10Review';
export { stepsData } from './steps/stepsData';
export { FormProvider } from './steps/FormProvider';
export { StepOwnerDetailsForm } from './steps/StepOwnerDetailsForm';
export { StepOwnerDetailsChangeDetailsForm } from './steps/StepOwnerDetailsChangeDetailsForm';
export { StepBusinessDetailsForm } from './steps/StepBusinessDetailsForm';
export { StepBusinessAddressForm } from './steps/StepBusinessAddressForm';
export { StepVehicleRegistrationForm } from './steps/StepVehicleRegistrationForm';
export { StepTradingTimeForm } from './steps/StepTradingTimeForm';
export { StepFoodServedForm } from './steps/StepFoodServedForm';
export { StepEmployeesForm } from './steps/StepEmployeesForm';
export { StepEmployeesAddEmployeeForm } from './steps/StepEmployeesAddEmployeeForm';
export { StepFoodSafetySupervisorForm } from './steps/StepFoodSafetySupervisorForm';
export { StepUploadDocumentsForm } from './steps/StepUploadDocumentsForm';
export { StepReviewAndSubmitForm } from './steps/StepReviewAndSubmitForm';
export { ReviewAndSubmitStepsSummary } from './steps/ReviewAndSubmitStepsSummary';
25 changes: 25 additions & 0 deletions yourgov/components/FormMobileFoodVendorPermit/steps/Form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { FormEventHandler, ReactNode } from 'react';
import { Stack } from '@ag.ds-next/react/stack';
import { StepActions } from '../StepActions';

type FormProps = {
children?: ReactNode;
noValidate?: boolean;
onSubmit: FormEventHandler<HTMLFormElement>;
submitText?: string;
};

export const Form = ({
children,
noValidate = true,
onSubmit,
submitText,
}: FormProps) => {
return (
<Stack as="form" gap={3} noValidate={noValidate} onSubmit={onSubmit}>
{children}

<StepActions submitText={submitText} />
</Stack>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import {
import { Stack } from '@ag.ds-next/react/stack';
import { useGlobalForm } from '../GlobalFormProvider';
import { FormContainer as GlobalFormContainer } from '../FormContainer';
import { formSteps, useFormContext } from './FormProvider';
import { useFormContext } from './FormProvider';
import { stepsData } from './stepsData';

type FormContainerProps = PropsWithChildren<{
formTitle: string;
Expand All @@ -28,21 +29,21 @@ export function FormContainer({
hideRequiredFieldsMessage,
shouldFocusTitle = true,
}: FormContainerProps) {
const { pathname } = useRouter();
const { asPath, pathname } = useRouter();
const { formState, startApplication } = useGlobalForm();
const { backHref, canConfirmAndSubmit } = useFormContext();

function getStepStatus(stepIndex: number): ProgressIndicatorItemStatus {
const step = formSteps[stepIndex];
const step = stepsData[stepIndex];
const stateStep = formState.steps?.[step.formStateKey];
// Current step is always in progress when the URL matches
if (step.href === pathname) return 'started';
// After submitting each step, the `completed` key is set to `true`
if (stateStep?.completed) return 'done';
// The user has save and exited
if (stateStep?.started) return 'started';
// The final step (confirm and submit) can only be viewed when all previous steps are complete
if (step.formStateKey === 'step10' && !canConfirmAndSubmit)
// The final step (review and submit) can only be viewed when all previous steps are complete
if (step.formStateKey === 'stepReviewAndSubmit' && !canConfirmAndSubmit)
return 'blocked';
// Otherwise, the step still needs to be done
return 'todo';
Expand All @@ -57,8 +58,8 @@ export function FormContainer({
<Column columnSpan={{ xs: 12, md: 4, lg: 3 }}>
<ContentBleed visible={{ md: false }}>
<ProgressIndicator
activePath={pathname}
items={formSteps.map(({ label, href }, index) => ({
activePath={asPath}
items={stepsData.map(({ label, href }, index) => ({
label,
href,
status: getStepStatus(index),
Expand Down
107 changes: 16 additions & 91 deletions yourgov/components/FormMobileFoodVendorPermit/steps/FormProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,85 +7,8 @@ import {
useMemo,
} from 'react';
import { useGlobalForm } from '../GlobalFormProvider';
import { type FormStep } from '../FormState';
import { formHomePage, getStepCompletionUrl } from '../utils';

export type StepNumber =
| 'step1'
| 'step2'
| 'step3'
| 'step4'
| 'step5'
| 'step6'
| 'step7'
| 'step8'
| 'step9'
| 'step10';

export const formSteps: Array<FormStep<StepNumber>> = [
{
formStateKey: 'step1',
label: 'Owner details',
href: formHomePage + '/steps/step-1',
items: [
{
label: 'Change business owner details',
href: formHomePage + '/steps/step-1/change-details',
},
],
},
{
formStateKey: 'step2',
label: 'Business details',
href: formHomePage + '/steps/step-2',
},
{
formStateKey: 'step3',
label: 'Business address',
href: formHomePage + '/steps/step-3',
},
{
formStateKey: 'step4',
label: 'Vehicle registration',
href: formHomePage + '/steps/step-4',
},
{
formStateKey: 'step5',
label: 'Trading time',
href: formHomePage + '/steps/step-5',
},
{
formStateKey: 'step6',
label: 'Food served',
href: formHomePage + '/steps/step-6',
},
{
formStateKey: 'step7',
label: 'Employees',
href: formHomePage + '/steps/step-7',
items: [
{
label: 'Add employee',
href: formHomePage + '/steps/step-7/add-employee',
},
],
},
{
formStateKey: 'step8',
label: 'Food safety supervisor',
href: formHomePage + '/steps/step-8',
},
{
formStateKey: 'step9',
label: 'Upload documents',
href: formHomePage + '/steps/step-9',
},
{
formStateKey: 'step10',
label: 'Review and submit',
href: formHomePage + '/steps/step-10',
},
];
import { applyForFoodPermitPage, getStepCompletionUrl } from '../utils';
import { stepsData } from './stepsData';

type ContextType = {
/** The href of the previous step. */
Expand All @@ -103,7 +26,7 @@ export function FormProvider({ children }: PropsWithChildren<{}>) {
const { setIsSubmittingStep, formState, isSavingBeforeExiting } =
useGlobalForm();

const currentStepIndex = formSteps.findIndex(({ href }) => href === pathname);
const currentStepIndex = stepsData.findIndex(({ href }) => href === pathname);

// Callback function to submit the current step
const submitStep = useCallback(async () => {
Expand All @@ -117,7 +40,7 @@ export function FormProvider({ children }: PropsWithChildren<{}>) {
const stepCompletionUrl = getStepCompletionUrl({
currentStepIndex,
id: formState.id,
steps: formSteps,
steps: stepsData,
});

push(stepCompletionUrl);
Expand All @@ -132,20 +55,22 @@ export function FormProvider({ children }: PropsWithChildren<{}>) {
]);

// The href of the previous step
const backHref = `${formSteps[currentStepIndex - 1]?.href ?? formHomePage}`;
const backHref = `${
stepsData[currentStepIndex - 1]?.href ?? applyForFoodPermitPage
}`;

// If true, the user can access the "confirm and submit step"
const canConfirmAndSubmit = useMemo(() => {
if (
!formState.steps?.step1?.completed ||
!formState.steps?.step2?.completed ||
!formState.steps?.step3?.completed ||
!formState.steps?.step4?.completed ||
!formState.steps?.step5?.completed ||
!formState.steps?.step6?.completed ||
!formState.steps?.step7?.completed ||
!formState.steps?.step8?.completed ||
!formState.steps?.step9?.completed
!formState.steps?.stepOwnerDetails?.completed ||
!formState.steps?.stepBusinessDetails?.completed ||
!formState.steps?.stepBusinessAddress?.completed ||
!formState.steps?.stepVehicleRegistration?.completed ||
!formState.steps?.stepTradingTime?.completed ||
!formState.steps?.stepFoodServed?.completed ||
!formState.steps?.stepEmployees?.completed ||
!formState.steps?.stepFoodSafetySupervisor?.completed ||
!formState.steps?.stepUploadDocuments?.completed
) {
return false;
}
Expand Down
Loading

0 comments on commit e4591a3

Please sign in to comment.