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
2 changes: 1 addition & 1 deletion .cursor/rules/auto-format.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ globs: src/**/*.{ts,tsx,js,jsx}
alwaysApply: false
---

# Auto-Format After Agentic Coding
# 🚨 CRITICAL: Auto-Format After Agentic Coding

**Always run these commands after making code changes:**

Expand Down
16 changes: 7 additions & 9 deletions example/src/ReviewContractorOnboardingStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,15 +112,13 @@ export const ReviewContractorOnboardingStep = ({
>
Edit Contract Details
</button>
<h2 className='title'>Contract Preview</h2>
<ReviewMeta meta={onboardingBag.meta.fields.contract_preview} />

<button
className='back-button'
onClick={() => onboardingBag.goTo('contract_preview')}
>
Edit Contract Preview
</button>
{onboardingBag.stepState.values?.pricing_plan?.subscription !==
corProductIdentifier && (
<>
<h2 className='title'>Contract Preview</h2>
<ReviewMeta meta={onboardingBag.meta.fields.contract_preview} />
</>
)}

{invitedStatus === 'not_invited' &&
typeof onboardingBag.employment?.basic_information?.name ===
Expand Down
4 changes: 4 additions & 0 deletions src/common/api/fixtures/eligibility-questionnaire.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { mockBaseResponse } from '@/src/common/api/fixtures/base';

export const mockEligibilityQuestionnaireSchema = {
data: {
schema: {
Expand Down Expand Up @@ -49,6 +51,8 @@ export const mockEligibilityQuestionnaireSchema = {
},
};

export const mockEligibilityQuestionnaireResponse = mockBaseResponse;

export const mockBlockedEligibilityQuestionnaireResponse = {
data: {
is_blocking: true,
Expand Down
21 changes: 19 additions & 2 deletions src/flows/ContractorOnboarding/hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ export const useContractorOnboarding = ({
const [includeEligibilityQuestionnaire, setIncludeEligibilityQuestionnaire] =
useState<boolean>(false);

const [includeContractPreview, setIncludeContractPreview] =
useState<boolean>(true);

const [includeChooseAlternativePlan, setIncludeChooseAlternativePlan] =
useState<boolean>(false);

Expand All @@ -139,8 +142,9 @@ export const useContractorOnboarding = ({
buildSteps({
includeSelectCountry: !skipSteps?.includes('select_country'),
includeEligibilityQuestionnaire: includeEligibilityQuestionnaire,
includeContractPreview: includeContractPreview,
}),
[includeEligibilityQuestionnaire, skipSteps],
[includeEligibilityQuestionnaire, includeContractPreview, skipSteps],
);

const {
Expand Down Expand Up @@ -300,6 +304,11 @@ export const useContractorOnboarding = ({
}
}, [hasEligibilityQuestionnaireSubmitted, selectedProduct]);

useEffect(() => {
const isCor = employment?.contractor_type === 'cor';
setIncludeContractPreview(!isCor);
}, [employment?.contractor_type]);

const eligibilityAnswers = useMemo(() => {
return contractorSubscriptions?.find(
(subscription) => subscription.product.short_name === 'COR',
Expand Down Expand Up @@ -768,13 +777,17 @@ export const useContractorOnboarding = ({
isLoadingChooseAlternativePlan;

const isNavigatingToReview = useMemo(() => {
const isCor = employment?.contractor_type === 'cor';
const hasContractPreviewFields =
isCor || stepFields.contract_preview.length > 0;

return Boolean(
shouldHandleReadOnlyEmployment &&
!initialLoading &&
Boolean(internalContractDocumentId) &&
stepFields.basic_information.length > 0 &&
stepFields.contract_details.length > 0 &&
stepFields.contract_preview.length > 0,
hasContractPreviewFields,
);
}, [
shouldHandleReadOnlyEmployment,
Expand All @@ -783,6 +796,7 @@ export const useContractorOnboarding = ({
stepFields.basic_information.length,
stepFields.contract_details.length,
stepFields.contract_preview.length,
employment?.contractor_type,
]);

useEffect(() => {
Expand Down Expand Up @@ -1009,6 +1023,9 @@ export const useContractorOnboarding = ({
throw createStructuredError('Contract document ID not found');
}
setInternalContractDocumentId(contractDocumentId);

await refetchEmployment();

return response;
}

Expand Down
128 changes: 122 additions & 6 deletions src/flows/ContractorOnboarding/tests/ContractorOnboarding.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1785,12 +1785,6 @@ describe('ContractorOnboardingFlow', () => {
return HttpResponse.json(mockBaseResponse);
},
),
http.post(
'*/v1/contractors/employments/*/contractor-cor-subscription',
async () => {
return HttpResponse.json({ data: { status: 'ok' } });
},
),
);

mockRender.mockImplementation(
Expand Down Expand Up @@ -2482,4 +2476,126 @@ describe('ContractorOnboardingFlow', () => {
expect(eorOption).not.toBeInTheDocument();
});
});

describe('COR Contract Preview Skip', () => {
it('should skip contract_preview step when COR is selected', async () => {
const contractDocumentSpy = vi.fn();

server.use(
http.post(
'*/v1/contractors/employments/*/contract-documents',
async ({ request }) => {
const requestBody = await request.json();
contractDocumentSpy(requestBody);
return HttpResponse.json(mockContractDocumentCreatedResponse);
},
),
http.get('*/v1/employments/*', () => {
return HttpResponse.json({
...mockContractorEmploymentResponse,
data: {
...mockContractorEmploymentResponse.data,
employment: {
...mockContractorEmploymentResponse.data.employment,
contractor_type: 'cor',
},
},
});
}),
);

mockRender.mockImplementation(
createMockRenderImplementation(MultiStepFormWithoutCountry),
);

render(
<ContractorOnboardingFlow
skipSteps={['select_country']}
countryCode='PRT'
{...defaultProps}
/>,
{ wrapper: TestProviders },
);

await screen.findByText(/Step: Basic Information/i);
await fillBasicInformation();

let nextButton = screen.getByText(/Next Step/i);
nextButton.click();

await screen.findByText(/Step: Pricing Plan/i);
await fillContractorSubscription('Contractor of Record');

nextButton = screen.getByText(/Next Step/i);
nextButton.click();

await screen.findByText(/Step: Eligibility Questionnaire/i);
await fillEligibilityQuestionnaire();

nextButton = screen.getByText(/Next Step/i);
nextButton.click();

await screen.findByText(/Step: Contract Details/i);
await fillContractDetails();

nextButton = screen.getByText(/Next Step/i);
nextButton.click();

await screen.findByText(/Step: Review/i);
});

it('should navigate to review for read-only COR employment without contract_preview step', async () => {
const employmentId = generateUniqueEmploymentId();

server.use(
http.get(`*/v1/employments/${employmentId}`, () => {
return HttpResponse.json({
...mockContractorEmploymentResponse,
data: {
...mockContractorEmploymentResponse.data,
employment: {
...mockContractorEmploymentResponse.data.employment,
id: employmentId,
status: 'invited',
contractor_type: 'cor',
contract_details: {
service_duration: {
provisional_start_date: '2025-11-26',
},
payment_terms: {
compensation_currency_code: 'USD',
rate: 5000,
rate_unit: 'monthly',
},
},
},
},
});
}),
);

mockRender.mockImplementation(
createMockRenderImplementation(MultiStepFormWithoutCountry),
);

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

await screen.findByText(/Step: Review/i);

expect(
screen.queryByText(/Step: Contract Preview/i),
).not.toBeInTheDocument();

expect(screen.getByText('name: Gabriel')).toBeInTheDocument();
});
});
});
3 changes: 2 additions & 1 deletion src/flows/ContractorOnboarding/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export type StepKeys =
type StepConfig = {
includeSelectCountry?: boolean;
includeEligibilityQuestionnaire?: boolean;
includeContractPreview?: boolean;
};

export function buildSteps(config: StepConfig = {}) {
Expand Down Expand Up @@ -61,7 +62,7 @@ export function buildSteps(config: StepConfig = {}) {
{
name: 'contract_preview',
label: 'Contract Preview',
visible: true,
visible: Boolean(config?.includeContractPreview),
},
{
name: 'review',
Expand Down
22 changes: 21 additions & 1 deletion src/tests/handlers.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import { http, HttpResponse } from 'msw';
import { identityMock } from '@/src/common/api/fixtures/identity';
import { legalEntitiesMock } from '@/src/common/api/fixtures/legal-entities';
import { mockEligibilityQuestionnaireSchema } from '@/src/common/api/fixtures/eligibility-questionnaire';
import {
mockEligibilityQuestionnaireResponse,
mockEligibilityQuestionnaireSchema,
} from '@/src/common/api/fixtures/eligibility-questionnaire';
import {
mockContractorSubscriptionResponse,
mockManageSubscriptionResponse,
} from '@/src/common/api/fixtures/contractors-subscriptions';
import { countriesMock } from '@/src/common/api/fixtures/countries';
import { mockContractorCurrenciesResponse } from '@/src/common/api/fixtures/contractors';
import { mockCompanyPricingPlansResponse } from '@/src/common/api/fixtures/companies';
import { mockBaseResponse } from '@/src/common/api/fixtures/base';

const identityHandler = http.get('*/v1/identity/current', () => {
return HttpResponse.json(identityMock);
Expand All @@ -25,6 +29,13 @@ const eligibilityQuestionnaireHandler = http.get(
},
);

const eligibilityQuestionnaireResponseHandler = http.post(
'*/v1/contractors/eligibility-questionnaire',
async () => {
return HttpResponse.json(mockEligibilityQuestionnaireResponse);
},
);

const contractorSubscriptionHandler = http.get(
'*/v1/contractors/employments/*/contractor-subscriptions',
async () => {
Expand All @@ -39,6 +50,13 @@ const manageSubscriptionHandler = http.post(
},
);

const contractorCORSubscriptionHandler = http.post(
'*/v1/contractors/employments/*/contractor-cor-subscription',
async () => {
return HttpResponse.json(mockBaseResponse);
},
);

export const countriesHandler = http.get('*/v1/countries', () => {
return HttpResponse.json(countriesMock);
});
Expand All @@ -61,7 +79,9 @@ export const defaultHandlers = [
identityHandler,
legalEntitiesHandler,
eligibilityQuestionnaireHandler,
eligibilityQuestionnaireResponseHandler,
contractorSubscriptionHandler,
contractorCORSubscriptionHandler,
manageSubscriptionHandler,
countriesHandler,
contractorCurrenciesHandler,
Expand Down
Loading