Skip to content

Commit 801ca9e

Browse files
authored
feat(contractor-management) - add validations (#691)
* refactor(contractor-onboarding): reorder steps to prioritize pricing plan before contract details * feat(contractor-onboarding): add validation for pricing plan subscription step * feat(contractor-onboarding): initialize pricing plan form with prettified values * fix review tab * add comment * feat(contractor-onboarding): add meta and description fields to subscription options * components * feat(contractor-onboarding): add pricing plan card component with selection UI * revert steps * refactor(card): improve semantic HTML and add polymorphic component support * fix order * remove code * fix tests * test(contractor-onboarding): reorder steps to place pricing plan before contract details * refactor(contractor-onboarding): extract product identifiers to constants and add pricing plan-based logic * feat(contractor-onboarding): add backdate warning for non-standard pricing plans * feat(form): support statement and extra fields in form components * refactor(contractor-onboarding): move pricing plan default to hook and sort subscription options * revert file * revert tests * remove export * fix(contractor-onboarding) - fix selected product plan * test(contractor-onboarding): add test for pre-selecting Contractor Management Plus * improve code * format
1 parent 9eccc04 commit 801ca9e

5 files changed

Lines changed: 151 additions & 24 deletions

File tree

example/src/ContractorOnboarding.tsx

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -361,12 +361,6 @@ export const ContractorOnboardingWithProps = ({
361361
render={OnBoardingRender}
362362
employmentId={employmentId}
363363
externalId={externalId}
364-
initialValues={{
365-
pricing_plan: {
366-
subscription:
367-
'urn:remotecom:resource:product:contractor:standard:monthly',
368-
},
369-
}}
370364
options={{
371365
jsfModify: {
372366
contract_details: {

src/components/form/fields/DatePickerField.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export function DatePickerField({
2727
let minDateValue: Date;
2828
if (rest.meta?.mot && typeof rest.meta.mot === 'number') {
2929
minDateValue = getMinStartDate(rest.meta.mot);
30-
} else if (minDate) {
30+
} else if (typeof minDate === 'string') {
3131
minDateValue = new Date(`${minDate}T00:00:00`);
3232
}
3333

src/flows/ContractorOnboarding/hooks.tsx

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import { $TSFixMe, JSFFieldset, Meta } from '@/src/types/remoteFlows';
4646
import {
4747
contractorStandardProductIdentifier,
4848
contractorPlusProductIdentifier,
49+
corProductIdentifier,
4950
} from '@/src/flows/ContractorOnboarding/constants';
5051
import { ContractPreviewHeader } from '@/src/flows/ContractorOnboarding/components/ContractPreviewHeader';
5152
import { ContractPreviewStatement } from '@/src/flows/ContractorOnboarding/components/ContractPreviewStatement';
@@ -282,6 +283,22 @@ export const useContractorOnboarding = ({
282283
fieldValues?.service_duration?.provisional_start_date,
283284
]);
284285

286+
const selectedPricingPlan = useMemo(() => {
287+
if (!employment?.contractor_type) {
288+
return undefined;
289+
}
290+
const subscriptions = {
291+
standard: contractorStandardProductIdentifier,
292+
plus: contractorPlusProductIdentifier,
293+
cor: corProductIdentifier,
294+
};
295+
return (
296+
subscriptions[
297+
employment?.contractor_type as keyof typeof subscriptions
298+
] || contractorStandardProductIdentifier
299+
);
300+
}, [employment]);
301+
285302
const {
286303
data: contractorOnboardingDetailsForm,
287304
isLoading: isLoadingContractorOnboardingDetailsForm,
@@ -295,6 +312,8 @@ export const useContractorOnboarding = ({
295312
jsfModify: buildContractDetailsJsfModify(
296313
options?.jsfModify?.contract_details,
297314
descriptionProvisionalStartDate,
315+
selectedPricingPlan,
316+
fieldValues,
298317
),
299318
},
300319
});
@@ -464,20 +483,16 @@ export const useContractorOnboarding = ({
464483
}, [stepFields.contract_preview, onboardingInitialValues]);
465484

466485
const pricingPlanInitialValues = useMemo(() => {
486+
const preselectedPricingPlan = {
487+
subscription: selectedPricingPlan,
488+
};
467489
const initialValues = {
490+
...preselectedPricingPlan,
468491
...onboardingInitialValues,
469-
...employmentContractDetails,
470492
};
471493

472-
return getInitialValues(
473-
stepFields.pricing_plan,
474-
(initialValues?.pricing_plan ?? {}) as Record<string, unknown>,
475-
);
476-
}, [
477-
stepFields.pricing_plan,
478-
employmentContractDetails,
479-
onboardingInitialValues,
480-
]);
494+
return getInitialValues(stepFields.pricing_plan, initialValues);
495+
}, [stepFields.pricing_plan, onboardingInitialValues, selectedPricingPlan]);
481496

482497
const initialValues = useMemo(() => {
483498
return {

src/flows/ContractorOnboarding/tests/ContractorOnboarding.test.tsx

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1375,4 +1375,82 @@ describe('ContractorOnboardingFlow', () => {
13751375
expect(elements.length).toBeGreaterThan(0);
13761376
});
13771377
});
1378+
1379+
it('should pre-select Contractor Management Plus when employment has contractor_type plus', async () => {
1380+
const employmentId = generateUniqueEmploymentId();
1381+
1382+
server.use(
1383+
http.get(`*/v1/employments/${employmentId}`, () => {
1384+
return HttpResponse.json({
1385+
...mockContractorEmploymentResponse,
1386+
data: {
1387+
...mockContractorEmploymentResponse.data,
1388+
employment: {
1389+
...mockContractorEmploymentResponse.data.employment,
1390+
id: employmentId,
1391+
contractor_type: 'plus',
1392+
},
1393+
},
1394+
});
1395+
}),
1396+
);
1397+
1398+
mockRender.mockImplementation(
1399+
({
1400+
contractorOnboardingBag,
1401+
components,
1402+
}: ContractorOnboardingRenderProps) => {
1403+
const currentStepIndex =
1404+
contractorOnboardingBag.stepState.currentStep.index;
1405+
1406+
const steps: Record<number, string> = {
1407+
[0]: 'Basic Information',
1408+
[1]: 'Pricing Plan',
1409+
[2]: 'Contract Details',
1410+
[3]: 'Contract Preview',
1411+
[4]: 'Review',
1412+
};
1413+
1414+
return (
1415+
<>
1416+
<h1>Step: {steps[currentStepIndex]}</h1>
1417+
<MultiStepFormWithoutCountry
1418+
contractorOnboardingBag={contractorOnboardingBag}
1419+
components={components}
1420+
/>
1421+
</>
1422+
);
1423+
},
1424+
);
1425+
1426+
render(
1427+
<ContractorOnboardingFlow
1428+
employmentId={employmentId}
1429+
skipSteps={['select_country']}
1430+
{...defaultProps}
1431+
/>,
1432+
{ wrapper: TestProviders },
1433+
);
1434+
1435+
await screen.findByText(/Step: Basic Information/i);
1436+
await waitForElementToBeRemoved(() => screen.getByTestId('spinner'));
1437+
1438+
await waitFor(() => {
1439+
expect(screen.getByLabelText(/Full name/i)).toBeInTheDocument();
1440+
});
1441+
1442+
const nextButton = screen.getByText(/Next Step/i);
1443+
nextButton.click();
1444+
1445+
await screen.findByText(/Step: Pricing Plan/i);
1446+
await waitForElementToBeRemoved(() => screen.getByTestId('spinner'));
1447+
1448+
// Verify that Contractor Management Plus is pre-selected
1449+
await waitFor(() => {
1450+
const plusRadio = screen.getByRole('radio', {
1451+
name: /Contractor Management Plus/i,
1452+
});
1453+
expect(plusRadio).toBeChecked();
1454+
});
1455+
});
13781456
});

src/flows/ContractorOnboarding/utils.ts

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
import { format } from 'date-fns';
12
import { JSFModify } from '@/src/flows/types';
23
import { Step } from '@/src/flows/useStepState';
4+
import { FieldValues } from 'react-hook-form';
5+
import { createStatementProperty } from '@/src/components/form/jsf-utils/createFields';
36
import {
47
contractorStandardProductIdentifier,
58
contractorPlusProductIdentifier,
@@ -55,6 +58,29 @@ export const calculateProvisionalStartDateDescription = (
5558
return undefined;
5659
};
5760

61+
const isStandardPricingPlan = (pricingPlan: string | undefined) => {
62+
return pricingPlan === contractorStandardProductIdentifier;
63+
};
64+
65+
const showBackDateWarning = (
66+
isStandardPricingPlanSelected: boolean,
67+
provisionalStartDate: string | undefined,
68+
) => {
69+
const isStartDateBackdated =
70+
provisionalStartDate &&
71+
// Compare full days omitting time of the day
72+
provisionalStartDate < format(new Date(), 'yyyy-MM-dd');
73+
74+
if (!isStandardPricingPlanSelected && isStartDateBackdated) {
75+
return createStatementProperty({
76+
severity: 'warning',
77+
description:
78+
'Backdating the service start date is not supported in the selected Contractor Management plan.',
79+
});
80+
}
81+
82+
return undefined;
83+
};
5884
/**
5985
* Merges internal jsfModify modifications with user-provided options for contract_details step
6086
* This abstracts the logic of applying internal field modifications (like dynamic descriptions)
@@ -63,18 +89,32 @@ export const calculateProvisionalStartDateDescription = (
6389
export const buildContractDetailsJsfModify = (
6490
userJsfModify: JSFModify | undefined,
6591
provisionalStartDateDescription: string | undefined,
92+
selectedPricingPlan: string | undefined,
93+
fieldValues: FieldValues,
6694
): JSFModify => {
95+
const isStandardPricingPlanSelected =
96+
isStandardPricingPlan(selectedPricingPlan);
97+
const provisionalStartDate =
98+
fieldValues?.service_duration?.provisional_start_date;
99+
const statement = showBackDateWarning(
100+
isStandardPricingPlanSelected,
101+
provisionalStartDate,
102+
);
67103
return {
68104
...userJsfModify,
69105
fields: {
70106
...userJsfModify?.fields,
71-
...(provisionalStartDateDescription
72-
? {
73-
'service_duration.provisional_start_date': {
74-
description: provisionalStartDateDescription,
75-
},
76-
}
77-
: {}),
107+
...{
108+
'service_duration.provisional_start_date': {
109+
description: provisionalStartDateDescription,
110+
'x-jsf-presentation': {
111+
minDate: !isStandardPricingPlanSelected
112+
? format(new Date(), 'yyyy-MM-dd')
113+
: undefined,
114+
...statement,
115+
},
116+
},
117+
},
78118
},
79119
};
80120
};

0 commit comments

Comments
 (0)