From 1bf542a4db752e837b607a43e140b974b5122931 Mon Sep 17 00:00:00 2001 From: Gabriel Garcia Date: Fri, 5 Jun 2026 11:57:27 +0200 Subject: [PATCH 1/3] add preview employment step --- example/src/Onboarding.tsx | 31 +++++++- src/flows/Onboarding/OnboardingFlow.tsx | 2 + .../PreviewEmploymentAgreementStep.tsx | 75 +++++++++++++++++++ src/flows/Onboarding/hooks.tsx | 24 +++++- src/flows/Onboarding/types.ts | 8 +- src/flows/Onboarding/utils.ts | 7 ++ 6 files changed, 144 insertions(+), 3 deletions(-) create mode 100644 src/flows/Onboarding/components/PreviewEmploymentAgreementStep.tsx diff --git a/example/src/Onboarding.tsx b/example/src/Onboarding.tsx index 1104c2237..7335afbae 100644 --- a/example/src/Onboarding.tsx +++ b/example/src/Onboarding.tsx @@ -13,6 +13,7 @@ import { EngagementAgreementDetailsFormPayload, ZendeskTriggerButton, zendeskArticles, + $TSFixMe, } from '@remoteoss/remote-flows'; import React, { useState } from 'react'; import { Card } from '@remoteoss/remote-flows/internals'; @@ -90,6 +91,7 @@ const MultiStepForm = ({ components, onboardingBag }: MultiStepFormProps) => { BackButton, SelectCountryStep, EngagementAgreementDetailsStep, + PreviewEmploymentAgreementStep, } = components; const [errors, setErrors] = useState<{ apiError: string; @@ -268,6 +270,33 @@ const MultiStepForm = ({ components, onboardingBag }: MultiStepFormProps) => { ); } + case 'employment_agreement_preview': { + return ( + <> + console.log('payload', payload)} + onSuccess={(data: $TSFixMe) => console.log('data', data)} + onError={({ error, fieldErrors }) => + setErrors({ apiError: error.message, fieldErrors }) + } + /> +
+ setErrors({ apiError: '', fieldErrors: [] })} + > + Previous Step + + setErrors({ apiError: '', fieldErrors: [] })} + > + Continue + +
+ + ); + } case 'review': return ( diff --git a/src/flows/Onboarding/components/PreviewEmploymentAgreementStep.tsx b/src/flows/Onboarding/components/PreviewEmploymentAgreementStep.tsx new file mode 100644 index 000000000..ba4835100 --- /dev/null +++ b/src/flows/Onboarding/components/PreviewEmploymentAgreementStep.tsx @@ -0,0 +1,75 @@ +import { useOnboardingContext } from '@/src/flows/Onboarding/context'; +import { OnboardingForm } from '@/src/flows/Onboarding/components/OnboardingForm'; +import { $TSFixMe } from '@/src/types/remoteFlows'; +import { + NormalizedFieldError, + normalizeFieldErrors, +} from '@/src/lib/mutations'; +import { SuccessResponse } from '@/src/client'; + +type PreviewEmploymentAgreementStepProps = { + /** + * The function is called when the form is submitted. It receives the form values as an argument. + */ + onSubmit?: (payload: $TSFixMe) => void | Promise; + /** + * The function is called when the form submission is successful. + */ + onSuccess?: (data: $TSFixMe) => void | Promise; + /** + * The function is called when an error occurs during form submission. + */ + onError?: ({ + error, + rawError, + fieldErrors, + }: { + error: Error; + rawError: Record; + fieldErrors: NormalizedFieldError[]; + }) => void; +}; + +export function PreviewEmploymentAgreementStep({ + onSubmit, + onSuccess, + onError, +}: PreviewEmploymentAgreementStepProps) { + const { onboardingBag } = useOnboardingContext(); + + const handleSubmit = async (payload: $TSFixMe) => { + try { + const parsedValues = await onboardingBag.parseFormValues(payload); + await onSubmit?.(parsedValues as $TSFixMe); + const response = await onboardingBag.onSubmit(payload); + if (response?.data) { + await onSuccess?.(response?.data as SuccessResponse); + onboardingBag?.next(); + return; + } + + if (response?.error) { + const normalizedFieldErrors = normalizeFieldErrors( + response?.fieldErrors || [], + {}, + ); + + onError?.({ + error: response?.error, + rawError: response?.rawError, + fieldErrors: normalizedFieldErrors, + }); + } + } catch (error: unknown) { + onError?.({ + error: error as Error, + rawError: error as Record, + fieldErrors: [], + }); + } + }; + + // this step in theory shouldn't be a form, not sure yet... + + return ; +} diff --git a/src/flows/Onboarding/hooks.tsx b/src/flows/Onboarding/hooks.tsx index e796c908b..c044dab26 100644 --- a/src/flows/Onboarding/hooks.tsx +++ b/src/flows/Onboarding/hooks.tsx @@ -3,6 +3,7 @@ import { Employment, EmploymentCreateParams, EmploymentFullParams, + SuccessResponse, } from '@/src/client'; import { JSFFields, NestedMeta } from '@/src/types/remoteFlows'; import { useStepState, Step } from '@/src/flows/useStepState'; @@ -63,6 +64,7 @@ const stepToFormSchemaMap: Record = { engagement_agreement_details: null, contract_details: 'contract_details', benefits: null, + employment_agreement_preview: null, review: null, }; @@ -171,6 +173,7 @@ export const useOnboarding = ({ ] = useState(false); const useDynamicSteps = options?.features?.includes('dynamic_steps') ?? false; + const useEAPreview = options?.features?.includes('ea_preview') ?? false; const { steps, stepsArray } = useMemo( () => @@ -178,8 +181,14 @@ export const useOnboarding = ({ includeSelectCountry: !skipSteps?.includes('select_country'), includeEngagementAgreementDetails, useDynamicSteps, + useEAPreview, }), - [includeEngagementAgreementDetails, skipSteps, useDynamicSteps], + [ + includeEngagementAgreementDetails, + skipSteps, + useDynamicSteps, + useEAPreview, + ], ); const onStepChange = useCallback( @@ -260,12 +269,14 @@ export const useOnboarding = ({ contract_details: NestedMeta; benefits: NestedMeta; engagement_agreement_details: NestedMeta; + employment_agreement_preview: NestedMeta; }>({ select_country: {}, basic_information: {}, contract_details: {}, benefits: {}, engagement_agreement_details: {}, + employment_agreement_preview: {}, }); const { @@ -577,6 +588,7 @@ export const useOnboarding = ({ engagementAgreementDetailsSchema?.fields || [], contract_details: contractDetailsForm?.fields || [], benefits: benefitOffersSchema?.fields || [], + employment_agreement_preview: [], review: [], }), [ @@ -598,6 +610,7 @@ export const useOnboarding = ({ engagementAgreementDetailsSchema?.meta['x-jsf-fieldsets'], contract_details: contractDetailsForm?.meta['x-jsf-fieldsets'], benefits: null, + employment_agreement_preview: null, review: null, }; @@ -611,6 +624,7 @@ export const useOnboarding = ({ engagementAgreementDetailsSchema?.meta?.['x-jsf-presentation'], contract_details: contractDetailsForm?.meta?.['x-jsf-presentation'], benefits: benefitOffersSchema?.meta?.['x-jsf-presentation'], + employment_agreement_preview: null, review: null, }; @@ -707,6 +721,7 @@ export const useOnboarding = ({ ) : contractDetailsInitialValues, benefits: benefitsInitialValues, + employment_agreement_preview: {}, }; } return { @@ -714,6 +729,7 @@ export const useOnboarding = ({ basic_information: basicInformationInitialValues, contract_details: contractDetailsInitialValues, benefits: benefitsInitialValues, + employment_agreement_preview: {}, }; }, [ selectCountryInitialValues, @@ -792,6 +808,7 @@ export const useOnboarding = ({ stepFields.benefits, { skipMoneyConversion: true }, ), + employment_agreement_preview: {}, }; setStepValues({ @@ -800,6 +817,7 @@ export const useOnboarding = ({ contract_details: contractDetailsInitialValues, benefits: benefitsInitialValues, engagement_agreement_details: engagementAgreementDetailsInitialValues, + employment_agreement_preview: {}, review: {}, }); @@ -960,6 +978,10 @@ export const useOnboarding = ({ ...parsedValues, }); } + case 'employment_agreement_preview': { + // in theory we shouldn't submit any info here... + return Promise.resolve({ data: { status: 'ok' } } as SuccessResponse); + } } return; } diff --git a/src/flows/Onboarding/types.ts b/src/flows/Onboarding/types.ts index 0a4fe4f81..5084e0150 100644 --- a/src/flows/Onboarding/types.ts +++ b/src/flows/Onboarding/types.ts @@ -16,6 +16,7 @@ import { ReviewStep } from '@/src/flows/Onboarding/components/ReviewStep'; import { SaveDraftButton } from '@/src/flows/Onboarding/components/SaveDraftButton'; import { EngagementAgreementDetailsStep } from '@/src/flows/Onboarding/components/EngagementAgreementDetailsStep'; import { PreOnboardingRequirements } from '@/src/flows/Onboarding/components/PreOnboardingRequirements'; +import { PreviewEmploymentAgreementStep } from '@/src/flows/Onboarding/components/PreviewEmploymentAgreementStep'; export type OnboardingRenderProps = { /** @@ -40,6 +41,7 @@ export type OnboardingRenderProps = { * @see {@link ReviewStep} * @see {@link SaveDraftButton} * @see {@link PreOnboardingRequirements} + * @see {@link PreviewEmploymentAgreementStep} */ components: { SubmitButton: typeof OnboardingSubmit; @@ -53,10 +55,14 @@ export type OnboardingRenderProps = { ReviewStep: typeof ReviewStep; SaveDraftButton: typeof SaveDraftButton; PreOnboardingRequirements: typeof PreOnboardingRequirements; + PreviewEmploymentAgreementStep: typeof PreviewEmploymentAgreementStep; }; }; -type OnboardingFeatures = 'onboarding_reserves' | 'dynamic_steps'; +type OnboardingFeatures = + | 'onboarding_reserves' + | 'dynamic_steps' + | 'ea_preview'; /** * JSON schema version configuration for a specific country diff --git a/src/flows/Onboarding/utils.ts b/src/flows/Onboarding/utils.ts index 69b4057bf..0cdeeee45 100644 --- a/src/flows/Onboarding/utils.ts +++ b/src/flows/Onboarding/utils.ts @@ -7,12 +7,14 @@ export type StepKeys = | 'engagement_agreement_details' | 'contract_details' | 'benefits' + | 'employment_agreement_preview' | 'review'; type StepConfig = { includeSelectCountry?: boolean; includeEngagementAgreementDetails?: boolean; useDynamicSteps?: boolean; + useEAPreview?: boolean; }; export function buildSteps(config: StepConfig = {}) { @@ -46,6 +48,11 @@ export function buildSteps(config: StepConfig = {}) { label: 'Benefits', visible: true, }, + { + name: 'employment_agreement_preview', + label: 'Preview Employment Agreement', + visible: Boolean(config?.useEAPreview), + }, { name: 'review', label: 'Review', From 7c072ebde5c7460ca41b90b6e79bf3d26a86bac0 Mon Sep 17 00:00:00 2001 From: Gabriel Garcia Date: Fri, 5 Jun 2026 11:59:51 +0200 Subject: [PATCH 2/3] add step --- example/src/Onboarding.tsx | 8 +-- .../PreviewEmploymentAgreementStep.tsx | 67 +------------------ 2 files changed, 4 insertions(+), 71 deletions(-) diff --git a/example/src/Onboarding.tsx b/example/src/Onboarding.tsx index 7335afbae..cbcd7b453 100644 --- a/example/src/Onboarding.tsx +++ b/example/src/Onboarding.tsx @@ -273,13 +273,7 @@ const MultiStepForm = ({ components, onboardingBag }: MultiStepFormProps) => { case 'employment_agreement_preview': { return ( <> - console.log('payload', payload)} - onSuccess={(data: $TSFixMe) => console.log('data', data)} - onError={({ error, fieldErrors }) => - setErrors({ apiError: error.message, fieldErrors }) - } - /> +
void | Promise; - /** - * The function is called when the form submission is successful. - */ - onSuccess?: (data: $TSFixMe) => void | Promise; - /** - * The function is called when an error occurs during form submission. - */ - onError?: ({ - error, - rawError, - fieldErrors, - }: { - error: Error; - rawError: Record; - fieldErrors: NormalizedFieldError[]; - }) => void; -}; - -export function PreviewEmploymentAgreementStep({ - onSubmit, - onSuccess, - onError, -}: PreviewEmploymentAgreementStepProps) { +export function PreviewEmploymentAgreementStep() { const { onboardingBag } = useOnboardingContext(); - const handleSubmit = async (payload: $TSFixMe) => { - try { - const parsedValues = await onboardingBag.parseFormValues(payload); - await onSubmit?.(parsedValues as $TSFixMe); - const response = await onboardingBag.onSubmit(payload); - if (response?.data) { - await onSuccess?.(response?.data as SuccessResponse); - onboardingBag?.next(); - return; - } - - if (response?.error) { - const normalizedFieldErrors = normalizeFieldErrors( - response?.fieldErrors || [], - {}, - ); - - onError?.({ - error: response?.error, - rawError: response?.rawError, - fieldErrors: normalizedFieldErrors, - }); - } - } catch (error: unknown) { - onError?.({ - error: error as Error, - rawError: error as Record, - fieldErrors: [], - }); - } + const handleSubmit = async () => { + onboardingBag?.next(); }; // this step in theory shouldn't be a form, not sure yet... From 023406a22d80d3e81e2d79ab60814408c68565d6 Mon Sep 17 00:00:00 2001 From: Gabriel Garcia Date: Fri, 5 Jun 2026 12:02:07 +0200 Subject: [PATCH 3/3] fix tsfix --- example/src/Onboarding.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/example/src/Onboarding.tsx b/example/src/Onboarding.tsx index cbcd7b453..6484afa9b 100644 --- a/example/src/Onboarding.tsx +++ b/example/src/Onboarding.tsx @@ -13,7 +13,6 @@ import { EngagementAgreementDetailsFormPayload, ZendeskTriggerButton, zendeskArticles, - $TSFixMe, } from '@remoteoss/remote-flows'; import React, { useState } from 'react'; import { Card } from '@remoteoss/remote-flows/internals';