diff --git a/example/src/Onboarding.tsx b/example/src/Onboarding.tsx index 7f3ea2eb..a6342a5a 100644 --- a/example/src/Onboarding.tsx +++ b/example/src/Onboarding.tsx @@ -15,12 +15,45 @@ import { 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 ( + +

About

+
+

+ Want more details on benefits?{' '} + + Check our guide + +

+ + ); +}; export const InviteSection = ({ title, description, @@ -188,9 +221,17 @@ const MultiStepForm = ({ components, onboardingBag }: MultiStepFormProps) => { ); - case 'benefits': + case 'benefits': { + // Example: Access schema-level presentation metadata + const benefitsPresentation = onboardingBag.meta.presentation; + return (
+ + console.log('payload', payload) @@ -204,6 +245,7 @@ const MultiStepForm = ({ components, onboardingBag }: MultiStepFormProps) => { }) => setErrors({ apiError: error.message, fieldErrors })} onSuccess={(data: SuccessResponse) => console.log('data', data)} /> +
{
); + } case 'review': return ( + | undefined, }, ...baseCreateHeadlessForm(jsfSchema, { initialValues, diff --git a/src/flows/ContractorOnboarding/hooks.tsx b/src/flows/ContractorOnboarding/hooks.tsx index d52e0abd..1b2624de 100644 --- a/src/flows/ContractorOnboarding/hooks.tsx +++ b/src/flows/ContractorOnboarding/hooks.tsx @@ -639,6 +639,22 @@ export const useContractorOnboarding = ({ review: null, }; + const stepPresentation: Record< + StepKeys, + Record | 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 = {}, @@ -1276,6 +1292,7 @@ export const useContractorOnboarding = ({ meta: { fields: fieldsMetaRef.current, fieldsets: stepFieldsWithFlatFieldsets[stepState.currentStep.name], + presentation: stepPresentation[stepState.currentStep.name], }, /** diff --git a/src/flows/Onboarding/hooks.tsx b/src/flows/Onboarding/hooks.tsx index f3799f05..b04a4255 100644 --- a/src/flows/Onboarding/hooks.tsx +++ b/src/flows/Onboarding/hooks.tsx @@ -570,6 +570,19 @@ export const useOnboarding = ({ review: null, }; + const stepPresentation: Record< + StepKeys, + Record | 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 = {}, @@ -1086,6 +1099,7 @@ export const useOnboarding = ({ meta: { fields: fieldsMetaRef.current, fieldsets: stepFieldsWithFlatFieldsets[stepState.currentStep.name], + presentation: stepPresentation[stepState.currentStep.name], }, /** diff --git a/src/flows/Onboarding/tests/OnboardingFlow.test.tsx b/src/flows/Onboarding/tests/OnboardingFlow.test.tsx index a3198d56..dc4e0229 100644 --- a/src/flows/Onboarding/tests/OnboardingFlow.test.tsx +++ b/src/flows/Onboarding/tests/OnboardingFlow.test.tsx @@ -2566,4 +2566,120 @@ describe('OnboardingFlow', () => { ).toBeInTheDocument(); }); }); + + it('should include description, fine_print, and benefits_service_fee in benefits presentation', async () => { + let capturedPresentation: Record | null | undefined = null; + + mockRender.mockImplementation( + ({ onboardingBag, components }: OnboardingRenderProps) => { + const currentStepIndex = onboardingBag.stepState.currentStep.index; + const steps: Record = { + [0]: 'Basic Information', + [1]: 'Contract Details', + [2]: 'Benefits', + [3]: 'Review', + }; + + if (onboardingBag.stepState.currentStep.name === 'benefits') { + capturedPresentation = onboardingBag.meta.presentation; + } + + if (onboardingBag.isLoading) { + return
Loading...
; + } + + return ( + <> +

Step: {steps[currentStepIndex]}

+ + + ); + }, + ); + + render( + , + { 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 + | null + | undefined = undefined; + + mockRender.mockImplementation( + ({ onboardingBag, components }: OnboardingRenderProps) => { + const currentStepIndex = onboardingBag.stepState.currentStep.index; + const steps: Record = { + [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
Loading...
; + } + + return ( + <> +

Step: {steps[currentStepIndex]}

+ + + ); + }, + ); + + render( + , + { wrapper: TestProviders }, + ); + + await waitForElementToBeRemoved(() => screen.getByTestId('spinner')); + await screen.findByText(/Step: Basic Information/i); + + expect(capturedBasicInfoPresentation).toBeUndefined(); + }); }); diff --git a/src/flows/types.ts b/src/flows/types.ts index 4aaccada..e36e1b3f 100644 --- a/src/flows/types.ts +++ b/src/flows/types.ts @@ -107,5 +107,6 @@ type FormResult = FormResultNext | FormResultLegacy; export type JSONSchemaFormResultWithFieldsets = FormResult & { meta: { 'x-jsf-fieldsets': JSFFieldset; + 'x-jsf-presentation'?: Record; }; }; diff --git a/src/internals.ts b/src/internals.ts index 7cab3ed7..f7695a95 100644 --- a/src/internals.ts +++ b/src/internals.ts @@ -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';