Skip to content
Merged
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
35 changes: 35 additions & 0 deletions src/flows/Onboarding/hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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: {
Expand Down Expand Up @@ -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: {
Expand Down Expand Up @@ -684,6 +716,9 @@ export const useOnboarding = ({
useSplitSalaryDescription,
isDailyScheduleEnabled,
dailyScheduleField,
isJobTitleEligibilityEnabled,
roleDescriptionField,
employment?.country?.name,
],
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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([
{
Expand Down
28 changes: 28 additions & 0 deletions src/flows/Onboarding/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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;
};
Loading