Skip to content

Commit aa870d0

Browse files
authored
feat(save-draft-button) - add save draft button functionality (#791)
* add savedrafbutton * add tests * fix * fix edge case * revert
1 parent cef7bac commit aa870d0

8 files changed

Lines changed: 606 additions & 9 deletions

File tree

example/src/ContractorOnboarding.tsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ const MultiStepForm = ({
148148
ContractDetailsStep,
149149
ContractPreviewStep,
150150
ContractReviewButton,
151+
SaveDraftButton,
151152
} = components;
152153
const [errors, setErrors] = useState<{
153154
apiError: string;
@@ -204,6 +205,19 @@ const MultiStepForm = ({
204205
>
205206
Back
206207
</BackButton>
208+
<SaveDraftButton
209+
className='submit-button'
210+
onSuccess={() => {
211+
console.log('Draft saved successfully');
212+
setErrors({ apiError: '', fieldErrors: [] });
213+
}}
214+
onError={({ error, fieldErrors }) => {
215+
console.log('Draft save failed', error);
216+
setErrors({ apiError: error.message, fieldErrors });
217+
}}
218+
>
219+
Save Draft
220+
</SaveDraftButton>
207221
<SubmitButton
208222
className='submit-button'
209223
onClick={() => setErrors({ apiError: '', fieldErrors: [] })}
@@ -263,6 +277,19 @@ const MultiStepForm = ({
263277
>
264278
Back
265279
</BackButton>
280+
<SaveDraftButton
281+
className='submit-button'
282+
onSuccess={() => {
283+
console.log('Contract details draft saved successfully');
284+
setErrors({ apiError: '', fieldErrors: [] });
285+
}}
286+
onError={({ error, fieldErrors }) => {
287+
console.log('Contract details draft save failed', error);
288+
setErrors({ apiError: error.message, fieldErrors });
289+
}}
290+
>
291+
Save Draft
292+
</SaveDraftButton>
266293
<SubmitButton
267294
className='submit-button'
268295
onClick={() => setErrors({ apiError: '', fieldErrors: [] })}

src/flows/ContractorOnboarding/ContractorOnboarding.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { ContractPreviewStep } from '@/src/flows/ContractorOnboarding/components
1313
import { OnboardingInvite } from '@/src/flows/ContractorOnboarding/components/OnboardingInvite';
1414
import { ContractReviewButton } from '@/src/flows/ContractorOnboarding/components/ContractReviewButton';
1515
import { EligibilityQuestionnaireStep } from '@/src/flows/ContractorOnboarding/components/EligibilityQuestionnaireStep';
16+
import { SaveDraftButton } from '@/src/flows/ContractorOnboarding/components/SaveDraftButton';
1617

1718
export const ContractorOnboardingFlow = ({
1819
render,
@@ -56,6 +57,7 @@ export const ContractorOnboardingFlow = ({
5657
ContractPreviewStep: ContractPreviewStep,
5758
OnboardingInvite: OnboardingInvite,
5859
ContractReviewButton: ContractReviewButton,
60+
SaveDraftButton: SaveDraftButton,
5961
},
6062
})}
6163
</ContractorOnboardingContext.Provider>

src/flows/ContractorOnboarding/components/ContractorOnboardingForm.tsx

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
PricingPlanFormPayload,
1313
ContractorOnboardingContractDetailsFormPayload,
1414
} from '@/src/flows/ContractorOnboarding/types';
15+
import { normalizeFieldErrors } from '@/src/lib/mutations';
1516

1617
type ContractorOnboardingFormProps = {
1718
onSubmit: (
@@ -76,8 +77,39 @@ export function ContractorOnboardingForm({
7677
// eslint-disable-next-line react-hooks/exhaustive-deps
7778
}, []);
7879

79-
const handleSubmit = async (values: Record<string, unknown>) => {
80-
await onSubmit(values as $TSFixMe);
80+
const handleSubmit = async (
81+
values: Record<string, unknown>,
82+
event?: React.BaseSyntheticEvent,
83+
) => {
84+
const nativeEvent = event?.nativeEvent as $TSFixMe;
85+
86+
if (nativeEvent?.isDraftSubmission) {
87+
const { onSuccess, onError } = nativeEvent.draftCallbacks;
88+
89+
try {
90+
await contractorOnboardingBag.onSubmit(values);
91+
onSuccess?.();
92+
} catch (error: $TSFixMe) {
93+
const currentStepName =
94+
contractorOnboardingBag.stepState.currentStep.name;
95+
const currentStepFields =
96+
contractorOnboardingBag.meta?.fields?.[
97+
currentStepName as keyof typeof contractorOnboardingBag.meta.fields
98+
];
99+
100+
const normalizedFieldErrors = normalizeFieldErrors(
101+
error?.fieldErrors || [],
102+
currentStepFields,
103+
);
104+
onError?.({
105+
error: error?.error || (error as Error),
106+
rawError: error?.rawError || (error as Record<string, unknown>),
107+
fieldErrors: normalizedFieldErrors,
108+
});
109+
}
110+
} else {
111+
await onSubmit(values as $TSFixMe);
112+
}
81113
};
82114

83115
return (
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { useFormFields } from '@/src/context';
2+
import { useContractorOnboardingContext } from '@/src/flows/ContractorOnboarding/context';
3+
import { ButtonHTMLAttributes } from 'react';
4+
import { NormalizedFieldError } from '@/src/lib/mutations';
5+
import { $TSFixMe } from '@/src/types/remoteFlows';
6+
7+
type SaveDraftButtonProps = Omit<
8+
ButtonHTMLAttributes<HTMLButtonElement>,
9+
'onError'
10+
> & {
11+
onSuccess?: () => void | Promise<void>;
12+
onError?: ({
13+
error,
14+
rawError,
15+
fieldErrors,
16+
}: {
17+
error: Error;
18+
rawError: Record<string, unknown>;
19+
fieldErrors: NormalizedFieldError[];
20+
}) => void;
21+
};
22+
23+
export const SaveDraftButton = ({
24+
onSuccess,
25+
onError,
26+
className,
27+
children,
28+
disabled = false,
29+
...props
30+
}: SaveDraftButtonProps) => {
31+
const { contractorOnboardingBag, formId } = useContractorOnboardingContext();
32+
33+
const { components } = useFormFields();
34+
35+
const handleSaveDraft = async () => {
36+
const form = document.getElementById(formId);
37+
if (form) {
38+
const submitEvent = new Event('submit', {
39+
bubbles: true,
40+
cancelable: true,
41+
});
42+
(submitEvent as $TSFixMe).isDraftSubmission = true;
43+
(submitEvent as $TSFixMe).draftCallbacks = { onSuccess, onError };
44+
form.dispatchEvent(submitEvent);
45+
}
46+
};
47+
48+
const CustomButton = components?.button;
49+
if (!CustomButton) {
50+
throw new Error(`Button component not found`);
51+
}
52+
53+
return (
54+
<CustomButton
55+
{...props}
56+
onClick={handleSaveDraft}
57+
disabled={disabled || contractorOnboardingBag.isSubmitting}
58+
className={className}
59+
>
60+
{children}
61+
</CustomButton>
62+
);
63+
};

src/flows/ContractorOnboarding/hooks.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -997,7 +997,6 @@ export const useContractorOnboarding = ({
997997
return;
998998
}
999999
case 'contract_details': {
1000-
console.log('parsedValues', parsedValues);
10011000
const shouldSkipAiChecks =
10021001
fieldValues.services_and_deliverables_error_skippable === true;
10031002
const payload: CreateContractDocument = {

0 commit comments

Comments
 (0)