Skip to content
Open
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
9 changes: 9 additions & 0 deletions example/api/get_token.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
const { buildGatewayURL } = require('./utils.js');
const { fetchCompanyManagerToken } = require('./jwt_auth.js');

async function fetchAccessToken() {
const {
VITE_CLIENT_ID,
VITE_CLIENT_SECRET,
VITE_REMOTE_GATEWAY,
VITE_REFRESH_TOKEN,
VITE_USER_ID,
} = process.env;

// Local dev has no interactively-obtained refresh token; fall back to the
// JWT-bearer assertion flow (same as fetchCompanyManagerToken) using
// VITE_USER_ID instead.
if (VITE_REMOTE_GATEWAY === 'local' && !VITE_REFRESH_TOKEN && VITE_USER_ID) {
return fetchCompanyManagerToken();
}

// for local development, we don't need a client secret
if (
!VITE_CLIENT_ID ||
Expand Down
25 changes: 25 additions & 0 deletions example/src/ContractorOnboarding.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,8 @@
ContractOriginStep,
InvoiceScheduleStep,
CreateInvoiceScheduleStep,
SkipInvoiceScheduleButton,
PreviewInvoiceButton,
} = components;
const [errors, setErrors] = useState<{
apiError: string;
Expand All @@ -243,10 +245,10 @@
<div className='contractor-onboarding-form-layout'>
<SelectCountryStep
onSubmit={(payload: SelectCountryFormPayload) =>
console.log('payload', payload)

Check warning on line 248 in example/src/ContractorOnboarding.tsx

View workflow job for this annotation

GitHub Actions / Lint and Format

eslint(no-console)

Unexpected console statement.
}
onSuccess={(response: SelectCountrySuccess) =>
console.log('response', response)

Check warning on line 251 in example/src/ContractorOnboarding.tsx

View workflow job for this annotation

GitHub Actions / Lint and Format

eslint(no-console)

Unexpected console statement.
}
onError={({
error,
Expand All @@ -268,10 +270,10 @@
<div className='contractor-onboarding-form-layout'>
<BasicInformationStep
onSubmit={(payload: BasicInformationFormPayload) =>
console.log('payload', payload)

Check warning on line 273 in example/src/ContractorOnboarding.tsx

View workflow job for this annotation

GitHub Actions / Lint and Format

eslint(no-console)

Unexpected console statement.
}
onSuccess={(data: EmploymentCreationResponse) =>
console.log('data', data)

Check warning on line 276 in example/src/ContractorOnboarding.tsx

View workflow job for this annotation

GitHub Actions / Lint and Format

eslint(no-console)

Unexpected console statement.
}
onError={({ error, fieldErrors }) =>
setErrors({ apiError: error.message, fieldErrors })
Expand All @@ -288,11 +290,11 @@
<SaveDraftButton
className='submit-button'
onSuccess={() => {
console.log('Draft saved successfully');

Check warning on line 293 in example/src/ContractorOnboarding.tsx

View workflow job for this annotation

GitHub Actions / Lint and Format

eslint(no-console)

Unexpected console statement.
setErrors({ apiError: '', fieldErrors: [] });
}}
onError={({ error, fieldErrors }) => {
console.log('Draft save failed', error);

Check warning on line 297 in example/src/ContractorOnboarding.tsx

View workflow job for this annotation

GitHub Actions / Lint and Format

eslint(no-console)

Unexpected console statement.
setErrors({ apiError: error.message, fieldErrors });
}}
>
Expand All @@ -314,12 +316,12 @@
<ContractDetailsStep
onSubmit={(
payload: ContractorOnboardingContractDetailsFormPayload,
) => console.log('payload', payload)}

Check warning on line 319 in example/src/ContractorOnboarding.tsx

View workflow job for this annotation

GitHub Actions / Lint and Format

eslint(no-console)

Unexpected console statement.
onSuccess={(
response: ContractorOnboardingContractDetailsResponse,
) => console.log('response', response)}

Check warning on line 322 in example/src/ContractorOnboarding.tsx

View workflow job for this annotation

GitHub Actions / Lint and Format

eslint(no-console)

Unexpected console statement.
onError={({ error, fieldErrors }) => {
console.log('error', error);

Check warning on line 324 in example/src/ContractorOnboarding.tsx

View workflow job for this annotation

GitHub Actions / Lint and Format

eslint(no-console)

Unexpected console statement.
setErrors({ apiError: error.message, fieldErrors });
}}
/>
Expand Down Expand Up @@ -360,7 +362,7 @@
<SaveDraftButton
className='submit-button'
onSuccess={() => {
console.log('Contract details draft saved successfully');

Check warning on line 365 in example/src/ContractorOnboarding.tsx

View workflow job for this annotation

GitHub Actions / Lint and Format

eslint(no-console)

Unexpected console statement.
setErrors({ apiError: '', fieldErrors: [] });
}}
onError={({ error, fieldErrors }) => {
Expand Down Expand Up @@ -591,6 +593,29 @@
>
Back
</BackButton>
{contractorOnboardingBag.existingInvoiceSchedule?.id && (
<SkipInvoiceScheduleButton
className='back-button'
onSuccess={() => console.log('invoice schedule skipped')}
onError={({ error, fieldErrors }) =>
setErrors({ apiError: error.message, fieldErrors })
}
>
Skip this invoice schedule
</SkipInvoiceScheduleButton>
)}
<PreviewInvoiceButton
className='back-button'
onSuccess={(preview) => {
console.log('invoice preview', preview);
window.open(preview.content, '_blank');
}}
onError={({ error, fieldErrors }) =>
setErrors({ apiError: error.message, fieldErrors })
}
>
Preview invoice
</PreviewInvoiceButton>
<SubmitButton
className='submit-button'
onClick={() => setErrors({ apiError: '', fieldErrors: [] })}
Expand Down
8 changes: 8 additions & 0 deletions src/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ export {
postV1Employments,
postV1EmploymentsEmploymentIdContractEligibility,
postV1EmploymentsEmploymentIdContractOrigin,
postV1EmploymentsEmploymentIdContractorInvoicesPreview,
postV1EmploymentsEmploymentIdEngagementAgreementDetails,
postV1EmploymentsEmploymentIdInvite,
postV1EmploymentsEmploymentIdRiskReserveProofOfPayments,
Expand Down Expand Up @@ -394,6 +395,7 @@ export type {
ContractorInvoiceId,
ContractorInvoiceItem,
ContractorInvoiceItemType,
ContractorInvoicePreviewResponse,
ContractorInvoiceResponse,
ContractorInvoiceSchedule,
ContractorInvoiceScheduleCreateParams,
Expand Down Expand Up @@ -1767,6 +1769,11 @@ export type {
PostV1EmploymentsEmploymentIdContractOriginErrors,
PostV1EmploymentsEmploymentIdContractOriginResponse,
PostV1EmploymentsEmploymentIdContractOriginResponses,
PostV1EmploymentsEmploymentIdContractorInvoicesPreviewData,
PostV1EmploymentsEmploymentIdContractorInvoicesPreviewError,
PostV1EmploymentsEmploymentIdContractorInvoicesPreviewErrors,
PostV1EmploymentsEmploymentIdContractorInvoicesPreviewResponse,
PostV1EmploymentsEmploymentIdContractorInvoicesPreviewResponses,
PostV1EmploymentsEmploymentIdEngagementAgreementDetailsData,
PostV1EmploymentsEmploymentIdEngagementAgreementDetailsError,
PostV1EmploymentsEmploymentIdEngagementAgreementDetailsErrors,
Expand Down Expand Up @@ -1977,6 +1984,7 @@ export type {
PostV2EmploymentsEmploymentIdEngagementAgreementDetailsResponses,
PreOnboardingDocumentRequirement,
PreOnboardingRequirement,
PreviewContractorInvoiceParams,
Price,
PricingPlan,
PricingPlanDetails,
Expand Down
36 changes: 36 additions & 0 deletions src/client/sdk.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,9 @@ import type {
PostV1EmploymentsEmploymentIdContractOriginData,
PostV1EmploymentsEmploymentIdContractOriginErrors,
PostV1EmploymentsEmploymentIdContractOriginResponses,
PostV1EmploymentsEmploymentIdContractorInvoicesPreviewData,
PostV1EmploymentsEmploymentIdContractorInvoicesPreviewErrors,
PostV1EmploymentsEmploymentIdContractorInvoicesPreviewResponses,
PostV1EmploymentsEmploymentIdEngagementAgreementDetailsData,
PostV1EmploymentsEmploymentIdEngagementAgreementDetailsErrors,
PostV1EmploymentsEmploymentIdEngagementAgreementDetailsResponses,
Expand Down Expand Up @@ -6595,6 +6598,39 @@ export const postV1EmploymentsEmploymentIdContractOrigin = <
},
});

/**
* Preview a Contractor Invoice
*
* Returns a base64-encoded PDF preview of a contractor invoice built from the given parameters.
*
* The document is a draft and is not persisted.
*
*/
export const postV1EmploymentsEmploymentIdContractorInvoicesPreview = <
ThrowOnError extends boolean = false,
>(
options: Options<
PostV1EmploymentsEmploymentIdContractorInvoicesPreviewData,
ThrowOnError
>,
) =>
(options.client ?? client).post<
PostV1EmploymentsEmploymentIdContractorInvoicesPreviewResponses,
PostV1EmploymentsEmploymentIdContractorInvoicesPreviewErrors,
ThrowOnError
>({
security: [
{ scheme: 'bearer', type: 'http' },
{ scheme: 'bearer', type: 'http' },
],
url: '/v1/employments/{employment_id}/contractor-invoices/preview',
...options,
headers: {
'Content-Type': 'application/json',
...options.headers,
},
});

/**
* List Time Off Types
*
Expand Down
86 changes: 86 additions & 0 deletions src/client/types.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5583,6 +5583,29 @@ export type UpdateScheduleContractorInvoiceParams = {
number?: string | null;
periodicity?: ContractorInvoiceSchedulePeriodicity;
start_date?: Date;
status?: 'inactive' | 'deleted' | 'active' | 'processing';
};

/**
* PreviewContractorInvoiceParams
*
* Payload shape used to preview a contractor invoice before it's created.
*/
export type PreviewContractorInvoiceParams = {
currency: CurrencyCode;
/**
* List of invoice items that composes the overall invoice amount.
*/
items: Array<ContractorInvoiceScheduleItem>;
/**
* Custom defined note.
*/
note?: string | null;
/**
* Invoice identifier.
*/
number?: string | null;
start_date: Date;
};

/**
Expand Down Expand Up @@ -5634,10 +5657,12 @@ export type CommonIncentiveParams = {
* - `generation_failed_unrelated_to_withdrawal_method`: Generation failed for any other reason.
* - `completed`: Number of generated contractor invoices has been reached.
* - `inactive`: Does not create any further contractor invoices but it's still possible for the employer to activate it again.
* - `deleted`: The schedule was cancelled and will not generate any further contractor invoices.
*
*/
export type ContractorInvoiceScheduleStatus =
| 'inactive'
| 'deleted'
| 'completed'
| 'active'
| 'processing'
Expand Down Expand Up @@ -12309,6 +12334,20 @@ export type ContractorInvoiceScheduleItem = {
description: string;
};

/**
* ContractorInvoicePreviewResponse
*
* Returns a base64 encoded Contractor Invoice preview document.
*/
export type ContractorInvoicePreviewResponse = {
data: {
contractor_invoice_preview: {
content: Blob | File;
name: string;
};
};
};

/**
* ForbiddenResponse
*
Expand Down Expand Up @@ -21487,6 +21526,53 @@ export type PostV1EmploymentsEmploymentIdContractOriginResponses = {
export type PostV1EmploymentsEmploymentIdContractOriginResponse =
PostV1EmploymentsEmploymentIdContractOriginResponses[keyof PostV1EmploymentsEmploymentIdContractOriginResponses];

export type PostV1EmploymentsEmploymentIdContractorInvoicesPreviewData = {
/**
* Preview parameters
*/
body: PreviewContractorInvoiceParams;
path: {
/**
* Employment identifier
*/
employment_id: UuidSlug;
};
query?: never;
url: '/v1/employments/{employment_id}/contractor-invoices/preview';
};

export type PostV1EmploymentsEmploymentIdContractorInvoicesPreviewErrors = {
/**
* Unauthorized
*/
401: UnauthorizedResponse;
/**
* Forbidden
*/
403: ForbiddenResponse;
/**
* Not Found
*/
404: NotFoundResponse;
/**
* Unprocessable Entity
*/
422: UnprocessableEntityResponse;
};

export type PostV1EmploymentsEmploymentIdContractorInvoicesPreviewError =
PostV1EmploymentsEmploymentIdContractorInvoicesPreviewErrors[keyof PostV1EmploymentsEmploymentIdContractorInvoicesPreviewErrors];

export type PostV1EmploymentsEmploymentIdContractorInvoicesPreviewResponses = {
/**
* Success
*/
200: ContractorInvoicePreviewResponse;
};

export type PostV1EmploymentsEmploymentIdContractorInvoicesPreviewResponse =
PostV1EmploymentsEmploymentIdContractorInvoicesPreviewResponses[keyof PostV1EmploymentsEmploymentIdContractorInvoicesPreviewResponses];

export type GetV1TimeoffTypesData = {
body?: never;
headers: {
Expand Down
4 changes: 4 additions & 0 deletions src/flows/ContractorOnboarding/ContractorOnboarding.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import { OnboardingInvite } from '@/src/flows/ContractorOnboarding/components/On
import { ContractReviewButton } from '@/src/flows/ContractorOnboarding/components/ContractReviewButton';
import { EligibilityQuestionnaireStep } from '@/src/flows/ContractorOnboarding/components/EligibilityQuestionnaireStep';
import { SaveDraftButton } from '@/src/flows/ContractorOnboarding/components/SaveDraftButton';
import { SkipInvoiceScheduleButton } from '@/src/flows/ContractorOnboarding/components/SkipInvoiceScheduleButton';
import { PreviewInvoiceButton } from '@/src/flows/ContractorOnboarding/components/PreviewInvoiceButton';
import { ContractOriginStep } from '@/src/flows/ContractorOnboarding/components/ContractOriginStep';
import { InvoiceScheduleStep } from '@/src/flows/ContractorOnboarding/components/InvoiceScheduleStep';
import { CreateInvoiceScheduleStep } from '@/src/flows/ContractorOnboarding/components/CreateInvoiceScheduleStep';
Expand Down Expand Up @@ -73,6 +75,8 @@ export const ContractorOnboardingFlow = ({
OnboardingInvite: OnboardingInvite,
ContractReviewButton: ContractReviewButton,
SaveDraftButton: SaveDraftButton,
SkipInvoiceScheduleButton: SkipInvoiceScheduleButton,
PreviewInvoiceButton: PreviewInvoiceButton,
},
})}
</ContractorOnboardingContext.Provider>
Expand Down
63 changes: 61 additions & 2 deletions src/flows/ContractorOnboarding/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ The contractor onboarding process consists of the following steps in order:
3. **Contract Details** - Define contract terms including services, duration, compensation, and notice periods
4. **Pricing Plan** - Select and configure subscription/pricing tier
5. **Contract Origin** - Choose where the contract comes from
6. **Contract Preview** - Review and electronically sign the generated contract
7. **Review** - Final review step (internal)
6. **Invoice Schedule** - Choose whether to set up a recurring invoice schedule now or skip it for later (shown when `options.features` includes `'create_invoice_schedule'`)
7. **Create Invoice Schedule** - Fill in the invoice schedule details (currency, periodicity, items, etc.); also used to edit an already-created schedule
8. **Contract Preview** - Review and electronically sign the generated contract
9. **Review** - Final review step (internal)

### Skipping Steps

Expand Down Expand Up @@ -72,6 +74,10 @@ The render function receives an object with:
- `ContractDetailsStep` - Contract terms form
- `PricingPlanStep` - Pricing tier selection
- `ContractOriginStep` - Contract origin selection form
- `InvoiceScheduleStep` - Invoice schedule preference selection form
- `CreateInvoiceScheduleStep` - Invoice schedule detail form (create or edit)
- `SkipInvoiceScheduleButton` - Cancels an existing invoice schedule
- `PreviewInvoiceButton` - Previews a draft invoice PDF from unsaved form values
- `ContractPreviewStep` - Contract review and signature
- `OnboardingInvite` - Invitation/status component
- `SubmitButton` - Multi-purpose submit button
Expand Down Expand Up @@ -139,6 +145,59 @@ Rendered as a radio group.
- `onSuccess?: (data: { contractOrigin: string }) => void` - Called on successful submission
- `onError?: (error: { error: Error; rawError: Record<string, unknown>; fieldErrors: NormalizedFieldError[] }) => void` - Called on error

### InvoiceScheduleStep

Lets the employer choose whether to set up a recurring invoice schedule now (`schedule`) or skip it for now (`manual`). Only rendered when `options.features` includes `'create_invoice_schedule'`.

**Props:**

- `onSubmit?: (payload: InvoiceScheduleFormPayload) => void` - Called before submission
- `onSuccess?: (data: InvoiceScheduleResponse) => void` - Called on successful submission
- `onError?: (error: { error: Error; fieldErrors: NormalizedFieldError[] }) => void` - Called on error

### CreateInvoiceScheduleStep

Collects the invoice schedule details: currency, periodicity, start date, up to 10 invoice items, invoice number, note, and number of occurrences. When an invoice schedule already exists for the employment, this same step is prefilled and submitting it updates the existing schedule instead of creating a new one.

**Props:**

- `onSubmit?: (payload: $TSFixMe) => void` - Called before submission
- `onSuccess?: (data: $TSFixMe) => void` - Called on successful submission
- `onError?: (error: { error: Error; fieldErrors: NormalizedFieldError[] }) => void` - Called on error

#### SkipInvoiceScheduleButton

Cancels the existing invoice schedule (marks it as `deleted`) and returns the wizard to the `InvoiceScheduleStep`. Disabled unless `contractorOnboardingBag.existingInvoiceSchedule` is present, so it's meant to be rendered alongside `CreateInvoiceScheduleStep` only when editing an existing schedule.

```tsx
<SkipInvoiceScheduleButton onSuccess={() => {}} onError={(error) => {}}>
Skip this invoice schedule
</SkipInvoiceScheduleButton>
```

**Props:**

- `onSuccess?: () => void` - Called after the schedule is successfully skipped
- `onError?: (error: { error: Error; rawError: Record<string, unknown>; fieldErrors: NormalizedFieldError[] }) => void` - Called on error

#### PreviewInvoiceButton

Generates a draft (non-persisted) PDF preview of a contractor invoice from the current, unsaved `CreateInvoiceScheduleStep` form values (`contractorOnboardingBag.fieldValues`). Meant to be rendered alongside `CreateInvoiceScheduleStep`.

```tsx
<PreviewInvoiceButton
onSuccess={(preview) => window.open(preview.content, '_blank')}
onError={(error) => {}}
>
Preview invoice
</PreviewInvoiceButton>
```

**Props:**

- `onSuccess?: (data: ContractorInvoicePreview) => void` - Called with `{ name, content }` (`content` is a `data:application/pdf;base64,...` URI) on success
- `onError?: (error: { error: Error; rawError: Record<string, unknown>; fieldErrors: NormalizedFieldError[] }) => void` - Called on error

### ContractPreviewStep

Displays the generated contract document for review and electronic signature.
Expand Down
Loading
Loading