From 3a6574bcf5d605d626063b6a945d467670358479 Mon Sep 17 00:00:00 2001 From: Steve Jensen Date: Fri, 26 Jun 2026 09:36:46 -0600 Subject: [PATCH 1/3] feat(contractor): add contractor self-onboarding profile components Add an `isAdmin` switch to `ContractorProfile` (admin by default) that, when false, renders dedicated individual/business self-onboarding profile components selected by the retrieved contractor's type. `contractorId` is required at the type level when `isAdmin` is false. Decouple `useContractorDetailsForm` + schema from the `selfOnboarding` flag (matching `useEmployeeDetailsForm`): SSN/EIN are exposed by contractor type only and submitted whenever present. The admin form now owns hiding SSN/EIN and relaxing their requiredness via the watched-value + state/effect loop, so each consumer controls its own required set. Co-authored-by: Cursor --- .../Profile/BusinessSelfOnboardingProfile.tsx | 108 ++++++++++++ .../Profile/ContractorProfile.test.tsx | 166 ++++++++++++++++++ .../Contractor/Profile/ContractorProfile.tsx | 116 +++++++++--- .../IndividualSelfOnboardingProfile.tsx | 123 +++++++++++++ .../SelfOnboardingContractorProfile.tsx | 59 +++++++ .../contractorDetailsSchema.test.ts | 28 ++- .../contractorDetailsSchema.ts | 13 +- .../useContractorDetailsForm/fields.tsx | 15 +- .../useContractorDetailsForm.test.tsx | 64 ++++++- .../useContractorDetailsForm.tsx | 20 ++- src/i18n/en/Contractor.Profile.json | 7 + src/types/i18next.d.ts | 7 + 12 files changed, 673 insertions(+), 53 deletions(-) create mode 100644 src/components/Contractor/Profile/BusinessSelfOnboardingProfile.tsx create mode 100644 src/components/Contractor/Profile/IndividualSelfOnboardingProfile.tsx create mode 100644 src/components/Contractor/Profile/SelfOnboardingContractorProfile.tsx diff --git a/src/components/Contractor/Profile/BusinessSelfOnboardingProfile.tsx b/src/components/Contractor/Profile/BusinessSelfOnboardingProfile.tsx new file mode 100644 index 000000000..d5d6b6aa2 --- /dev/null +++ b/src/components/Contractor/Profile/BusinessSelfOnboardingProfile.tsx @@ -0,0 +1,108 @@ +import { useTranslation } from 'react-i18next' +import classNames from 'classnames' +import { + useContractorDetailsForm, + type ContractorDetailsOptionalFieldsToRequire, +} from './shared/useContractorDetailsForm' +import type { ContractorSelfOnboardingProfileProps } from './SelfOnboardingContractorProfile' +import styles from './ContractorProfile.module.scss' +import { BaseLayout } from '@/components/Base' +import { SDKFormProvider } from '@/partner-hook-utils/form/SDKFormProvider' +import { Form } from '@/components/Common/Form' +import { Flex } from '@/components/Common' +import { useComponentContext } from '@/contexts/ComponentAdapter/useComponentContext' +import { componentEvents } from '@/shared/constants' + +const BUSINESS_REQUIRED_FIELDS: ContractorDetailsOptionalFieldsToRequire = { + update: ['businessName', 'ein'], +} + +/** + * Self-onboarding profile for a business contractor — collects business name + * and EIN. + * + * @internal + */ +export function BusinessSelfOnboardingProfile({ + contractorId, + onEvent, + className, +}: ContractorSelfOnboardingProfileProps) { + const { t } = useTranslation('Contractor.Profile') + const Components = useComponentContext() + + const contractor = useContractorDetailsForm({ + contractorId, + withSelfOnboardingField: false, + defaultValues: { selfOnboarding: true }, + optionalFieldsToRequire: BUSINESS_REQUIRED_FIELDS, + }) + + if (contractor.isLoading) { + return + } + + const { Fields } = contractor.form + + const handleSubmit = async () => { + const result = await contractor.actions.onSubmit() + if (!result) return + + onEvent(componentEvents.CONTRACTOR_UPDATED, result.data) + onEvent(componentEvents.CONTRACTOR_PROFILE_DONE, { + contractorId: result.data.uuid, + selfOnboarding: true, + }) + } + + return ( +
+ + +
void handleSubmit()}> + +
+ + {t('selfOnboarding.title')} + + {t('selfOnboarding.businessDescription')} + + +
+ + {Fields.BusinessName && ( + <> + + {Fields.Ein && ( + + )} + + )} +
+ + + + {contractor.status.isPending + ? t('selfOnboarding.submitting') + : t('selfOnboarding.continue')} + + +
+
+
+
+ ) +} diff --git a/src/components/Contractor/Profile/ContractorProfile.test.tsx b/src/components/Contractor/Profile/ContractorProfile.test.tsx index 4fd83b8e8..c2f4062f3 100644 --- a/src/components/Contractor/Profile/ContractorProfile.test.tsx +++ b/src/components/Contractor/Profile/ContractorProfile.test.tsx @@ -232,4 +232,170 @@ describe('Contractor profile component behavior', () => { ) }) }) + + describe('self-onboarding (isAdmin=false)', () => { + beforeEach(() => { + setupApiTestMocks() + }) + + const companyId = 'company-123' + + // Self-onboarding contractors were invited by email, so the GET always + // includes one; the schema requires email while self_onboarding is true. + const individualContractor = { + uuid: 'contractor_id', + version: 'version-1', + type: 'Individual', + wage_type: 'Fixed', + start_date: '2024-01-01', + first_name: 'John', + last_name: 'Doe', + email: 'john.doe@example.com', + has_ssn: false, + has_ein: false, + is_active: true, + file_new_hire_report: false, + onboarding_status: 'self_onboarding_invited', + } + + const businessContractor = { + uuid: 'contractor_id', + version: 'version-1', + type: 'Business', + wage_type: 'Fixed', + start_date: '2024-01-01', + business_name: 'Acme LLC', + email: 'billing@acme.com', + has_ssn: false, + has_ein: false, + is_active: true, + file_new_hire_report: false, + onboarding_status: 'self_onboarding_invited', + } + + it('renders the individual self-onboarding profile based on contractor type', async () => { + server.use(handleGetContractor(() => HttpResponse.json(individualContractor))) + + renderWithProviders( + , + ) + + await screen.findByText('Complete your profile') + + expect(screen.getByLabelText('First Name')).toBeInTheDocument() + expect(screen.getByLabelText('Last Name')).toBeInTheDocument() + expect(screen.getByLabelText('Social Security Number')).toBeInTheDocument() + expect(screen.queryByLabelText('Business Name')).not.toBeInTheDocument() + expect(screen.queryByRole('switch')).not.toBeInTheDocument() + }) + + it('submits the individual SSN and emits self-onboarding events', async () => { + const user = userEvent.setup() + const onEvent = vi.fn() + let body: Record | null = null + const updateResolver = vi.fn(async ({ request }) => { + body = (await request.json()) as Record + return HttpResponse.json({ + ...individualContractor, + version: 'updated-version', + }) + }) + server.use( + handleGetContractor(() => HttpResponse.json(individualContractor)), + handleUpdateContractor(updateResolver), + ) + + renderWithProviders( + , + ) + + await screen.findByText('Complete your profile') + await user.type(screen.getByLabelText('Social Security Number'), '123-45-6789') + await user.click(screen.getByRole('button', { name: 'Continue' })) + + await waitFor(() => { + expect(updateResolver).toHaveBeenCalledTimes(1) + }) + expect(body).toMatchObject({ ssn: '123456789', self_onboarding: true }) + expect(onEvent).toHaveBeenCalledWith( + contractorEvents.CONTRACTOR_UPDATED, + expect.objectContaining({ uuid: 'contractor_id' }), + ) + expect(onEvent).toHaveBeenCalledWith( + contractorEvents.CONTRACTOR_PROFILE_DONE, + expect.objectContaining({ contractorId: 'contractor_id', selfOnboarding: true }), + ) + }) + + it('renders the business self-onboarding profile and submits the EIN', async () => { + const user = userEvent.setup() + let body: Record | null = null + const updateResolver = vi.fn(async ({ request }) => { + body = (await request.json()) as Record + return HttpResponse.json({ ...businessContractor, version: 'updated-version' }) + }) + server.use( + handleGetContractor(() => HttpResponse.json(businessContractor)), + handleUpdateContractor(updateResolver), + ) + + renderWithProviders( + , + ) + + await screen.findByText('Complete your profile') + expect(screen.getByLabelText('Business Name')).toBeInTheDocument() + expect(screen.queryByLabelText('First Name')).not.toBeInTheDocument() + + await user.type(screen.getByLabelText('EIN'), '12-3456789') + await user.click(screen.getByRole('button', { name: 'Continue' })) + + await waitFor(() => { + expect(updateResolver).toHaveBeenCalledTimes(1) + }) + expect(body).toMatchObject({ ein: '123456789', self_onboarding: true }) + }) + + it('does not require re-entering SSN for an individual with SSN on file', async () => { + const user = userEvent.setup() + const updateResolver = vi.fn(() => + HttpResponse.json({ ...individualContractor, version: 'updated-version' }), + ) + server.use( + handleGetContractor(() => HttpResponse.json({ ...individualContractor, has_ssn: true })), + handleUpdateContractor(updateResolver), + ) + + renderWithProviders( + , + ) + + await screen.findByText('Complete your profile') + await user.click(screen.getByRole('button', { name: 'Continue' })) + + await waitFor(() => { + expect(updateResolver).toHaveBeenCalledTimes(1) + }) + }) + }) }) diff --git a/src/components/Contractor/Profile/ContractorProfile.tsx b/src/components/Contractor/Profile/ContractorProfile.tsx index e39a80b13..d8b402d70 100644 --- a/src/components/Contractor/Profile/ContractorProfile.tsx +++ b/src/components/Contractor/Profile/ContractorProfile.tsx @@ -1,4 +1,4 @@ -import { useMemo } from 'react' +import { useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { useWatch } from 'react-hook-form' import classNames from 'classnames' @@ -9,6 +9,7 @@ import { type ContractorDetailsOptionalFieldsToRequire, type UseContractorDetailsFormReady, } from './shared/useContractorDetailsForm' +import { SelfOnboardingContractorProfile } from './SelfOnboardingContractorProfile' import styles from './ContractorProfile.module.scss' import type { BaseComponentInterface } from '@/components/Base/Base' import { BaseBoundaries, BaseLayout } from '@/components/Base' @@ -20,37 +21,69 @@ import { useI18n } from '@/i18n' import { useComponentDictionary } from '@/i18n/I18n' import { componentEvents, ContractorOnboardingStatus } from '@/shared/constants' -// Restores the contractor profile's product requiredness on top of the hook's -// API-aligned baseline (which leaves ssn/ein optional and create-only fields -// optional on update). Promoting an excluded field is a no-op, and an SSN/EIN -// already on file is still waived via redaction, so this static superset is safe. -const CONTRACTOR_PROFILE_REQUIRED_FIELDS: ContractorDetailsOptionalFieldsToRequire = { - create: ['ssn', 'ein'], - update: ['firstName', 'lastName', 'businessName', 'hourlyRate', 'workState', 'ssn', 'ein'], +// The admin form restores the contractor profile's product requiredness on top +// of the hook's API-aligned baseline (which leaves ssn/ein optional and +// create-only fields optional on update). When the admin invites the contractor +// to self-onboard, SSN/EIN are hidden and the contractor supplies them later, so +// they are dropped from the required set. An SSN/EIN already on file is still +// waived via redaction, so promoting them when not inviting is safe. +function computeRequiredFields( + selfOnboardingActive: boolean, +): ContractorDetailsOptionalFieldsToRequire { + const base: ContractorDetailsOptionalFieldsToRequire = { + create: [], + update: ['firstName', 'lastName', 'businessName', 'hourlyRate', 'workState'], + } + if (selfOnboardingActive) return base + return { + create: [...(base.create ?? []), 'ssn', 'ein'], + update: [...(base.update ?? []), 'ssn', 'ein'], + } } /** * Props for {@link ContractorProfile}. * + * @remarks + * Discriminated by `isAdmin`. In admin mode (the default) `contractorId` is + * optional — omitting it creates a new contractor. In self-onboarding mode + * (`isAdmin={false}`) `contractorId` is required, because the contractor must + * already exist for its type (individual vs. business) to be resolved. + * * @public */ -export interface ContractorProfileProps extends BaseComponentInterface<'Contractor.Profile'> { +export type ContractorProfileProps = BaseComponentInterface<'Contractor.Profile'> & { /** UUID of the company the contractor belongs to. */ companyId: string - /** UUID of an existing contractor to edit. When omitted, the form creates a new contractor. */ - contractorId?: string /** Initial values for the contractor profile form fields. */ defaultValues?: Partial -} +} & ( + | { + /** When `true` (the default), renders the admin create/edit form. */ + isAdmin?: true + /** UUID of an existing contractor to edit. When omitted, the form creates a new contractor. */ + contractorId?: string + } + | { + /** When `false`, renders the contractor self-onboarding profile. */ + isAdmin: false + /** UUID of the existing contractor completing self-onboarding. Required in self-onboarding mode. */ + contractorId: string + } + ) /** * Form for creating or editing a contractor profile, supporting both individual and business contractor types. * * @remarks - * Renders different field sets depending on the contractor type (individual vs. business) and wage type - * (hourly vs. fixed), and exposes a self-onboarding toggle that invites the contractor to complete their - * own setup. When `contractorId` is provided, the form fetches the existing contractor and updates it on - * submit; otherwise it creates a new contractor under `companyId`. + * In admin mode (the default), renders different field sets depending on the contractor type (individual + * vs. business) and wage type (hourly vs. fixed), and exposes a self-onboarding toggle that invites the + * contractor to complete their own setup. When `contractorId` is provided, the form fetches the existing + * contractor and updates it on submit; otherwise it creates a new contractor under `companyId`. + * + * When `isAdmin` is `false`, renders the contractor self-onboarding profile instead: it resolves the + * existing contractor's type and presents the individual (name + SSN) or business (business name + EIN) + * fields for the contractor to complete. * * | Event | Description | Data | * | ----- | ----------- | ---- | @@ -66,7 +99,15 @@ export function ContractorProfile(props: ContractorProfileProps) { useComponentDictionary('Contractor.Profile', props.dictionary) return ( - + {props.isAdmin === false ? ( + + ) : ( + + )} ) } @@ -85,18 +126,30 @@ function ContractorProfileRoot({ [defaultValues], ) + // Mirrors the Employee AdminProfile loop: SSN/EIN requiredness depends on the + // self-onboarding toggle, which lives in the form the hook creates. Seed from + // the create-mode default, then sync from the watched value (see the ready child). + const [selfOnboardingActive, setSelfOnboardingActive] = useState( + !contractorId && (resolvedDefaults.selfOnboarding ?? false), + ) + + const optionalFieldsToRequire = useMemo( + () => computeRequiredFields(selfOnboardingActive), + [selfOnboardingActive], + ) + const contractor = useContractorDetailsForm( contractorId ? { companyId, contractorId, defaultValues: resolvedDefaults, - optionalFieldsToRequire: CONTRACTOR_PROFILE_REQUIRED_FIELDS, + optionalFieldsToRequire, } : { companyId, defaultValues: resolvedDefaults, - optionalFieldsToRequire: CONTRACTOR_PROFILE_REQUIRED_FIELDS, + optionalFieldsToRequire, }, ) @@ -104,16 +157,29 @@ function ContractorProfileRoot({ return } - return + return ( + + ) } interface ContractorProfileReadyProps { contractor: UseContractorDetailsFormReady onEvent: ContractorProfileProps['onEvent'] className?: string + setSelfOnboardingActive: (value: boolean) => void } -function ContractorProfileReady({ contractor, onEvent, className }: ContractorProfileReadyProps) { +function ContractorProfileReady({ + contractor, + onEvent, + className, + setSelfOnboardingActive, +}: ContractorProfileReadyProps) { const { t } = useTranslation('Contractor.Profile') const Components = useComponentContext() @@ -122,6 +188,10 @@ function ContractorProfileReady({ contractor, onEvent, className }: ContractorPr name: 'selfOnboarding', }) + useEffect(() => { + setSelfOnboardingActive(watchedSelfOnboarding) + }, [watchedSelfOnboarding, setSelfOnboardingActive]) + const { Fields } = contractor.form const mode = contractor.status.mode @@ -204,7 +274,7 @@ function ContractorProfileReady({ contractor, onEvent, className }: ContractorPr INVALID_NAME: t('validations.lastNameFormat'), }} /> - {Fields.Ssn && ( + {Fields.Ssn && !watchedSelfOnboarding && ( - {Fields.Ein && ( + {Fields.Ein && !watchedSelfOnboarding && ( + } + + const { Fields } = contractor.form + + const handleSubmit = async () => { + const result = await contractor.actions.onSubmit() + if (!result) return + + onEvent(componentEvents.CONTRACTOR_UPDATED, result.data) + onEvent(componentEvents.CONTRACTOR_PROFILE_DONE, { + contractorId: result.data.uuid, + selfOnboarding: true, + }) + } + + return ( +
+ + +
void handleSubmit()}> + +
+ + {t('selfOnboarding.title')} + + {t('selfOnboarding.individualDescription')} + + +
+ + {Fields.FirstName && Fields.LastName && ( + <> + + + {Fields.MiddleInitial && ( + + )} + + + {Fields.Ssn && ( + + )} + + )} +
+ + + + {contractor.status.isPending + ? t('selfOnboarding.submitting') + : t('selfOnboarding.continue')} + + +
+
+
+
+ ) +} diff --git a/src/components/Contractor/Profile/SelfOnboardingContractorProfile.tsx b/src/components/Contractor/Profile/SelfOnboardingContractorProfile.tsx new file mode 100644 index 000000000..7c65f2f2d --- /dev/null +++ b/src/components/Contractor/Profile/SelfOnboardingContractorProfile.tsx @@ -0,0 +1,59 @@ +import { useContractorsGet } from '@gusto/embedded-api-v-2025-11-15/react-query/contractorsGet' +import { ContractorType } from './shared/useContractorDetailsForm' +import { IndividualSelfOnboardingProfile } from './IndividualSelfOnboardingProfile' +import { BusinessSelfOnboardingProfile } from './BusinessSelfOnboardingProfile' +import { BaseLayout } from '@/components/Base' +import type { OnEventType } from '@/components/Base/useBase' +import { composeErrorHandler } from '@/partner-hook-utils/composeErrorHandler' +import { useI18n } from '@/i18n' +import type { EventType } from '@/shared/constants' + +/** + * Props shared by the contractor self-onboarding profile components. + * + * @internal + */ +export interface ContractorSelfOnboardingProfileProps { + /** UUID of the existing contractor completing self-onboarding. */ + contractorId: string + /** Callback invoked when the component emits an event. */ + onEvent: OnEventType + /** Optional class name applied to the root element. */ + className?: string +} + +/** + * Resolves the contractor's type and renders the matching self-onboarding + * profile — individual or business. + * + * @internal + */ +export function SelfOnboardingContractorProfile({ + contractorId, + onEvent, + className, +}: ContractorSelfOnboardingProfileProps) { + useI18n('Contractor.Profile') + + const contractorQuery = useContractorsGet({ contractorUuid: contractorId }) + const contractor = contractorQuery.data?.contractor + + if (contractorQuery.isLoading || !contractor) { + const errorHandling = composeErrorHandler([contractorQuery]) + return + } + + return contractor.type === ContractorType.Business ? ( + + ) : ( + + ) +} diff --git a/src/components/Contractor/Profile/shared/useContractorDetailsForm/contractorDetailsSchema.test.ts b/src/components/Contractor/Profile/shared/useContractorDetailsForm/contractorDetailsSchema.test.ts index c46120cdc..e0d2816b6 100644 --- a/src/components/Contractor/Profile/shared/useContractorDetailsForm/contractorDetailsSchema.test.ts +++ b/src/components/Contractor/Profile/shared/useContractorDetailsForm/contractorDetailsSchema.test.ts @@ -145,11 +145,27 @@ describe('createContractorDetailsSchema', () => { }) describe('Individual, self-onboarding (selfOnboarding: true)', () => { - it('does not require ssn (field excluded)', () => { + it('treats ssn as optional by default (not gated by self-onboarding)', () => { const result = parse({ ...validIndividualSelfOnboarding, ssn: '' }, { mode: 'create' }) expect(result.success).toBe(true) }) + it('requires ssn when promoted even while self-onboarding', () => { + const result = parse( + { ...validIndividualSelfOnboarding, ssn: '' }, + { mode: 'create', optionalFieldsToRequire: { create: ['ssn'] } }, + ) + expect(issueFor(result, 'ssn')?.message).toBe(REQUIRED) + }) + + it('still validates ssn format when present while self-onboarding', () => { + const result = parse( + { ...validIndividualSelfOnboarding, ssn: '123-45-678' }, + { mode: 'create' }, + ) + expect(issueFor(result, 'ssn')?.message).toBe(INVALID_SSN) + }) + it('requires email on create', () => { const result = parse({ ...validIndividualSelfOnboarding, email: '' }, { mode: 'create' }) expect(issueFor(result, 'email')?.message).toBe(REQUIRED) @@ -216,11 +232,19 @@ describe('createContractorDetailsSchema', () => { }) describe('Business, self-onboarding (selfOnboarding: true)', () => { - it('does not require ein (field excluded)', () => { + it('treats ein as optional by default (not gated by self-onboarding)', () => { const result = parse({ ...validBusinessSelfOnboarding, ein: '' }, { mode: 'create' }) expect(result.success).toBe(true) }) + it('requires ein when promoted even while self-onboarding', () => { + const result = parse( + { ...validBusinessSelfOnboarding, ein: '' }, + { mode: 'create', optionalFieldsToRequire: { create: ['ein'] } }, + ) + expect(issueFor(result, 'ein')?.message).toBe(REQUIRED) + }) + it('requires email', () => { const result = parse({ ...validBusinessSelfOnboarding, email: '' }, { mode: 'create' }) expect(issueFor(result, 'email')?.message).toBe(REQUIRED) diff --git a/src/components/Contractor/Profile/shared/useContractorDetailsForm/contractorDetailsSchema.ts b/src/components/Contractor/Profile/shared/useContractorDetailsForm/contractorDetailsSchema.ts index 060727472..fd9638baa 100644 --- a/src/components/Contractor/Profile/shared/useContractorDetailsForm/contractorDetailsSchema.ts +++ b/src/components/Contractor/Profile/shared/useContractorDetailsForm/contractorDetailsSchema.ts @@ -110,9 +110,11 @@ export type ContractorDetailsFormOutputs = ContractorDetailsFormData // // Requiredness mirrors the contractor create/update API contract: fields the // API requires on create are `'create'` (optional on update), and fields the -// API treats as optional are `'never'`. Type/wage/self-onboarding *applicability* -// (does a field apply to this contractor at all?) is handled separately by -// `excludeFields` — see `getExcludedContractorFields` — not by requiredness. +// API treats as optional are `'never'`. Type/wage *applicability* (does a field +// apply to this contractor at all?) is handled separately by `excludeFields` — +// see `getExcludedContractorFields` — not by requiredness. SSN/EIN are never +// gated by `selfOnboarding`: each consumer decides whether to render and require +// them (admins hide them when inviting; self-onboarding profiles collect them). // // `email` is the one genuinely value-conditional rule: it always applies but is // only required when self-onboarding is on (create and update, matching the @@ -176,7 +178,7 @@ export function createContractorDetailsSchema(options: ContractorDetailsSchemaOp /** @internal */ export type ContractorDiscriminators = Partial< - Pick + Pick > /** @@ -191,18 +193,15 @@ export function getExcludedContractorFields( values: ContractorDiscriminators, ): Array { const isIndividual = values.type === ContractorType.Individual - const selfOnboarding = Boolean(values.selfOnboarding) const excluded: Array = [] if (values.wageType !== WageType.Hourly) excluded.push('hourlyRate') if (isIndividual) { excluded.push('businessName', 'ein') - if (selfOnboarding) excluded.push('ssn') if (!values.fileNewHireReport) excluded.push('workState') } else { excluded.push('firstName', 'lastName', 'middleInitial', 'fileNewHireReport', 'ssn', 'workState') - if (selfOnboarding) excluded.push('ein') } return excluded diff --git a/src/components/Contractor/Profile/shared/useContractorDetailsForm/fields.tsx b/src/components/Contractor/Profile/shared/useContractorDetailsForm/fields.tsx index 8147c7532..a9a735867 100644 --- a/src/components/Contractor/Profile/shared/useContractorDetailsForm/fields.tsx +++ b/src/components/Contractor/Profile/shared/useContractorDetailsForm/fields.tsx @@ -335,10 +335,10 @@ export type SsnFieldProps = HookFieldProps< * Text input bound to the `ssn` field of {@link useContractorDetailsForm}. * * @remarks - * Available on the hook result as `form.Fields.Ssn` for individual contractors - * who are not self-onboarding. Auto-formats input with dashes - * (`XXX-XX-XXXX`). When the contractor already has an SSN on file, the field - * shows a masked placeholder and the required rule is waived. + * Available on the hook result as `form.Fields.Ssn` for individual contractors. + * Auto-formats input with dashes (`XXX-XX-XXXX`). When the contractor already + * has an SSN on file, the field shows a masked placeholder and the required + * rule is waived. * * @param props - {@link SsnFieldProps}. * @returns The rendered text input bound to `ssn`. @@ -371,10 +371,9 @@ export type EinFieldProps = HookFieldProps< * Text input bound to the `ein` field of {@link useContractorDetailsForm}. * * @remarks - * Available on the hook result as `form.Fields.Ein` for business contractors - * who are not self-onboarding. Auto-formats input as `XX-XXXXXXX`. When the - * contractor already has an EIN on file, the field shows a masked placeholder - * and the required rule is waived. + * Available on the hook result as `form.Fields.Ein` for business contractors. + * Auto-formats input as `XX-XXXXXXX`. When the contractor already has an EIN on + * file, the field shows a masked placeholder and the required rule is waived. * * @param props - {@link EinFieldProps}. * @returns The rendered text input bound to `ein`. diff --git a/src/components/Contractor/Profile/shared/useContractorDetailsForm/useContractorDetailsForm.test.tsx b/src/components/Contractor/Profile/shared/useContractorDetailsForm/useContractorDetailsForm.test.tsx index 3a6e6d46e..0112970fe 100644 --- a/src/components/Contractor/Profile/shared/useContractorDetailsForm/useContractorDetailsForm.test.tsx +++ b/src/components/Contractor/Profile/shared/useContractorDetailsForm/useContractorDetailsForm.test.tsx @@ -77,7 +77,7 @@ describe('useContractorDetailsForm', () => { expect(ready.form.Fields.Ssn).toBeUndefined() }) - it('reveals email and hides ssn when self-onboarding is enabled', async () => { + it('reveals email and keeps ssn exposed when self-onboarding is enabled', async () => { const { result } = renderForm({ companyId: 'company-1', defaultValues: { type: ContractorType.Individual, selfOnboarding: true }, @@ -89,8 +89,25 @@ describe('useContractorDetailsForm', () => { const ready = result.current assertReady(ready) + // SSN/EIN are exposed by contractor type only — never gated by + // self-onboarding. Consumers decide whether to render them. expect(ready.form.Fields.Email).toBeDefined() - expect(ready.form.Fields.Ssn).toBeUndefined() + expect(ready.form.Fields.Ssn).toBeDefined() + }) + + it('keeps ein exposed for a business contractor when self-onboarding is enabled', async () => { + const { result } = renderForm({ + companyId: 'company-1', + defaultValues: { type: ContractorType.Business, selfOnboarding: true }, + }) + + await waitFor(() => { + expect(result.current.isLoading).toBe(false) + }) + const ready = result.current + assertReady(ready) + + expect(ready.form.Fields.Ein).toBeDefined() }) it('reveals the hourly rate field when wageType is Hourly', async () => { @@ -252,7 +269,7 @@ describe('useContractorDetailsForm', () => { }) }) - it('omits ssn and sends email when self-onboarding is enabled', async () => { + it('sends ssn and email together when self-onboarding is enabled', async () => { let body: Record | null = null const createResolver = vi.fn(async ({ request }) => { body = (await request.json()) as Record @@ -283,12 +300,51 @@ describe('useContractorDetailsForm', () => { await ready.actions.onSubmit() }) + // The hook submits whatever SSN is present regardless of self-onboarding; + // suppressing it (admin invite flow) is the consuming component's job. expect(createResolver).toHaveBeenCalledTimes(1) expect(body).toMatchObject({ self_onboarding: true, email: 'jane@example.com', + ssn: '123456789', + }) + }) + + it('sends ein when a business contractor is self-onboarding', async () => { + let body: Record | null = null + const createResolver = vi.fn(async ({ request }) => { + body = (await request.json()) as Record + return HttpResponse.json({ uuid: 'new-uuid', is_active: true }, { status: 201 }) + }) + server.use(handleCreateContractor(createResolver)) + + const { result } = renderForm({ + companyId: 'company-1', + defaultValues: { + type: ContractorType.Business, + wageType: WageType.Fixed, + selfOnboarding: true, + businessName: 'Acme LLC', + email: 'billing@acme.com', + ein: '12-3456789', + }, + }) + + await waitFor(() => { + expect(result.current.isLoading).toBe(false) + }) + const ready = result.current + assertReady(ready) + + await act(async () => { + await ready.actions.onSubmit() + }) + + expect(createResolver).toHaveBeenCalledTimes(1) + expect(body).toMatchObject({ + self_onboarding: true, + ein: '123456789', }) - expect(body).not.toHaveProperty('ssn') }) it('does not call the mutation when validation fails', async () => { diff --git a/src/components/Contractor/Profile/shared/useContractorDetailsForm/useContractorDetailsForm.tsx b/src/components/Contractor/Profile/shared/useContractorDetailsForm/useContractorDetailsForm.tsx index 820907bde..59f2ab1d5 100644 --- a/src/components/Contractor/Profile/shared/useContractorDetailsForm/useContractorDetailsForm.tsx +++ b/src/components/Contractor/Profile/shared/useContractorDetailsForm/useContractorDetailsForm.tsx @@ -129,9 +129,9 @@ export interface ContractorDetailsFields { MiddleInitial: typeof MiddleInitialField | undefined /** Text input bound to `businessName`; available only for business contractors. */ BusinessName: typeof BusinessNameField | undefined - /** Text input bound to `ssn`; available only for individual contractors who are not self-onboarding. */ + /** Text input bound to `ssn`; available only for individual contractors. */ Ssn: typeof SsnField | undefined - /** Text input bound to `ein`; available only for business contractors who are not self-onboarding. */ + /** Text input bound to `ein`; available only for business contractors. */ Ein: typeof EinField | undefined /** Select bound to `workState`; available only for individual contractors filing a new-hire report. */ WorkState: typeof WorkStateField | undefined @@ -189,9 +189,11 @@ const canToggleSelfOnboarding = (contractor?: Contractor) => { * Returns a discriminated union: a loading variant while the contractor fetch * resolves, and a ready variant exposing the form's data, pending status, * submit action, error handling, and bound `Fields`. Field visibility is - * driven by the current `type`, `wageType`, and self-onboarding selection; - * fields that do not apply are `undefined` on `form.Fields`. Self-onboarding - * is only toggleable when the contractor's onboarding status allows it. + * driven by the current `type` and `wageType` (self-onboarding only toggles the + * `Email` field); fields that do not apply are `undefined` on `form.Fields`. + * SSN/EIN are exposed by contractor type regardless of self-onboarding — each + * consumer decides whether to render them. Self-onboarding is only toggleable + * when the contractor's onboarding status allows it. * * @param input - See {@link UseContractorDetailsFormProps}. * @returns A {@link HookLoadingResult} while loading, or a {@link UseContractorDetailsFormReady} once ready. @@ -340,12 +342,12 @@ export function useContractorDetailsForm({ workState: payload.fileNewHireReport ? payload.workState || undefined : undefined, - ssn: !selfOnboardingEnabled && cleanedSsn ? cleanedSsn : undefined, + ssn: cleanedSsn || undefined, } : { fileNewHireReport: false, businessName: payload.businessName, - ein: !selfOnboardingEnabled && cleanedEin ? cleanedEin : undefined, + ein: cleanedEin || undefined, }), } @@ -442,8 +444,8 @@ export function useContractorDetailsForm({ LastName: isIndividual ? LastNameField : undefined, MiddleInitial: isIndividual ? MiddleInitialField : undefined, BusinessName: isBusiness ? BusinessNameField : undefined, - Ssn: isIndividual && !watchedSelfOnboarding ? SsnField : undefined, - Ein: isBusiness && !watchedSelfOnboarding ? EinField : undefined, + Ssn: isIndividual ? SsnField : undefined, + Ein: isBusiness ? EinField : undefined, WorkState: isIndividual && watchedFileNewHireReport ? WorkStateField : undefined, }, fieldsMetadata, diff --git a/src/i18n/en/Contractor.Profile.json b/src/i18n/en/Contractor.Profile.json index 6bb348554..7a7f2b24c 100644 --- a/src/i18n/en/Contractor.Profile.json +++ b/src/i18n/en/Contractor.Profile.json @@ -1,6 +1,13 @@ { "title": "Contractor profile", "subtitle": "This information will be used for payments and on tax documents, so double-check that it's accurate.", + "selfOnboarding": { + "title": "Complete your profile", + "individualDescription": "Please verify your name and provide your Social Security Number.", + "businessDescription": "Please verify your business name and provide your EIN.", + "continue": "Continue", + "submitting": "Saving..." + }, "fields": { "selfOnboarding": { "label": "Invite this contractor to enter some of their own details online", diff --git a/src/types/i18next.d.ts b/src/types/i18next.d.ts index 34cf1f473..364358f23 100644 --- a/src/types/i18next.d.ts +++ b/src/types/i18next.d.ts @@ -1239,6 +1239,13 @@ export interface ContractorPaymentsPaymentsList{ export interface ContractorProfile{ "title":string; "subtitle":string; +"selfOnboarding":{ +"title":string; +"individualDescription":string; +"businessDescription":string; +"continue":string; +"submitting":string; +}; "fields":{ "selfOnboarding":{ "label":string; From e7d6e526cd7034224f32adbba8e42746efba64d7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 26 Jun 2026 15:52:44 +0000 Subject: [PATCH 2/3] chore: update derived files --- .reports/embedded-react-sdk.api.md | 11 ++-- .../hooks/use-contractor-details-form.md | 27 +++++----- .../reference/contractor/onboarding/blocks.md | 50 ++++++++++++------- docs/reference/index.md | 1 - 4 files changed, 55 insertions(+), 34 deletions(-) diff --git a/.reports/embedded-react-sdk.api.md b/.reports/embedded-react-sdk.api.md index a61c09b99..108301511 100644 --- a/.reports/embedded-react-sdk.api.md +++ b/.reports/embedded-react-sdk.api.md @@ -1390,11 +1390,16 @@ export const ContractorOnboardingStatus: { function ContractorProfile(props: ContractorProfileProps): JSX; // @public -interface ContractorProfileProps extends BaseComponentInterface<'Contractor.Profile'> { +type ContractorProfileProps = BaseComponentInterface<'Contractor.Profile'> & { companyId: string; - contractorId?: string; defaultValues?: Partial; -} +} & ({ + isAdmin?: true; + contractorId?: string; +} | { + isAdmin: false; + contractorId: string; +}); // @public export function ContractorSelfOnboardingField(props: ContractorSelfOnboardingFieldProps): JSX; diff --git a/docs/reference/contractor/hooks/use-contractor-details-form.md b/docs/reference/contractor/hooks/use-contractor-details-form.md index a95b9ebac..ea3f06588 100644 --- a/docs/reference/contractor/hooks/use-contractor-details-form.md +++ b/docs/reference/contractor/hooks/use-contractor-details-form.md @@ -46,10 +46,9 @@ Text input bound to the `ein` field of [useContractorDetailsForm](#usecontractor #### Remarks -Available on the hook result as `form.Fields.Ein` for business contractors -who are not self-onboarding. Auto-formats input as `XX-XXXXXXX`. When the -contractor already has an EIN on file, the field shows a masked placeholder -and the required rule is waived. +Available on the hook result as `form.Fields.Ein` for business contractors. +Auto-formats input as `XX-XXXXXXX`. When the contractor already has an EIN on +file, the field shows a masked placeholder and the required rule is waived. *** @@ -205,10 +204,10 @@ Text input bound to the `ssn` field of [useContractorDetailsForm](#usecontractor #### Remarks -Available on the hook result as `form.Fields.Ssn` for individual contractors -who are not self-onboarding. Auto-formats input with dashes -(`XXX-XX-XXXX`). When the contractor already has an SSN on file, the field -shows a masked placeholder and the required rule is waived. +Available on the hook result as `form.Fields.Ssn` for individual contractors. +Auto-formats input with dashes (`XXX-XX-XXXX`). When the contractor already +has an SSN on file, the field shows a masked placeholder and the required +rule is waived. *** @@ -316,9 +315,11 @@ A [HookLoadingResult](../../utilities.md#hookloadingresult) while loading, or a Returns a discriminated union: a loading variant while the contractor fetch resolves, and a ready variant exposing the form's data, pending status, submit action, error handling, and bound `Fields`. Field visibility is -driven by the current `type`, `wageType`, and self-onboarding selection; -fields that do not apply are `undefined` on `form.Fields`. Self-onboarding -is only toggleable when the contractor's onboarding status allows it. +driven by the current `type` and `wageType` (self-onboarding only toggles the +`Email` field); fields that do not apply are `undefined` on `form.Fields`. +SSN/EIN are exposed by contractor type regardless of self-onboarding — each +consumer decides whether to render them. Self-onboarding is only toggleable +when the contractor's onboarding status allows it. ## Variables @@ -395,7 +396,7 @@ before rendering. | Property | Type | Description | | ------ | ------ | ------ | | `BusinessName` | ((`props`) => `Element`) \| `undefined` | Text input bound to `businessName`; available only for business contractors. | -| `Ein` | ((`props`) => `Element`) \| `undefined` | Text input bound to `ein`; available only for business contractors who are not self-onboarding. | +| `Ein` | ((`props`) => `Element`) \| `undefined` | Text input bound to `ein`; available only for business contractors. | | `Email` | ((`props`) => `Element`) \| `undefined` | Text input bound to `email`; available only when self-onboarding is enabled. | | `FileNewHireReport` | ((`props`) => `Element`) \| `undefined` | Switch bound to `fileNewHireReport`; available only for individual contractors. | | `FirstName` | ((`props`) => `Element`) \| `undefined` | Text input bound to `firstName`; available only for individual contractors. | @@ -403,7 +404,7 @@ before rendering. | `LastName` | ((`props`) => `Element`) \| `undefined` | Text input bound to `lastName`; available only for individual contractors. | | `MiddleInitial` | ((`props`) => `Element`) \| `undefined` | Text input bound to `middleInitial`; available only for individual contractors. | | `SelfOnboarding` | ((`props`) => `Element`) \| `undefined` | Switch bound to `selfOnboarding`; available only when toggleable. | -| `Ssn` | ((`props`) => `Element`) \| `undefined` | Text input bound to `ssn`; available only for individual contractors who are not self-onboarding. | +| `Ssn` | ((`props`) => `Element`) \| `undefined` | Text input bound to `ssn`; available only for individual contractors. | | `StartDate` | (`props`) => `Element` | Date picker bound to `startDate`. Always available. | | `Type` | (`props`) => `Element` | Radio group bound to `type`. Always available. | | `WageType` | (`props`) => `Element` | Radio group bound to `wageType`. Always available. | diff --git a/docs/reference/contractor/onboarding/blocks.md b/docs/reference/contractor/onboarding/blocks.md index 5d8a2c997..dbe2907b1 100644 --- a/docs/reference/contractor/onboarding/blocks.md +++ b/docs/reference/contractor/onboarding/blocks.md @@ -89,28 +89,22 @@ _Inherits `children`, `className`, `defaultValues`, `FallbackComponent`, `Loader Form for creating or editing a contractor profile, supporting both individual and business contractor types. -### ContractorProfileProps - - +### Parameters -Props for [ContractorProfile](#contractorprofile). - -| Property | Type | Description | +| Parameter | Type | Description | | ------ | ------ | ------ | -| `companyId` | `string` | UUID of the company the contractor belongs to. | -| `onEvent` | [`OnEventType`](../../index.md#oneventtype)\<[`EventType`](../../events.md#eventtype), `unknown`\> | Callback invoked each time the component emits an event — user interactions, successful API responses, step transitions, or errors. Receives the event type constant and an optional payload whose shape varies by event. See the [Event Handling guide](https://docs.gusto.com/embedded-payroll/docs/event-handling) and each component's event table for the full list of emitted events. | -| `contractorId?` | `string` | UUID of an existing contractor to edit. When omitted, the form creates a new contractor. | -| `defaultValues?` | `Partial`\<[`ContractorDetailsFormData`](../hooks/use-contractor-details-form.md#contractordetailsformdata)\> | Initial values for the contractor profile form fields. | -| `dictionary?` | `Record`\<`"en"`, [`DeepPartial`](../../index.md#deeppartial)\<`ContractorProfile`\>\> | Overrides for the component's i18n strings. Supply a partial object whose keys match the component's resource namespace — any omitted keys fall back to SDK defaults. See the [Translation guide](https://docs.gusto.com/embedded-payroll/docs/translation) for details. | - -_Inherits `children`, `className`, `FallbackComponent`, `LoaderComponent` from [BaseComponentInterface](../../index.md#basecomponentinterface)._ +| `props` | [`ContractorProfileProps`](#contractorprofileprops) | See [ContractorProfileProps](#contractorprofileprops). | ### Remarks -Renders different field sets depending on the contractor type (individual vs. business) and wage type -(hourly vs. fixed), and exposes a self-onboarding toggle that invites the contractor to complete their -own setup. When `contractorId` is provided, the form fetches the existing contractor and updates it on -submit; otherwise it creates a new contractor under `companyId`. +In admin mode (the default), renders different field sets depending on the contractor type (individual +vs. business) and wage type (hourly vs. fixed), and exposes a self-onboarding toggle that invites the +contractor to complete their own setup. When `contractorId` is provided, the form fetches the existing +contractor and updates it on submit; otherwise it creates a new contractor under `companyId`. + +When `isAdmin` is `false`, renders the contractor self-onboarding profile instead: it resolves the +existing contractor's type and presents the individual (name + SSN) or business (business name + EIN) +fields for the contractor to complete. | Event | Description | Data | | ----- | ----------- | ---- | @@ -276,6 +270,28 @@ function PaymentMethodStep() { Pre-fill values accepted by [Address](#address). At least one of `street1`, `street2`, `city`, `state`, or `zip` must be provided. + + +### ContractorProfileProps + +> **ContractorProfileProps** = [`BaseComponentInterface`](../../index.md#basecomponentinterface)\<`"Contractor.Profile"`\> & `object` & \{ `contractorId?`: `string`; `isAdmin?`: `true`; \} \| \{ `contractorId`: `string`; `isAdmin`: `false`; \} + +Props for [ContractorProfile](#contractorprofile). + +#### Type Declaration + +| Name | Type | Description | +| ------ | ------ | ------ | +| `companyId` | `string` | UUID of the company the contractor belongs to. | +| `defaultValues?` | `Partial`\<[`ContractorDetailsFormData`](../hooks/use-contractor-details-form.md#contractordetailsformdata)\> | Initial values for the contractor profile form fields. | + +#### Remarks + +Discriminated by `isAdmin`. In admin mode (the default) `contractorId` is +optional — omitting it creates a new contractor. In self-onboarding mode +(`isAdmin={false}`) `contractorId` is required, because the contractor must +already exist for its type (individual vs. business) to be resolved. + ### OnboardingFlowDefaultValues diff --git a/docs/reference/index.md b/docs/reference/index.md index 1939a9ac6..6db72fbd6 100644 --- a/docs/reference/index.md +++ b/docs/reference/index.md @@ -367,7 +367,6 @@ shape mixed into every public SDK feature component. - [`OnboardingFlowProps`](contractor/onboarding/onboarding-flow.md#onboardingflowprops) - [`LandingProps`](contractor/onboarding/blocks.md#landingprops) - [`ContractorListProps`](contractor/onboarding/blocks.md#contractorlistprops) -- [`ContractorProfileProps`](contractor/onboarding/blocks.md#contractorprofileprops) - [`PaymentMethodProps`](contractor/onboarding/blocks.md#paymentmethodprops) - [`NewHireReportProps`](contractor/onboarding/blocks.md#newhirereportprops) - [`ContractorSubmitProps`](contractor/onboarding/blocks.md#contractorsubmitprops) From c00d851c080de969ffdf939965111709cd4459c3 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 26 Jun 2026 16:09:17 +0000 Subject: [PATCH 3/3] fix(ci): resolve docs-build failures (downgrade cspell, remove stale docs/api) --- docs/api/Contractor/hooks.md | 404 ----------------------------------- package-lock.json | 336 +++++++++++++++-------------- package.json | 2 +- 3 files changed, 178 insertions(+), 564 deletions(-) delete mode 100644 docs/api/Contractor/hooks.md diff --git a/docs/api/Contractor/hooks.md b/docs/api/Contractor/hooks.md deleted file mode 100644 index 8f36639e3..000000000 --- a/docs/api/Contractor/hooks.md +++ /dev/null @@ -1,404 +0,0 @@ ---- -# Autogenerated by TypeDoc from TSDoc comments in the source code. -# To update content: edit TSDoc comments in src/. -# To update structure: edit docs-site/typedoc.config.ts or docs-site/plugins/sdk-router.ts. -# Then run `npm run docs:api:generate` to regenerate. -title: Contractor Hooks -description: Contractor Hooks API reference. -generated_by: typedoc -custom_edit_url: null ---- - -# Hooks - -## useContractorAddressForm - - - -### useContractorAddressForm() - -> **useContractorAddressForm**(`props`): [`HookLoadingResult`](../utilities.md#hookloadingresult) \| [`UseContractorAddressFormReady`](#usecontractoraddressformready) - -Form hook for editing a contractor's address. - -#### Parameters - -| Parameter | Type | Description | -| --------- | ----------------------------------------------------------------- | -------------------------------------------------------------------- | -| `props` | [`UseContractorAddressFormProps`](#usecontractoraddressformprops) | See [UseContractorAddressFormProps](#usecontractoraddressformprops). | - -#### Returns - -[`HookLoadingResult`](../utilities.md#hookloadingresult) \| [`UseContractorAddressFormReady`](#usecontractoraddressformready) - -A [HookLoadingResult](../utilities.md#hookloadingresult) while loading, or a [UseContractorAddressFormReady](#usecontractoraddressformready) once ready. - -#### Remarks - -A contractor always has exactly one address (created with the contractor), -so this hook operates only in update mode and issues a PUT on submit. The -same address is labelled a "home" address for Individual contractors and a -"business" address for Business contractors; the hook exposes `contractorType` -so the consuming component can choose the appropriate copy. - ---- - - - -### ContractorAddressCityField - -Text input bound to the `city` field of [useContractorAddressForm](#usecontractoraddressform). - -#### Parameters - -| Parameter | Type | Description | -| --------- | --------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `props` | [`ContractorAddressCityFieldProps`](#contractoraddresscityfieldprops) | [CityFieldProps](#contractoraddresscityfieldprops) — accepts the standard hook field props (label, description, validationMessages, FieldComponent override). | - -#### Remarks - -Available on the hook result as `form.Fields.City`. Required. - ---- - - - -### ContractorAddressCityFieldProps - -> **ContractorAddressCityFieldProps** = [`HookFieldProps`](../utilities.md#hookfieldprops)\<[`TextInputHookFieldProps`](../utilities.md#textinputhookfieldprops)\<[`ContractorAddressRequiredValidation`](#contractoraddressrequiredvalidation)\>\> - -Props accepted by [useContractorAddressForm](#usecontractoraddressform)'s `Fields.City` component. - ---- - - - -### ContractorAddressErrorCode - -> **ContractorAddressErrorCode** = _typeof_ [`ContractorAddressErrorCodes`](#contractoraddresserrorcodes)\[keyof _typeof_ [`ContractorAddressErrorCodes`](#contractoraddresserrorcodes)\] - -Union of validation error code strings emitted by the contractor address -form schema. - ---- - - - -### ContractorAddressErrorCodes - -> `const` **ContractorAddressErrorCodes**: `object` - -Validation error codes emitted by the contractor address form schema. Map -these codes to localized copy in `validationMessages` when composing the -hook. - -#### Type Declaration - -| Name | Type | Default value | -| ------------- | --------------- | --------------- | -| `INVALID_ZIP` | `"INVALID_ZIP"` | `'INVALID_ZIP'` | -| `REQUIRED` | `"REQUIRED"` | `'REQUIRED'` | - ---- - - - -### ContractorAddressField - -> **ContractorAddressField** = keyof _typeof_ `fieldValidators` - -Field names accepted by the contractor address form. - ---- - - - -### ContractorAddressFields - -Pre-bound field components exposed on `useContractorAddressForm().form.Fields`. - -#### Properties - -| Property | Type | Description | -| --------- | ---------------------- | ------------------------------------------- | -| `City` | (`props`) => `Element` | City text input. Required. | -| `State` | (`props`) => `Element` | State selector. Required. | -| `Street1` | (`props`) => `Element` | Street address line 1 text input. Required. | -| `Street2` | (`props`) => `Element` | Street address line 2 text input. Optional. | -| `Zip` | (`props`) => `Element` | ZIP code text input. Required. | - ---- - - - -### ContractorAddressFieldsMetadata - -> **ContractorAddressFieldsMetadata** = [`UseContractorAddressFormReady`](#usecontractoraddressformready)\[`"form"`\]\[`"fieldsMetadata"`\] - -Type of `form.fieldsMetadata` returned by [useContractorAddressForm](#usecontractoraddressform). - ---- - - - -### ContractorAddressFormData - -> **ContractorAddressFormData** = `{ [K in keyof typeof fieldValidators]: z.infer }` - -Shape of the values managed by the contractor address form. - ---- - - - -### ContractorAddressFormFields - -> **ContractorAddressFormFields** = [`UseContractorAddressFormReady`](#usecontractoraddressformready)\[`"form"`\]\[`"Fields"`\] - -Type of `form.Fields` returned by [useContractorAddressForm](#usecontractoraddressform). - ---- - - - -### ContractorAddressFormOutputs - -> **ContractorAddressFormOutputs** = [`ContractorAddressFormData`](#contractoraddressformdata) - -Shape of the validated values produced by the contractor address form on -submit. - ---- - - - -### ContractorAddressOptionalFieldsToRequire - -> **ContractorAddressOptionalFieldsToRequire** = `OptionalFieldsToRequire`\<_typeof_ `requiredFieldsConfig`\> - -Keys of optional contractor address fields that can be promoted to required -via the hook's `optionalFieldsToRequire` option. - ---- - - - -### ContractorAddressRequiredValidation - -> **ContractorAddressRequiredValidation** = _typeof_ `ContractorAddressErrorCodes.REQUIRED` - -The required-field error code produced by [useContractorAddressForm](#usecontractoraddressform) fields that only emit `REQUIRED`. - -#### Remarks - -Used as the `validationMessages` key for the street, city, and state fields. -See [ContractorAddressErrorCodes](#contractoraddresserrorcodes). - ---- - - - -### ContractorAddressStateField - -Select bound to the `state` field of [useContractorAddressForm](#usecontractoraddressform). - -#### Parameters - -| Parameter | Type | Description | -| --------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | -| `props` | [`ContractorAddressStateFieldProps`](#contractoraddressstatefieldprops) | [StateFieldProps](#contractoraddressstatefieldprops) — accepts the standard hook field props plus `getOptionLabel` to localize state names. | - -#### Remarks - -Available on the hook result as `form.Fields.State`. Options are the -standard two-letter US state abbreviations. Required. - ---- - - - -### ContractorAddressStateFieldProps - -> **ContractorAddressStateFieldProps** = [`HookFieldProps`](../utilities.md#hookfieldprops)\<[`SelectHookFieldProps`](../utilities.md#selecthookfieldprops)\<[`ContractorAddressRequiredValidation`](#contractoraddressrequiredvalidation), `string`\>\> - -Props accepted by [useContractorAddressForm](#usecontractoraddressform)'s `Fields.State` component. - ---- - - - -### ContractorAddressStreet1Field - -Text input bound to the `street1` field of [useContractorAddressForm](#usecontractoraddressform). - -#### Parameters - -| Parameter | Type | Description | -| --------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `props` | [`ContractorAddressStreet1FieldProps`](#contractoraddressstreet1fieldprops) | [Street1FieldProps](#contractoraddressstreet1fieldprops) — accepts the standard hook field props (label, description, validationMessages, FieldComponent override). | - -#### Remarks - -Available on the hook result as `form.Fields.Street1`. Required. - ---- - - - -### ContractorAddressStreet1FieldProps - -> **ContractorAddressStreet1FieldProps** = [`HookFieldProps`](../utilities.md#hookfieldprops)\<[`TextInputHookFieldProps`](../utilities.md#textinputhookfieldprops)\<[`ContractorAddressRequiredValidation`](#contractoraddressrequiredvalidation)\>\> - -Props accepted by [useContractorAddressForm](#usecontractoraddressform)'s `Fields.Street1` component. - ---- - - - -### ContractorAddressStreet2Field - -Text input bound to the `street2` field of [useContractorAddressForm](#usecontractoraddressform). - -#### Parameters - -| Parameter | Type | Description | -| --------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `props` | [`ContractorAddressStreet2FieldProps`](#contractoraddressstreet2fieldprops) | [Street2FieldProps](#contractoraddressstreet2fieldprops) — accepts the standard hook field props (label, description, validationMessages, FieldComponent override). | - -#### Remarks - -Available on the hook result as `form.Fields.Street2`. Optional. - ---- - - - -### ContractorAddressStreet2FieldProps - -> **ContractorAddressStreet2FieldProps** = [`HookFieldProps`](../utilities.md#hookfieldprops)\<[`TextInputHookFieldProps`](../utilities.md#textinputhookfieldprops)\<[`ContractorAddressRequiredValidation`](#contractoraddressrequiredvalidation)\>\> - -Props accepted by [useContractorAddressForm](#usecontractoraddressform)'s `Fields.Street2` component. - ---- - - - -### ContractorAddressSubmitOptions - -Optional overrides passed to [onSubmit](#usecontractoraddressformready). - -#### Properties - -| Property | Type | Description | -| --------------- | -------- | -------------------------------------------------------- | -| `contractorId?` | `string` | Override the contractor identifier supplied to the hook. | - ---- - - - -### ContractorAddressZipField - -Text input bound to the `zip` field of [useContractorAddressForm](#usecontractoraddressform). - -#### Parameters - -| Parameter | Type | Description | -| --------- | ------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `props` | [`ContractorAddressZipFieldProps`](#contractoraddresszipfieldprops) | [ZipFieldProps](#contractoraddresszipfieldprops) — accepts the standard hook field props (label, description, validationMessages, FieldComponent override). | - -#### Remarks - -Available on the hook result as `form.Fields.Zip`. Required; also validates -ZIP code format and emits `INVALID_ZIP` when the value does not match. - ---- - - - -### ContractorAddressZipFieldProps - -> **ContractorAddressZipFieldProps** = [`HookFieldProps`](../utilities.md#hookfieldprops)\<[`TextInputHookFieldProps`](../utilities.md#textinputhookfieldprops)\<[`ContractorAddressZipValidation`](#contractoraddresszipvalidation)\>\> - -Props accepted by [useContractorAddressForm](#usecontractoraddressform)'s `Fields.Zip` component. - ---- - - - -### ContractorAddressZipValidation - -> **ContractorAddressZipValidation** = _typeof_ [`ContractorAddressErrorCodes`](#contractoraddresserrorcodes)\[`"REQUIRED"` \| `"INVALID_ZIP"`\] - -Validation error codes emitted by the `zip` field of [useContractorAddressForm](#usecontractoraddressform). - -#### Remarks - -Use these as keys in `validationMessages` on `Fields.Zip`. See -[ContractorAddressErrorCodes](#contractoraddresserrorcodes). - ---- - - - -### UseContractorAddressFormProps - -Configuration options for [useContractorAddressForm](#usecontractoraddressform). - -#### Properties - -| Property | Type | Description | -| -------------------------- | --------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `contractorId` | `string` | UUID of the contractor whose address is being edited. | -| `defaultValues?` | `Partial`\<[`ContractorAddressFormData`](#contractoraddressformdata)\> | Pre-fill form values. Server data takes precedence. | -| `optionalFieldsToRequire?` | [`ContractorAddressOptionalFieldsToRequire`](#contractoraddressoptionalfieldstorequire) | Override fields that are optional by default to be required. See `ContractorAddressOptionalFieldsToRequire`. | -| `shouldFocusError?` | `boolean` | Auto-focus the first invalid field on submit. Set to `false` when using `composeSubmitHandler` so submit-time focus is coordinated across multiple forms. Defaults to `true`. | -| `validationMode?` | `"onChange"` \| `"onBlur"` \| `"onSubmit"` \| `"onTouched"` \| `"all"` | Passed through to react-hook-form. Defaults to `'onSubmit'`. | - ---- - - - -### UseContractorAddressFormReady - -Ready-state shape returned by [useContractorAddressForm](#usecontractoraddressform) once data has loaded. - -#### Remarks - -Discriminated by `isLoading: false`. Extends [BaseFormHookReady](../utilities.md#baseformhookready) with -the contractor-address-specific `data`, `status`, `actions`, and `form.Fields` shape. - -#### Extends - -- [`BaseFormHookReady`](../utilities.md#baseformhookready)\<[`FieldsMetadata`](../utilities.md#fieldsmetadata), [`ContractorAddressFormData`](#contractoraddressformdata), [`ContractorAddressFields`](#contractoraddressfields)\> - -#### Properties - -| Property | Type | Description | -| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | -| `actions` | `object` | Available actions. | -| `actions.onSubmit` | (`options?`) => `Promise`\<[`HookSubmitResult`](../utilities.md#hooksubmitresult)\<`ContractorAddress`\> \| `undefined`\> | - | -| `data` | `object` | Static entity data resolved from the API. | -| `data.contractor` | `Contractor` | The full contractor entity loaded alongside the address. | -| `data.contractorAddress` | `ContractorAddress` | The contractor address row loaded for update. | -| `data.contractorType` | `ContractorType` \| `undefined` | The contractor's type — drives whether the address is labelled "home" (Individual) or "business" (Business). | -| `errorHandling` | [`HookErrorHandling`](../utilities.md#hookerrorhandling) | Error state and recovery actions. | -| `form` | `object` | Form bindings: pre-bound field components, per-field metadata, submission values, and react-hook-form internals. | -| `form.Fields` | [`ContractorAddressFields`](#contractoraddressfields) | - | -| `form.fieldsMetadata` | [`FieldsMetadata`](../utilities.md#fieldsmetadata) | - | -| `form.getFormSubmissionValues` | () => `Record`\<`string`, `unknown`\> \| `undefined` | - | -| `form.hookFormInternals` | [`HookFormInternals`](../utilities.md#hookforminternals)\<[`ContractorAddressFormData`](#contractoraddressformdata)\> | - | -| `isLoading` | `false` | Always `false` in this branch; discriminates from [HookLoadingResult](../utilities.md#hookloadingresult). | -| `status` | `object` | Reactive status flags. | -| `status.isPending` | `boolean` | - | -| `status.mode` | `"update"` | - | - ---- - - - -### UseContractorAddressFormResult - -> **UseContractorAddressFormResult** = [`HookLoadingResult`](../utilities.md#hookloadingresult) \| [`UseContractorAddressFormReady`](#usecontractoraddressformready) - -Discriminated union returned by [useContractorAddressForm](#usecontractoraddressform). diff --git a/package-lock.json b/package-lock.json index 742600545..1943b623f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -55,7 +55,7 @@ "@vitest/coverage-v8": "^4.1.9", "ajv": "^8.20.0", "axe-core": "^4.12.1", - "cspell": "^10.0.1", + "cspell": "^9.8.0", "dotenv": "^17.4.2", "eslint": "^9.39.4", "eslint-plugin-import": "^2.31.0", @@ -1132,9 +1132,9 @@ } }, "node_modules/@cspell/cspell-bundled-dicts": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@cspell/cspell-bundled-dicts/-/cspell-bundled-dicts-10.0.1.tgz", - "integrity": "sha512-WvkSDNX4Uyyj/ZgbPO6L38iFNMfK1EqsH1FteRiI2qLz6QZMXRFrIt12OqiWIplzZDDaVpBH9FCJOPJll0fjCQ==", + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/@cspell/cspell-bundled-dicts/-/cspell-bundled-dicts-9.8.0.tgz", + "integrity": "sha512-MpXFpVyBPfJQ1YuVotljqUaGf6lWuf+fuWBBgs0PHFYTSjRPWuIxviAaCDnup/CJLLH60xQL4IlcQe4TOjzljw==", "dev": true, "license": "MIT", "dependencies": { @@ -1199,86 +1199,86 @@ "@cspell/dict-zig": "^1.0.0" }, "engines": { - "node": ">=22.18.0" + "node": ">=20" } }, "node_modules/@cspell/cspell-json-reporter": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@cspell/cspell-json-reporter/-/cspell-json-reporter-10.0.1.tgz", - "integrity": "sha512-/nes1RGILec3WCBcoMOd0byNTBtnJuPaVz/+ZzqYkLtY7x58VMcBG5kyP6hPyN8cIwjRADE/SR43gwdXuqk/FA==", + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/@cspell/cspell-json-reporter/-/cspell-json-reporter-9.8.0.tgz", + "integrity": "sha512-nqUaSo9T7l8KrE22gc7ZIs+zvP7ak1i7JqGdRs8sGvh2Ijqj43qYQLePgb1b/vm8a1bavnc51m+vf05hpd3g3Q==", "dev": true, "license": "MIT", "dependencies": { - "@cspell/cspell-types": "10.0.1" + "@cspell/cspell-types": "9.8.0" }, "engines": { - "node": ">=22.18.0" + "node": ">=20" } }, "node_modules/@cspell/cspell-performance-monitor": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@cspell/cspell-performance-monitor/-/cspell-performance-monitor-10.0.1.tgz", - "integrity": "sha512-9tVcHXwRnbazUv4WSG0h3MqV4+LgmLNgSALAQUflPPW0EMxTf7C4Dmv9cgxJyCEQrdnVKCr58nPPaahhz9LJUg==", + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/@cspell/cspell-performance-monitor/-/cspell-performance-monitor-9.8.0.tgz", + "integrity": "sha512-IsrXYzn23yJICIQ915ACdf+2lNEcFNTu5BIQt3khHOsGVvZ9/AZYpu9Dk825vUyZG7RHg2Oi6dYNiJtULG4ouQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=22.18.0" + "node": ">=20.18" } }, "node_modules/@cspell/cspell-pipe": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@cspell/cspell-pipe/-/cspell-pipe-10.0.1.tgz", - "integrity": "sha512-HPeXMD9AZ3V/qPkvQaPcak+C7cJ2z7JTHN8smd6J8L2aThLRky2cHc2OyeaHPSHB7WA47b4z2n5u5nawZhv5VQ==", + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/@cspell/cspell-pipe/-/cspell-pipe-9.8.0.tgz", + "integrity": "sha512-ISEUD8PHYkd2Ktafc6hFfIXdGKYUvthA09NbwwZsWmOqYyk4wWKHZKqyyxD+BcrFwOyMOJcD8OEvIjkRQp2SJw==", "dev": true, "license": "MIT", "engines": { - "node": ">=22.18.0" + "node": ">=20" } }, "node_modules/@cspell/cspell-resolver": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@cspell/cspell-resolver/-/cspell-resolver-10.0.1.tgz", - "integrity": "sha512-PIzkZHD1fGUQx1XteK2d1iQ0Mzq/maYcoB4jkvAiiR6WqP3MWYNKFdI9z+R5pOq5KgMfW+5Ig1q0oSR6h8irlA==", + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/@cspell/cspell-resolver/-/cspell-resolver-9.8.0.tgz", + "integrity": "sha512-PZJj56BZpKfMxOzWkyt7b+aIXObe+8Ku/zLI4xDXPSuQPENbHBFHfPIZx68CyGEkanKxZ1ewKVx/FT1FUy+wDA==", "dev": true, "license": "MIT", "dependencies": { "global-directory": "^5.0.0" }, "engines": { - "node": ">=22.18.0" + "node": ">=20" } }, "node_modules/@cspell/cspell-service-bus": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@cspell/cspell-service-bus/-/cspell-service-bus-10.0.1.tgz", - "integrity": "sha512-y6NcIGP2IdXaBL4PVH8vxsr7K27wzz3Ech87UtUtrDSXAiVEOvXgAIknEOUVp59rTlUE8Rn4IRURC6f/hgMyfw==", + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/@cspell/cspell-service-bus/-/cspell-service-bus-9.8.0.tgz", + "integrity": "sha512-P45sd2nqwcqhulBBbQnZB/JNcobecTrP4Ky3vmEq0cprsvavc+ZoHF9U2Ql5ghMSUzjrF2n1aNzZ8cH4IlsnKg==", "dev": true, "license": "MIT", "engines": { - "node": ">=22.18.0" + "node": ">=20" } }, "node_modules/@cspell/cspell-types": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@cspell/cspell-types/-/cspell-types-10.0.1.tgz", - "integrity": "sha512-kLgLShnWADDVreKC63pBrWkcvxgZzFIfO34Jhx/SWfuOIA3cD8AXT+HjyuLfoGJ7mUb58hv2kUziKzEy4INb1w==", + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/@cspell/cspell-types/-/cspell-types-9.8.0.tgz", + "integrity": "sha512-7Ge4UD6SCA49Tcc3+GTlz3Xn4cqVUAXtDO0u9IeHvJgkN3Me2Rw2GB/CtGmhKST3YeEeZMX7ww09TdHMUJlehw==", "dev": true, "license": "MIT", "engines": { - "node": ">=22.18.0" + "node": ">=20" } }, "node_modules/@cspell/cspell-worker": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@cspell/cspell-worker/-/cspell-worker-10.0.1.tgz", - "integrity": "sha512-L2bJerfuYOls2wEknm8FmynLtj/G7O4UqX9I/HznRggEW6i2yZIxagDetpVDNowpyavNHJ3SJtUFiyMiZc16Sw==", + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/@cspell/cspell-worker/-/cspell-worker-9.8.0.tgz", + "integrity": "sha512-W8FLdE3MXPLbWtAXciILQhk9CHd6Mt+HRjZHM8m+dwE1Bc2TAjUai8kIxsdhHUq58p7gYY2ekr5sg1uYOUgTAA==", "dev": true, "license": "MIT", "dependencies": { - "cspell-lib": "10.0.1" + "cspell-lib": "9.8.0" }, "engines": { - "node": ">=22.18.0" + "node": ">=20.18" } }, "node_modules/@cspell/dict-ada": { @@ -1707,57 +1707,57 @@ "license": "MIT" }, "node_modules/@cspell/dynamic-import": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@cspell/dynamic-import/-/dynamic-import-10.0.1.tgz", - "integrity": "sha512-mP1gdq00aIcH8HxNMqnH11X6BKxLcneDtFgl/ecjIKnaGKwi44m8AndP5Kr4ODaYdl8UUw9O3dJh7KaQXnLHZQ==", + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/@cspell/dynamic-import/-/dynamic-import-9.8.0.tgz", + "integrity": "sha512-wMgb32lqG9g6lCipUQsY9Bk5idXPDz7wvzOqEsU1M2HmNYmdE1wfPoRpfQfsVL965iG3+6h8QLr2+8FKpweFEQ==", "dev": true, "license": "MIT", "dependencies": { - "@cspell/url": "10.0.1", + "@cspell/url": "9.8.0", "import-meta-resolve": "^4.2.0" }, "engines": { - "node": ">=22.18.0" + "node": ">=20" } }, "node_modules/@cspell/filetypes": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@cspell/filetypes/-/filetypes-10.0.1.tgz", - "integrity": "sha512-Z5S35giU5IW49fBBq6BksUbE8PC4IYPfaKuwl5Nl9jkf/OkAKiBmCowKX45NzRUQInwK/GSqqIUifrNeI6LdLw==", + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/@cspell/filetypes/-/filetypes-9.8.0.tgz", + "integrity": "sha512-yHvtYn9qt6zykua77sNzTcf7HrG/dpo/+2pCMGSrfSrQypSNT6FUFvMS04W7kwhP86U1GkCjppNykXuoH3cqug==", "dev": true, "license": "MIT", "engines": { - "node": ">=22.18.0" + "node": ">=20" } }, "node_modules/@cspell/rpc": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@cspell/rpc/-/rpc-10.0.1.tgz", - "integrity": "sha512-axSRKv3zEAmBm66iD/FV/MPmE4/Yf7c3PZiwTW894Yd3iEhtn3KPKeTrqQ2/tDrhB1Z2qTsap/Hue0MK4o5WXg==", + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/@cspell/rpc/-/rpc-9.8.0.tgz", + "integrity": "sha512-t4lHEa254W+PePXNQ1noW7QhQxz/mhsJ9X8LEt0ILzBbPWCJzN+JuaM7EiolIPiwxtfxpMwKx9482kt4eTja7A==", "dev": true, "license": "MIT", "engines": { - "node": ">=22.18.0" + "node": ">=20.18" } }, "node_modules/@cspell/strong-weak-map": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@cspell/strong-weak-map/-/strong-weak-map-10.0.1.tgz", - "integrity": "sha512-lenN1DVyPi8nJLSMSJJ670ddTjyiruLueuSZO1qLcxBqUhgxDt/mALu9N/1m6WdOVcg6m/5cLiZVg2KOo2UzRw==", + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/@cspell/strong-weak-map/-/strong-weak-map-9.8.0.tgz", + "integrity": "sha512-HocksAqZ0JcWA5oWO7TIlOCftXVGkPGzbeFlCRRrjJpZmYQH+4NdeEXyQC6T89NGocp45td/CgyBcAaFMy1N9w==", "dev": true, "license": "MIT", "engines": { - "node": ">=22.18.0" + "node": ">=20" } }, "node_modules/@cspell/url": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@cspell/url/-/url-10.0.1.tgz", - "integrity": "sha512-abYYgI29wJhWIfWTYrYuzRYDcHQUQ1N5ylnhxYn1NJnIQMqUWGLbDmt12JABtZ+R6h6UNatQrS7rhP86etvJyQ==", + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/@cspell/url/-/url-9.8.0.tgz", + "integrity": "sha512-LY1lFiZLTQF/ma1ilfKmRmFmEOw0RfYhyl0UMhY7/d93b+kiDMhxP/9Qir4+5LyiRncaE3++ZcWno9Hya+ssRg==", "dev": true, "license": "MIT", "engines": { - "node": ">=22.18.0" + "node": ">=20" } }, "node_modules/@csstools/color-helpers": { @@ -12241,6 +12241,36 @@ "node": ">=6" } }, + "node_modules/clear-module": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/clear-module/-/clear-module-4.1.3.tgz", + "integrity": "sha512-XdLrg7BnbXKntyrbs2dNjDN9CVoTQ+WV0i7jT5/r9ahzAaSDSzC9e2OVZB/QVwbxBb1/1AeObzjlxsYk5HFvww==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^2.0.0", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clear-module/node_modules/parent-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-2.0.0.tgz", + "integrity": "sha512-uo0Z9JJeWzv8BG+tRcapBKNJ0dro9cLyczGzulS6EfeyAdeC9sbojtW6XwvYxJkEne9En+J2XEl4zyglVeIwFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/cli-cursor": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", @@ -12507,9 +12537,9 @@ } }, "node_modules/comment-json": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-5.0.0.tgz", - "integrity": "sha512-uiqLcOiVDJtBP8WGkZHEP+FZIhTzP1dxvn59EfoYUi9gqupjrBWVQkO2atDrbnKPwLeotFYDsuNb26uBMqB+hw==", + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-4.6.2.tgz", + "integrity": "sha512-R2rze/hDX30uul4NZoIZ76ImSJLFxn/1/ZxtKC1L77y2X1k+yYu1joKbAtMA2Fg3hZrTOiw0I5mwVMo0cf250w==", "dev": true, "license": "MIT", "dependencies": { @@ -12843,174 +12873,175 @@ } }, "node_modules/cspell": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/cspell/-/cspell-10.0.1.tgz", - "integrity": "sha512-Gg6w/flT3fKfl3la62hfTnhtNnDQ+9mU7kUhVqw/axl/Ms4oENw0oJMkWFIoj4f6nL/SDPz7KcPXd2XbkKFNmQ==", + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/cspell/-/cspell-9.8.0.tgz", + "integrity": "sha512-qL0VErMSn8BDxaPxcV+9uenffgjPS+5Jfz+m4rCsvYjzLwr7AaaJBWWSV2UiAe/4cturae8n8qzxiGnbbazkRw==", "dev": true, "license": "MIT", "dependencies": { - "@cspell/cspell-json-reporter": "10.0.1", - "@cspell/cspell-performance-monitor": "10.0.1", - "@cspell/cspell-pipe": "10.0.1", - "@cspell/cspell-types": "10.0.1", - "@cspell/cspell-worker": "10.0.1", - "@cspell/dynamic-import": "10.0.1", - "@cspell/url": "10.0.1", + "@cspell/cspell-json-reporter": "9.8.0", + "@cspell/cspell-performance-monitor": "9.8.0", + "@cspell/cspell-pipe": "9.8.0", + "@cspell/cspell-types": "9.8.0", + "@cspell/cspell-worker": "9.8.0", + "@cspell/dynamic-import": "9.8.0", + "@cspell/url": "9.8.0", "ansi-regex": "^6.2.2", "chalk": "^5.6.2", "chalk-template": "^1.1.2", "commander": "^14.0.3", - "cspell-config-lib": "10.0.1", - "cspell-dictionary": "10.0.1", - "cspell-gitignore": "10.0.1", - "cspell-glob": "10.0.1", - "cspell-io": "10.0.1", - "cspell-lib": "10.0.1", + "cspell-config-lib": "9.8.0", + "cspell-dictionary": "9.8.0", + "cspell-gitignore": "9.8.0", + "cspell-glob": "9.8.0", + "cspell-io": "9.8.0", + "cspell-lib": "9.8.0", "fast-json-stable-stringify": "^2.1.0", "flatted": "^3.4.2", - "semver": "^7.8.1", - "tinyglobby": "^0.2.16" + "semver": "^7.7.4", + "tinyglobby": "^0.2.15" }, "bin": { "cspell": "bin.mjs", "cspell-esm": "bin.mjs" }, "engines": { - "node": ">=22.18.0" + "node": ">=20.18" }, "funding": { "url": "https://github.com/streetsidesoftware/cspell?sponsor=1" } }, "node_modules/cspell-config-lib": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/cspell-config-lib/-/cspell-config-lib-10.0.1.tgz", - "integrity": "sha512-hMpo/0j6k7pbiqrLDOLJKD2IGP9XwhjKf2miiM6p84Xeo4nyuFZaxxDCQ68R851HSYFrrdltgpoipMbj1h2Tnw==", + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/cspell-config-lib/-/cspell-config-lib-9.8.0.tgz", + "integrity": "sha512-gMJBAgYPvvO+uDFLUcGWaTu6/e+r8mm4GD4rQfWa/yV4F9fj+yOYLIMZqLWRvT1moHZX1FxyVvUbJcmZ1gfebg==", "dev": true, "license": "MIT", "dependencies": { - "@cspell/cspell-types": "10.0.1", - "comment-json": "^5.0.0", + "@cspell/cspell-types": "9.8.0", + "comment-json": "^4.6.2", "smol-toml": "^1.6.1", - "yaml": "^2.9.0" + "yaml": "^2.8.3" }, "engines": { - "node": ">=22.18.0" + "node": ">=20" } }, "node_modules/cspell-dictionary": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/cspell-dictionary/-/cspell-dictionary-10.0.1.tgz", - "integrity": "sha512-3cZ659vgsZWkzGQJR/sNqGDVt/OnvTSieLKI76V++4t1bHJfochb9ZrrwsuMsb1VPGiyqClUP1/O6WrefF/FVg==", + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/cspell-dictionary/-/cspell-dictionary-9.8.0.tgz", + "integrity": "sha512-QW4hdkWcrxZA1QNqi26U0S/U3/V+tKCm7JaaesEJW2F6Ao+23AbHVwidyAVtXaEhGkn6PxB+epKrrAa6nE69qA==", "dev": true, "license": "MIT", "dependencies": { - "@cspell/cspell-performance-monitor": "10.0.1", - "@cspell/cspell-pipe": "10.0.1", - "@cspell/cspell-types": "10.0.1", - "cspell-trie-lib": "10.0.1", + "@cspell/cspell-performance-monitor": "9.8.0", + "@cspell/cspell-pipe": "9.8.0", + "@cspell/cspell-types": "9.8.0", + "cspell-trie-lib": "9.8.0", "fast-equals": "^6.0.0" }, "engines": { - "node": ">=22.18.0" + "node": ">=20" } }, "node_modules/cspell-gitignore": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/cspell-gitignore/-/cspell-gitignore-10.0.1.tgz", - "integrity": "sha512-wN23U61Mx6qPJN3CesOmBU9vnbJ0jQm/ylK0iaVui3CcnO7Zzl5qLu5mPHUzGQGm8yso6qjyxqo16Ho7LpZGOQ==", + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/cspell-gitignore/-/cspell-gitignore-9.8.0.tgz", + "integrity": "sha512-SDUa1DmSfT20+JH7XtyzcEL9KfurneoR/XbmlrtPQZP/LUHXh3yz4x/0vFIkEFXNWdSckY0QdWTz8DaxClCf4Q==", "dev": true, "license": "MIT", "dependencies": { - "@cspell/url": "10.0.1", - "cspell-glob": "10.0.1", - "cspell-io": "10.0.1" + "@cspell/url": "9.8.0", + "cspell-glob": "9.8.0", + "cspell-io": "9.8.0" }, "bin": { "cspell-gitignore": "bin.mjs" }, "engines": { - "node": ">=22.18.0" + "node": ">=20" } }, "node_modules/cspell-glob": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/cspell-glob/-/cspell-glob-10.0.1.tgz", - "integrity": "sha512-7bII9J3aSSpZDwhx7w+zfQXbMxHZQ3be0ilUp5bHrsjz6o07v/NqOHMGcwKdPn1sw2dxDz9sv057xE5pqXnSdw==", + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/cspell-glob/-/cspell-glob-9.8.0.tgz", + "integrity": "sha512-Uvj/iHXs+jpsJyIEnhEoJTWXb1GVyZ9T05L5JFtZfsQNXrh8SRDQPscjxbg4okKr63N7WevfioQum/snHNYvmw==", "dev": true, "license": "MIT", "dependencies": { - "@cspell/url": "10.0.1", + "@cspell/url": "9.8.0", "picomatch": "^4.0.4" }, "engines": { - "node": ">=22.18.0" + "node": ">=20" } }, "node_modules/cspell-grammar": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/cspell-grammar/-/cspell-grammar-10.0.1.tgz", - "integrity": "sha512-xC9AFYmaI9wsO//a7S5tdDGKGJVD5UEEsTg+Up2fi7lPfXIryisYmV6tePNL1SEg0idYss4ja8LUZ3Mib09BjQ==", + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/cspell-grammar/-/cspell-grammar-9.8.0.tgz", + "integrity": "sha512-01XMq2vhPS0Gvxnfed9uvOwH+3cXddHYxW0PwCE+SZdcC6TN8yM6glByuLt1qFustAmQVE5GSr7uAY9o4pZQRg==", "dev": true, "license": "MIT", "dependencies": { - "@cspell/cspell-pipe": "10.0.1", - "@cspell/cspell-types": "10.0.1" + "@cspell/cspell-pipe": "9.8.0", + "@cspell/cspell-types": "9.8.0" }, "bin": { "cspell-grammar": "bin.mjs" }, "engines": { - "node": ">=22.18.0" + "node": ">=20" } }, "node_modules/cspell-io": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/cspell-io/-/cspell-io-10.0.1.tgz", - "integrity": "sha512-8C2ka07faxflnaqEBO3pektS21XViE/SEHT7F5ZD1ou7FyMR5u3xawTBJSczClfsxLt/WYeztBYrpmGAjmjksw==", + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/cspell-io/-/cspell-io-9.8.0.tgz", + "integrity": "sha512-JINaEWQEzR4f2upwdZOFcft+nBvQgizJfrOLszxG3p+BIzljnGklqE/nUtLFZpBu0oMJvuM/Fd+GsWor0yP7Xw==", "dev": true, "license": "MIT", "dependencies": { - "@cspell/cspell-service-bus": "10.0.1", - "@cspell/url": "10.0.1" + "@cspell/cspell-service-bus": "9.8.0", + "@cspell/url": "9.8.0" }, "engines": { - "node": ">=22.18.0" + "node": ">=20" } }, "node_modules/cspell-lib": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/cspell-lib/-/cspell-lib-10.0.1.tgz", - "integrity": "sha512-RpsIPiLzc4/YMW8BMRKpyJ81x439qjYWcqgdKeXnMkbKM88J9PexzutfFf/4v97v96KzfNitEzMpbI0uj8OeUg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@cspell/cspell-bundled-dicts": "10.0.1", - "@cspell/cspell-performance-monitor": "10.0.1", - "@cspell/cspell-pipe": "10.0.1", - "@cspell/cspell-resolver": "10.0.1", - "@cspell/cspell-types": "10.0.1", - "@cspell/dynamic-import": "10.0.1", - "@cspell/filetypes": "10.0.1", - "@cspell/rpc": "10.0.1", - "@cspell/strong-weak-map": "10.0.1", - "@cspell/url": "10.0.1", - "cspell-config-lib": "10.0.1", - "cspell-dictionary": "10.0.1", - "cspell-glob": "10.0.1", - "cspell-grammar": "10.0.1", - "cspell-io": "10.0.1", - "cspell-trie-lib": "10.0.1", + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/cspell-lib/-/cspell-lib-9.8.0.tgz", + "integrity": "sha512-G2TtPcye5QE5ev3YgWq42UOJLpTZ6naO/47oIm+jmeSYbgnbcOSThnEE7uMycx+TTNOz/vJVFpZmQyt0bWCftw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/cspell-bundled-dicts": "9.8.0", + "@cspell/cspell-performance-monitor": "9.8.0", + "@cspell/cspell-pipe": "9.8.0", + "@cspell/cspell-resolver": "9.8.0", + "@cspell/cspell-types": "9.8.0", + "@cspell/dynamic-import": "9.8.0", + "@cspell/filetypes": "9.8.0", + "@cspell/rpc": "9.8.0", + "@cspell/strong-weak-map": "9.8.0", + "@cspell/url": "9.8.0", + "clear-module": "^4.1.2", + "cspell-config-lib": "9.8.0", + "cspell-dictionary": "9.8.0", + "cspell-glob": "9.8.0", + "cspell-grammar": "9.8.0", + "cspell-io": "9.8.0", + "cspell-trie-lib": "9.8.0", "env-paths": "^4.0.0", "gensequence": "^8.0.8", - "import-fresh": "^4.0.0", + "import-fresh": "^3.3.1", "resolve-from": "^5.0.0", "vscode-languageserver-textdocument": "^1.0.12", "vscode-uri": "^3.1.0", "xdg-basedir": "^5.1.0" }, "engines": { - "node": ">=22.18.0" + "node": ">=20" } }, "node_modules/cspell-lib/node_modules/env-paths": { @@ -13029,30 +13060,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cspell-lib/node_modules/import-fresh": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-4.0.0.tgz", - "integrity": "sha512-Fpi660c7VPDM3fPKYovStd9IP1CPOikf6v/dGxJJMmHPcwYQIMJ4W7kO1avBYEpMqkCh+Dx3Ln6H7VYqgztLjw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=22.15" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/cspell-trie-lib": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/cspell-trie-lib/-/cspell-trie-lib-10.0.1.tgz", - "integrity": "sha512-BFvhalSkRQFjKrZ//FKK7fRGrZFpifnxB5AwCkzsIsBZqicsfafcQ1xP21qpb0QqyV/IomjNgviG+tRJs+0rMw==", + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/cspell-trie-lib/-/cspell-trie-lib-9.8.0.tgz", + "integrity": "sha512-GXIyqxya8QLp6SjKsAN9w3apvt1Ww7GKcZvTBaP76OfLoyb1QC6unwmObY2cZs1manCntGwHrgU6vFNuXnTzpw==", "dev": true, "license": "MIT", "engines": { - "node": ">=22.18.0" + "node": ">=20" }, "peerDependencies": { - "@cspell/cspell-types": "10.0.1" + "@cspell/cspell-types": "9.8.0" } }, "node_modules/cspell/node_modules/ansi-regex": { diff --git a/package.json b/package.json index b44fe612b..ea781020e 100644 --- a/package.json +++ b/package.json @@ -118,7 +118,7 @@ "@vitest/coverage-v8": "^4.1.9", "ajv": "^8.20.0", "axe-core": "^4.12.1", - "cspell": "^10.0.1", + "cspell": "^9.8.0", "dotenv": "^17.4.2", "eslint": "^9.39.4", "eslint-plugin-import": "^2.31.0",