From de49f063bcd7d683bba82c10e3c07bdb61ed8206 Mon Sep 17 00:00:00 2001 From: Gabriel Garcia Date: Tue, 5 May 2026 12:14:24 +0200 Subject: [PATCH 1/5] add presentation property --- example/src/Onboarding.tsx | 41 +++++++++++++++++++++++- src/common/createHeadlessForm.tsx | 3 ++ src/flows/ContractorOnboarding/hooks.tsx | 17 ++++++++++ src/flows/Onboarding/hooks.tsx | 14 ++++++++ src/flows/types.ts | 1 + 5 files changed, 75 insertions(+), 1 deletion(-) diff --git a/example/src/Onboarding.tsx b/example/src/Onboarding.tsx index 7f3ea2eb..a96be1bb 100644 --- a/example/src/Onboarding.tsx +++ b/example/src/Onboarding.tsx @@ -15,12 +15,39 @@ 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 './css/main.css'; +const BenefitsAboutSection = ({ + title, + description, + finePrint, + url, +}: { + title: string; + description: string; + finePrint: string; + url: string; +}) => { + console.log('title', title); + console.log('description', description); + console.log('finePrint', finePrint); + console.log('url', url); + return ( + +

About

+

{description}

+

{finePrint}

+ + {url} + +
+ ); +}; export const InviteSection = ({ title, description, @@ -188,9 +215,19 @@ 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 +241,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 e5c47a02..c2b7770b 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 = {}, @@ -1269,6 +1285,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 4ac814e1..eb951dac 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 = {}, @@ -1081,6 +1094,7 @@ export const useOnboarding = ({ meta: { fields: fieldsMetaRef.current, fieldsets: stepFieldsWithFlatFieldsets[stepState.currentStep.name], + presentation: stepPresentation[stepState.currentStep.name], }, /** 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; }; }; From 950f00bb37e00ca4f8cb8b29c2aef8f79b8691d9 Mon Sep 17 00:00:00 2001 From: Gabriel Garcia Date: Tue, 5 May 2026 13:03:48 +0200 Subject: [PATCH 2/5] fix text --- example/src/Onboarding.tsx | 38 +++++++++++++++++++++----------------- src/internals.ts | 2 +- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/example/src/Onboarding.tsx b/example/src/Onboarding.tsx index a96be1bb..c3548091 100644 --- a/example/src/Onboarding.tsx +++ b/example/src/Onboarding.tsx @@ -20,31 +20,37 @@ 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 = ({ - title, description, - finePrint, url, }: { - title: string; description: string; - finePrint: string; url: string; }) => { - console.log('title', title); - console.log('description', description); - console.log('finePrint', finePrint); - console.log('url', url); + if (!description || !url) { + return null; + } return ( - -

About

-

{description}

-

{finePrint}

- - {url} - + +

About

+
+

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

); }; @@ -222,9 +228,7 @@ const MultiStepForm = ({ components, onboardingBag }: MultiStepFormProps) => { return (
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'; From 496792fc1c0781a2da11893319922f607f5e452b Mon Sep 17 00:00:00 2001 From: Gabriel Garcia Date: Tue, 5 May 2026 13:42:59 +0200 Subject: [PATCH 3/5] margin --- example/src/Onboarding.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/src/Onboarding.tsx b/example/src/Onboarding.tsx index c3548091..a6342a5a 100644 --- a/example/src/Onboarding.tsx +++ b/example/src/Onboarding.tsx @@ -34,7 +34,7 @@ const BenefitsAboutSection = ({ return null; } return ( - +

About

Date: Fri, 8 May 2026 17:31:53 +0200 Subject: [PATCH 4/5] add tests --- .../Onboarding/tests/OnboardingFlow.test.tsx | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) diff --git a/src/flows/Onboarding/tests/OnboardingFlow.test.tsx b/src/flows/Onboarding/tests/OnboardingFlow.test.tsx index a3198d56..4c041a90 100644 --- a/src/flows/Onboarding/tests/OnboardingFlow.test.tsx +++ b/src/flows/Onboarding/tests/OnboardingFlow.test.tsx @@ -2566,4 +2566,126 @@ 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')); + + // Navigate to benefits step + 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); + + // Verify presentation metadata structure + 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); + + // Verify basic_information step has no presentation + expect( + capturedBasicInfoPresentation === null || + capturedBasicInfoPresentation === undefined, + ).toBe(true); + }); }); From d0954273c9ead303b1575276a139be59d4fbfa3f Mon Sep 17 00:00:00 2001 From: Gabriel Garcia Date: Fri, 8 May 2026 18:25:03 +0200 Subject: [PATCH 5/5] fix test --- src/flows/Onboarding/tests/OnboardingFlow.test.tsx | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/flows/Onboarding/tests/OnboardingFlow.test.tsx b/src/flows/Onboarding/tests/OnboardingFlow.test.tsx index 4c041a90..dc4e0229 100644 --- a/src/flows/Onboarding/tests/OnboardingFlow.test.tsx +++ b/src/flows/Onboarding/tests/OnboardingFlow.test.tsx @@ -2611,7 +2611,6 @@ describe('OnboardingFlow', () => { await waitForElementToBeRemoved(() => screen.getByTestId('spinner')); - // Navigate to benefits step let nextButton = screen.getByText(/Next Step/i); nextButton.click(); await screen.findByText(/Step: Contract Details/i); @@ -2620,7 +2619,6 @@ describe('OnboardingFlow', () => { nextButton.click(); await screen.findByText(/Step: Benefits/i); - // Verify presentation metadata structure expect(capturedPresentation).toEqual({ benefits_service_fee: { amount: 15.0, @@ -2682,10 +2680,6 @@ describe('OnboardingFlow', () => { await waitForElementToBeRemoved(() => screen.getByTestId('spinner')); await screen.findByText(/Step: Basic Information/i); - // Verify basic_information step has no presentation - expect( - capturedBasicInfoPresentation === null || - capturedBasicInfoPresentation === undefined, - ).toBe(true); + expect(capturedBasicInfoPresentation).toBeUndefined(); }); });