diff --git a/src/flows/Onboarding/hooks.tsx b/src/flows/Onboarding/hooks.tsx index a588e364c..f963d112a 100644 --- a/src/flows/Onboarding/hooks.tsx +++ b/src/flows/Onboarding/hooks.tsx @@ -15,6 +15,8 @@ import { buildSteps, StepKeys, usesJsfV1ContractDetails, + getJobTitleEligibilityStatement, + JOB_TITLE_ELIGIBILITY_RESULT_FIELD, } from '@/src/flows/Onboarding/utils'; import { prettifyFormValues } from '@/src/lib/utils'; import { @@ -596,6 +598,9 @@ export const useOnboarding = ({ options?.features?.includes('daily_schedule'), ); + const roleDescriptionField = + options?.jsfModify?.contract_details?.fields?.role_description; + const contractDetailsCustomFields = useMemo( () => ({ fields: { @@ -644,6 +649,33 @@ export const useOnboarding = ({ }, }, }, + ...(isJobTitleEligibilityEnabled + ? { + role_description: { + ...(roleDescriptionField as $TSFixMe), + presentation: { + ...(roleDescriptionField as $TSFixMe)?.presentation, + calculateDynamicProperties: ( + values: FieldValues, + field: JSFField, + ) => { + const partnerFn = (roleDescriptionField as $TSFixMe) + ?.presentation?.calculateDynamicProperties; + const partnerProps = partnerFn + ? partnerFn(values, field) + : {}; + + const ourProps = getJobTitleEligibilityStatement( + values[JOB_TITLE_ELIGIBILITY_RESULT_FIELD], + employment?.country?.name, + ); + + return { ...partnerProps, ...ourProps }; + }, + }, + }, + } + : {}), ...(isDailyScheduleEnabled ? { daily_schedule: { @@ -684,6 +716,9 @@ export const useOnboarding = ({ useSplitSalaryDescription, isDailyScheduleEnabled, dailyScheduleField, + isJobTitleEligibilityEnabled, + roleDescriptionField, + employment?.country?.name, ], ); diff --git a/src/flows/Onboarding/tests/OnboardingFlowJobTitleEligibility.test.tsx b/src/flows/Onboarding/tests/OnboardingFlowJobTitleEligibility.test.tsx index d9266b45d..57b0810e9 100644 --- a/src/flows/Onboarding/tests/OnboardingFlowJobTitleEligibility.test.tsx +++ b/src/flows/Onboarding/tests/OnboardingFlowJobTitleEligibility.test.tsx @@ -637,6 +637,91 @@ describe.each(schemaPaths)( }); }); + it('shows an info statement when the check needs review', async () => { + mockCheckResponse([{ check_id: 'check-id', verdict: 'needs_review' }]); + + await renderContractDetailsStep(); + await fillRoleFields(); + + expect( + await screen.findByText( + `AI isn't sure if we can hire this role in ${countryName}`, + ), + ).toBeInTheDocument(); + expect( + screen.getByText( + "Complete the add employee process and we'll let you know as soon as we can if we can hire this role.", + ), + ).toBeInTheDocument(); + }); + + it('shows an error statement when the role is not eligible', async () => { + mockCheckResponse([{ check_id: 'check-id', verdict: 'not_eligible' }]); + + await renderContractDetailsStep(); + await fillRoleFields(); + + expect( + await screen.findByText( + `AI determined we can't hire this role in ${countryName}`, + ), + ).toBeInTheDocument(); + expect( + screen.getByText( + "Based on the job title and role description entered, we can't hire this role. Update the role details to check again.", + ), + ).toBeInTheDocument(); + }); + + it('does not show a statement when the role is eligible', async () => { + mockCheckResponse([{ check_id: 'check-id', verdict: 'eligible' }]); + + await renderContractDetailsStep(); + await fillRoleFields(); + await waitFor(() => + expect(checkResponsesSent).toHaveBeenCalledTimes(1), + ); + + expect(screen.queryByText(/^AI /)).not.toBeInTheDocument(); + }); + + it('switches the statement when a new check changes the verdict and removes it once eligible', async () => { + mockCheckResponse([ + { check_id: 'not-eligible-check-id', verdict: 'not_eligible' }, + { check_id: 'needs-review-check-id', verdict: 'needs_review' }, + { check_id: 'eligible-check-id', verdict: 'eligible' }, + ]); + + await renderContractDetailsStep(); + await fillRoleFields(); + await screen.findByText( + `AI determined we can't hire this role in ${countryName}`, + ); + + const user = userEvent.setup(); + const changeRoleDescription = async (text: string) => { + const roleDescription = screen.getByLabelText(/Role description/i); + await user.clear(roleDescription); + await user.type(roleDescription, text); + await user.tab(); + }; + + await changeRoleDescription('Frontend engineer responsibilities'); + await screen.findByText( + `AI isn't sure if we can hire this role in ${countryName}`, + ); + expect( + screen.queryByText( + `AI determined we can't hire this role in ${countryName}`, + ), + ).not.toBeInTheDocument(); + + await changeRoleDescription('Product engineer responsibilities'); + await waitFor(() => + expect(screen.queryByText(/^AI /)).not.toBeInTheDocument(), + ); + }); + it('hides the risk acknowledgement again once the role changes and the new check is eligible', async () => { mockCheckResponse([ { diff --git a/src/flows/Onboarding/utils.ts b/src/flows/Onboarding/utils.ts index 1e0098a6c..a3f866878 100644 --- a/src/flows/Onboarding/utils.ts +++ b/src/flows/Onboarding/utils.ts @@ -5,6 +5,7 @@ import { import { Employment, OnboardingFlowProps } from '@/src/flows/Onboarding/types'; import { Step } from '@/src/flows/useStepState'; import { JSFField, JSFFields } from '@/src/types/remoteFlows'; +import { createStatementProperty } from '@/src/components/form/jsf-utils/createFields'; export type StepKeys = | 'select_country' @@ -353,3 +354,30 @@ export const getJobTitleEligibilityValues = ( [JOB_TITLE_ELIGIBILITY_RESULT_FIELD]: JOB_TITLE_ELIGIBILITY_VERDICT_RESULTS[check.verdict] ?? null, }); + +export const getJobTitleEligibilityStatement = ( + result: unknown, + countryName?: string, +) => { + const inCountry = countryName ? `in ${countryName}` : 'in this country'; + + if (result === 'maybe') { + return createStatementProperty({ + title: `AI isn't sure if we can hire this role ${inCountry}`, + description: + "Complete the add employee process and we'll let you know as soon as we can if we can hire this role.", + severity: 'info', + }); + } + + if (result === 'no') { + return createStatementProperty({ + title: `AI determined we can't hire this role ${inCountry}`, + description: + "Based on the job title and role description entered, we can't hire this role. Update the role details to check again.", + severity: 'error', + }); + } + + return null; +};