Skip to content

Commit 2dfb2d9

Browse files
authored
feat(contractor-onboarding) - use pricing plan to render the EOR card (#763)
* navigate to pricing plan when eligibility is blocked * add pricing plan eor * fix logic && tests * fix tests * fix types * handle invalid product plan
1 parent d67f5ad commit 2dfb2d9

10 files changed

Lines changed: 216 additions & 528 deletions

File tree

example/src/ContractorOnboarding.tsx

Lines changed: 70 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,13 @@ import {
1717
PricingPlanDataProps,
1818
corProductIdentifier,
1919
eorProductIdentifier,
20-
useDiscardEmploymentMutation,
20+
$TSFixMe,
2121
} from '@remoteoss/remote-flows';
2222
import {
2323
Card,
2424
Tabs,
2525
TabsTrigger,
2626
TabsList,
27-
cn,
2827
} from '@remoteoss/remote-flows/internals';
2928
import Flag from 'react-flagpack';
3029
import React, { useState } from 'react';
@@ -36,32 +35,75 @@ import { EngagingContractorsModal } from './components/PricingPlanModals';
3635
import './css/main.css';
3736
import './css/contractor-onboarding.css';
3837

38+
const groupOptionsBySeparator = (options: $TSFixMe[]) => {
39+
const groups: $TSFixMe[][] = [];
40+
let currentGroup: $TSFixMe[] = [];
41+
42+
options?.forEach((option) => {
43+
if (option.meta?.groupSeparator?.show && currentGroup.length > 0) {
44+
groups.push(currentGroup);
45+
currentGroup = [option];
46+
} else {
47+
currentGroup.push(option);
48+
}
49+
});
50+
51+
if (currentGroup.length > 0) {
52+
groups.push(currentGroup);
53+
}
54+
55+
return groups;
56+
};
57+
3958
const PricingPlanCards = ({
4059
field,
4160
fieldData,
4261
fieldState,
4362
showPrice = true,
4463
}: PricingPlanComponentProps & { showPrice?: boolean }) => {
4564
const hasError = !!fieldState.error;
46-
const items = fieldData.options?.length;
65+
66+
// Group options by separator
67+
const groups = groupOptionsBySeparator(fieldData.options);
4768

4869
return (
49-
<div className={cn('grid gap-2', `grid-cols-${items ?? 3}`)}>
50-
{fieldData.options?.map((option) => (
51-
<PricingPlanCard
52-
key={option.value}
53-
title={option.label}
54-
description={option.description}
55-
features={option.meta?.features as string[]}
56-
price={option.meta?.price}
57-
value={option.value}
58-
selected={field.value === option.value}
59-
disabled={option.disabled}
60-
showPrice={showPrice}
61-
onSelect={(value: string) => {
62-
field.onChange(value);
63-
}}
64-
/>
70+
<div className='flex flex-col gap-6'>
71+
{groups.map((group, groupIndex) => (
72+
<React.Fragment key={groupIndex}>
73+
{/* Render separator if first option in group has one */}
74+
{group[0]?.meta?.groupSeparator?.show && (
75+
<div className='mt-4 mb-2'>
76+
<h2 className='text-xl font-bold text-[#22863a]'>
77+
Other available hiring plans
78+
</h2>
79+
<p className='text-sm text-[#71717A] mt-1'>
80+
Based on the responses in your questionnaire, this individual
81+
may also be eligible for our Employer of Record plan. For any
82+
questions, please contact help@remote.com
83+
</p>
84+
</div>
85+
)}
86+
87+
{/* Render all cards in this group in a single grid */}
88+
<div className='grid gap-2 grid-cols-3'>
89+
{group.map((option) => (
90+
<PricingPlanCard
91+
key={option.value}
92+
title={option.label}
93+
description={option.description}
94+
features={option.meta?.features as string[]}
95+
price={option.meta?.price}
96+
value={option.value}
97+
selected={field.value === option.value}
98+
disabled={option.disabled}
99+
showPrice={showPrice}
100+
onSelect={(value: string) => {
101+
field.onChange(value);
102+
}}
103+
/>
104+
))}
105+
</div>
106+
</React.Fragment>
65107
))}
66108
{hasError && <p className='error-message'>{fieldState.error?.message}</p>}
67109
</div>
@@ -103,7 +145,6 @@ const MultiStepForm = ({
103145
SelectCountryStep,
104146
PricingPlanStep,
105147
EligibilityQuestionnaireStep,
106-
ChooseAlternativePlanStep,
107148
ContractDetailsStep,
108149
ContractPreviewStep,
109150
ContractReviewButton,
@@ -115,8 +156,6 @@ const MultiStepForm = ({
115156
apiError: '',
116157
fieldErrors: [],
117158
});
118-
const { mutateAsync: discardEmployment } = useDiscardEmploymentMutation();
119-
120159
switch (contractorOnboardingBag.stepState.currentStep.name) {
121160
case 'select_country':
122161
return (
@@ -263,9 +302,12 @@ const MultiStepForm = ({
263302
);
264303
},
265304
}}
266-
onSubmit={(payload: PricingPlanFormPayload) =>
267-
console.log('payload', payload)
268-
}
305+
onSubmit={(payload: PricingPlanFormPayload) => {
306+
console.log('payload', payload);
307+
if (payload.subscription === eorProductIdentifier) {
308+
window.location.href = '?demo=onboarding-basic';
309+
}
310+
}}
269311
onSuccess={(response: PricingPlanResponse) =>
270312
console.log('response', response)
271313
}
@@ -310,9 +352,9 @@ const MultiStepForm = ({
310352
<EligibilityQuestionnaireStep
311353
onSubmit={(payload) => console.log('payload', payload)}
312354
onSuccess={(response) => console.log('response', response)}
313-
onError={({ error, fieldErrors }) =>
314-
setErrors({ apiError: error.message, fieldErrors })
315-
}
355+
onError={({ error, fieldErrors }) => {
356+
setErrors({ apiError: error.message, fieldErrors });
357+
}}
316358
/>
317359
<AlertError errors={errors} />
318360
<div className='contractor-onboarding-buttons-container'>
@@ -332,70 +374,6 @@ const MultiStepForm = ({
332374
</div>
333375
);
334376

335-
case 'choose_alternative_plan':
336-
return (
337-
<div className='pricing-plan-form-layout'>
338-
<div className='flex flex-col gap-2 text-center mb-6'>
339-
<h1 className='text-2xl font-bold text-[#000000]'>
340-
Choose Your Plan
341-
</h1>
342-
<p className='text-sm text-[#71717A]'>
343-
This individual is not eligible for Contractor of Record. The
344-
engagement terms imply an employer-employee relationship. We
345-
suggest the plans below and recommend a legal review before
346-
deciding. For any questions, contact help@remote.com.
347-
</p>
348-
</div>
349-
<div className='mb-6'>
350-
<ChooseAlternativePlanStep
351-
components={{
352-
radio: ({ field, fieldData, fieldState }) => {
353-
return (
354-
<PricingPlanCards
355-
fieldData={fieldData as PricingPlanDataProps}
356-
fieldState={fieldState}
357-
field={field}
358-
/>
359-
);
360-
},
361-
}}
362-
onSubmit={(payload) => {
363-
console.log(
364-
'submitted choose alternative plan payload',
365-
payload,
366-
);
367-
}}
368-
onSuccess={async (response) => {
369-
if (response.subscription === eorProductIdentifier) {
370-
try {
371-
await discardEmployment({
372-
employmentId:
373-
contractorOnboardingBag.employmentId as string,
374-
});
375-
} catch (error) {
376-
console.error('error discarding employment', error);
377-
} finally {
378-
window.location.href = '?demo=onboarding-basic';
379-
}
380-
}
381-
}}
382-
onError={({ error, fieldErrors }) =>
383-
setErrors({ apiError: error.message, fieldErrors })
384-
}
385-
/>
386-
</div>
387-
<AlertError errors={errors} />
388-
<div className='contractor-onboarding-buttons-container'>
389-
<SubmitButton
390-
className='submit-button'
391-
onClick={() => setErrors({ apiError: '', fieldErrors: [] })}
392-
>
393-
Continue
394-
</SubmitButton>
395-
</div>
396-
</div>
397-
);
398-
399377
case 'review': {
400378
return (
401379
<div className='contractor-onboarding-form-layout'>

src/flows/ContractorOnboarding/ContractorOnboarding.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ 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 { ChooseAlternativePlanStep } from '@/src/flows/ContractorOnboarding/components/ChooseAlternativePlanStep';
1716

1817
export const ContractorOnboardingFlow = ({
1918
render,
@@ -53,7 +52,6 @@ export const ContractorOnboardingFlow = ({
5352
SubmitButton: OnboardingSubmit,
5453
PricingPlanStep: PricingPlanStep,
5554
EligibilityQuestionnaireStep: EligibilityQuestionnaireStep,
56-
ChooseAlternativePlanStep: ChooseAlternativePlanStep,
5755
ContractDetailsStep: ContractDetailsStep,
5856
ContractPreviewStep: ContractPreviewStep,
5957
OnboardingInvite: OnboardingInvite,

0 commit comments

Comments
 (0)