Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 44 additions & 1 deletion example/src/Onboarding.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,45 @@
zendeskArticles,
} from '@remoteoss/remote-flows';
import React, { useState } from 'react';
import { Card } from '@remoteoss/remote-flows/internals';
import { ReviewOnboardingStep } from './ReviewOnboardingStep';
import { OnboardingAlertStatuses } from './OnboardingAlertStatuses';
import { RemoteFlows } from './RemoteFlows';
import { AlertError } from './AlertError';
import { sanitizeHtml } from '@remoteoss/remote-flows/internals';
import './css/main.css';

const BenefitsAboutSection = ({
description,
url,
}: {
description: string;
url: string;
}) => {
if (!description || !url) {
return null;
}
return (
<Card className='space-y-4 p-6 mb-4'>
<h2 className='text-xl font-semibold text-gray-900'>About</h2>
<div
className='prose prose-sm max-w-none text-xs text-gray-700 leading-relaxed space-y-4'
dangerouslySetInnerHTML={{ __html: sanitizeHtml(description) }}
/>
<p className='text-xs text-gray-700 leading-relaxed space-y-4'>
Want more details on benefits?{' '}
<a
href={url}
className='inline-block text-blue-600 hover:text-blue-700 hover:underline text-xs mt-2'
target='_blank'
rel='noopener noreferrer'
>
Check our guide
</a>
</p>
</Card>
);
};
export const InviteSection = ({
title,
description,
Expand Down Expand Up @@ -68,10 +101,10 @@
<>
<SelectCountryStep
onSubmit={(payload: SelectCountryFormPayload) =>
console.log('payload', payload)

Check warning on line 104 in example/src/Onboarding.tsx

View workflow job for this annotation

GitHub Actions / Lint and Format

eslint(no-console)

Unexpected console statement.
}
onSuccess={(response: SelectCountrySuccess) =>
console.log('response', response)

Check warning on line 107 in example/src/Onboarding.tsx

View workflow job for this annotation

GitHub Actions / Lint and Format

eslint(no-console)

Unexpected console statement.
}
onError={({
error,
Expand All @@ -96,10 +129,10 @@
/>
<BasicInformationStep
onSubmit={(payload: BasicInformationFormPayload) =>
console.log('payload', payload)

Check warning on line 132 in example/src/Onboarding.tsx

View workflow job for this annotation

GitHub Actions / Lint and Format

eslint(no-console)

Unexpected console statement.
}
onSuccess={(data: EmploymentCreationResponse) =>
console.log('data', data)

Check warning on line 135 in example/src/Onboarding.tsx

View workflow job for this annotation

GitHub Actions / Lint and Format

eslint(no-console)

Unexpected console statement.
}
onError={({ error, fieldErrors }) =>
setErrors({ apiError: error.message, fieldErrors })
Expand Down Expand Up @@ -127,7 +160,7 @@
<>
<EngagementAgreementDetailsStep
onSubmit={(payload: EngagementAgreementDetailsFormPayload) =>
console.log('payload', payload)

Check warning on line 163 in example/src/Onboarding.tsx

View workflow job for this annotation

GitHub Actions / Lint and Format

eslint(no-console)

Unexpected console statement.
}
onSuccess={(data: SuccessResponse) => console.log('data', data)}
onError={({ error, fieldErrors }) =>
Expand Down Expand Up @@ -188,9 +221,17 @@
</>
);

case 'benefits':
case 'benefits': {
// Example: Access schema-level presentation metadata
const benefitsPresentation = onboardingBag.meta.presentation;

return (
<div className='benefits-container'>
<BenefitsAboutSection
description={benefitsPresentation?.description as string}
url={benefitsPresentation?.url as string}
/>

<BenefitsStep
onSubmit={(payload: BenefitsFormPayload) =>
console.log('payload', payload)
Expand All @@ -204,6 +245,7 @@
}) => setErrors({ apiError: error.message, fieldErrors })}
onSuccess={(data: SuccessResponse) => console.log('data', data)}
/>

<AlertError errors={errors} />
<div className='buttons-container'>
<BackButton
Expand All @@ -221,6 +263,7 @@
</div>
</div>
);
}
case 'review':
return (
<ReviewOnboardingStep
Expand Down
3 changes: 3 additions & 0 deletions src/common/createHeadlessForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ export const createHeadlessForm = (
return {
meta: {
'x-jsf-fieldsets': jsfSchema['x-jsf-fieldsets'] as JSFFieldset,
'x-jsf-presentation': jsfSchema['x-jsf-presentation'] as
| Record<string, unknown>
| undefined,
},
...baseCreateHeadlessForm(jsfSchema, {
initialValues,
Expand Down
17 changes: 17 additions & 0 deletions src/flows/ContractorOnboarding/hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,22 @@ export const useContractorOnboarding = ({
review: null,
};

const stepPresentation: Record<
StepKeys,
Record<string, unknown> | null | undefined
> = {
select_country: selectCountryForm?.meta?.['x-jsf-presentation'],
basic_information: basicInformationForm?.meta?.['x-jsf-presentation'],
pricing_plan:
selectContractorSubscriptionForm?.meta?.['x-jsf-presentation'],
eligibility_questionnaire:
eligibilityQuestionnaireForm?.meta?.['x-jsf-presentation'],
contract_details:
contractorOnboardingDetailsForm?.meta?.['x-jsf-presentation'],
contract_preview: signatureSchemaForm?.meta?.['x-jsf-presentation'],
review: null,
};

const {
country,
basic_information: employmentBasicInformation = {},
Expand Down Expand Up @@ -1276,6 +1292,7 @@ export const useContractorOnboarding = ({
meta: {
fields: fieldsMetaRef.current,
fieldsets: stepFieldsWithFlatFieldsets[stepState.currentStep.name],
presentation: stepPresentation[stepState.currentStep.name],
},

/**
Expand Down
14 changes: 14 additions & 0 deletions src/flows/Onboarding/hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,19 @@ export const useOnboarding = ({
review: null,
};

const stepPresentation: Record<
StepKeys,
Record<string, unknown> | null | undefined
> = {
select_country: selectCountryForm?.meta?.['x-jsf-presentation'],
basic_information: basicInformationForm?.meta?.['x-jsf-presentation'],
engagement_agreement_details:
engagementAgreementDetailsSchema?.meta?.['x-jsf-presentation'],
contract_details: contractDetailsForm?.meta?.['x-jsf-presentation'],
benefits: benefitOffersSchema?.meta?.['x-jsf-presentation'],
review: null,
};

const {
country,
basic_information: employmentBasicInformation = {},
Expand Down Expand Up @@ -1086,6 +1099,7 @@ export const useOnboarding = ({
meta: {
fields: fieldsMetaRef.current,
fieldsets: stepFieldsWithFlatFieldsets[stepState.currentStep.name],
presentation: stepPresentation[stepState.currentStep.name],
},

/**
Expand Down
116 changes: 116 additions & 0 deletions src/flows/Onboarding/tests/OnboardingFlow.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2566,4 +2566,120 @@ describe('OnboardingFlow', () => {
).toBeInTheDocument();
});
});

it('should include description, fine_print, and benefits_service_fee in benefits presentation', async () => {
let capturedPresentation: Record<string, unknown> | null | undefined = null;

mockRender.mockImplementation(
({ onboardingBag, components }: OnboardingRenderProps) => {
const currentStepIndex = onboardingBag.stepState.currentStep.index;
const steps: Record<number, string> = {
[0]: 'Basic Information',
[1]: 'Contract Details',
[2]: 'Benefits',
[3]: 'Review',
};

if (onboardingBag.stepState.currentStep.name === 'benefits') {
capturedPresentation = onboardingBag.meta.presentation;
}

if (onboardingBag.isLoading) {
return <div data-testid='spinner'>Loading...</div>;
}

return (
<>
<h1>Step: {steps[currentStepIndex]}</h1>
<MultiStepFormWithoutCountry
onboardingBag={onboardingBag}
components={components}
/>
</>
);
},
);

render(
<OnboardingFlow
employmentId={generateUniqueEmploymentId()}
skipSteps={['select_country']}
{...defaultProps}
/>,
{ wrapper: TestProviders },
);

await waitForElementToBeRemoved(() => screen.getByTestId('spinner'));

let nextButton = screen.getByText(/Next Step/i);
nextButton.click();
await screen.findByText(/Step: Contract Details/i);

nextButton = screen.getByText(/Next Step/i);
nextButton.click();
await screen.findByText(/Step: Benefits/i);

expect(capturedPresentation).toEqual({
benefits_service_fee: {
amount: 15.0,
currency: 'USD',
},
description:
'We offer our employees supplemental benefits - Meal and Health Insurance (In partnership with Advance Care/Tranquilidade and Coverflex)',
fine_print:
'New: Health Insurance is now optional for new hires in Portugal.\r\nPlease note that all local payroll deductions for required coverages are included in the TCE.\r\nAny pricing changes will be communicated in advance of updated billing.',
url: 'https://remote.com/benefits-guide/portugal',
});
});

it('should have null or undefined presentation for basic_information step', async () => {
let capturedBasicInfoPresentation:
| Record<string, unknown>
| null
| undefined = undefined;

mockRender.mockImplementation(
({ onboardingBag, components }: OnboardingRenderProps) => {
const currentStepIndex = onboardingBag.stepState.currentStep.index;
const steps: Record<number, string> = {
[0]: 'Basic Information',
[1]: 'Contract Details',
[2]: 'Benefits',
[3]: 'Review',
};

if (onboardingBag.stepState.currentStep.name === 'basic_information') {
capturedBasicInfoPresentation = onboardingBag.meta.presentation;
}

if (onboardingBag.isLoading) {
return <div data-testid='spinner'>Loading...</div>;
}

return (
<>
<h1>Step: {steps[currentStepIndex]}</h1>
<MultiStepFormWithoutCountry
onboardingBag={onboardingBag}
components={components}
/>
</>
);
},
);

render(
<OnboardingFlow
employmentId={generateUniqueEmploymentId()}
skipSteps={['select_country']}
{...defaultProps}
/>,
{ wrapper: TestProviders },
);

await waitForElementToBeRemoved(() => screen.getByTestId('spinner'));
await screen.findByText(/Step: Basic Information/i);

expect(capturedBasicInfoPresentation).toBeUndefined();
});
});
1 change: 1 addition & 0 deletions src/flows/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,5 +107,6 @@ type FormResult = FormResultNext | FormResultLegacy;
export type JSONSchemaFormResultWithFieldsets = FormResult & {
meta: {
'x-jsf-fieldsets': JSFFieldset;
'x-jsf-presentation'?: Record<string, unknown>;
};
};
2 changes: 1 addition & 1 deletion src/internals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
*/

// Internal utilities
export { cn } from './lib/utils';
export { cn, sanitizeHtml } from './lib/utils';

// UI Components for internal use
export { Button, buttonVariants } from './components/ui/button';
Expand Down
Loading