Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 39 additions & 9 deletions docs/workflows-overview/employee-onboarding/employee-onboarding.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ Employee onboarding components can be used to compose your own workflow, or can
- Employee.EmployeeList
- Employee.Profile
- Employee.Compensation
- Employee.Taxes
- Employee.FederalTaxes
- Employee.StateTaxes
- Employee.PaymentMethod
- Employee Deductions
- Employee.OnboardingSummary
Expand Down Expand Up @@ -167,15 +168,45 @@ function MyComponent() {
| EMPLOYEE_COMPENSATION_UPDATED | Fired after updating compensation details | Response from the Update a compensation endpoint |
| EMPLOYEE_COMPENSATION_DONE | Fired when compensation setup is complete and we are ready to advance to the next step | None |

### Employee.Taxes
### Employee.FederalTaxes

Provides required form inputs for employee state and federal tax configuration.
Provides required form inputs for employee federal tax configuration.

```jsx
import { Employee } from '@gusto/embedded-react-sdk'

function MyComponent() {
return <Employee.Taxes employeeId="4b3f930f-82cd-48a8-b797-798686e12e5e" onEvent={() => {}} />
return (
<Employee.FederalTaxes employeeId="4b3f930f-82cd-48a8-b797-798686e12e5e" onEvent={() => {}} />
)
}
```

#### Props

| Name | Type | Description |
| ------------------- | ------ | -------------------------------------- |
| employeeId Required | string | The associated employee identifier. |
| onEvent Required | | See events table for available events. |

#### Events

| Event type | Description | Data |
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- |
| EMPLOYEE_FEDERAL_TAXES_UPDATED | Fired when the employee federal taxes form is submitted and federal taxes are successfully updated | Response from the Update federal taxes endpoint |
| EMPLOYEE_FEDERAL_TAXES_DONE | Fired when the employee federal taxes form is successfully submitted, API request is completed, and we are ready to advance to the next step | None |

### Employee.StateTaxes

Provides required form inputs for employee state tax configuration.

```jsx
import { Employee } from '@gusto/embedded-react-sdk'

function MyComponent() {
return (
<Employee.StateTaxes employeeId="4b3f930f-82cd-48a8-b797-798686e12e5e" onEvent={() => {}} />
)
}
```

Expand All @@ -189,11 +220,10 @@ function MyComponent() {

#### Events

| Event type | Description | Data | Description |
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| EMPLOYEE_FEDERAL_TAXES_UPDATED | Fired when the employee taxes form is submitted and federal taxes are successfully updated | Response from the Update federal taxes endpoint | The associated employee identifier. |
| EMPLOYEE_STATE_TAXES_UPDATED | Fired when the employee taxes form is submitted and state taxes are successfully updated | Response from the Update state taxes endpoint | See events table for available events. |
| EMPLOYEE_TAXES_DONE | Fired when the employee taxes form is successfully submitted, above API requests are completed, and we are ready to advance to the next step | None | If the onboarding is being performed by an admin. When false it is configured to be self onboarding. |
| Event type | Description | Data |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------- |
| EMPLOYEE_STATE_TAXES_UPDATED | Fired when the employee state taxes form is submitted and state taxes are successfully updated | Response from the Update state taxes endpoint |
| EMPLOYEE_STATE_TAXES_DONE | Fired when the employee state taxes form is successfully submitted, API request is completed, and we are ready to advance to the next step | None |

### Employee.PaymentMethod

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ In the case an employer elects to allow the employee to self-onboard, they can b
### Implementation

```jsx
import { EmployeeSelfOnboardingFlow } from '@gusto/embedded-react-sdk'
import { Employee } from '@gusto/embedded-react-sdk'

function MyApp() {
return (
<EmployeeSelfOnboardingFlow
<Employee.SelfOnboardingFlow
companyId="a007e1ab-3595-43c2-ab4b-af7a5af2e365"
employeeId="4b3f930f-82cd-48a8-b797-798686e12e5e"
onEvent={() => {}}
Expand Down Expand Up @@ -95,19 +95,35 @@ function MyApp() {
}
```

### Employee.Taxes
### Employee.FederalTaxes

_See component documentation in the Employee Onboarding section for a complete list of props and events since this component is used in both employee onboarding and employee self onboarding._

Provides required form inputs for employee state and federal tax configuration.
Provides required form inputs for employee federal tax configuration.

The `isAdmin` property should be left out or set to false (which is the setting by default). The following example has the Taxes component configured for self onboarding:
```jsx
import { Employee } from '@gusto/embedded-react-sdk'

function MyComponent() {
return (
<Employee.FederalTaxes employeeId="4b3f930f-82cd-48a8-b797-798686e12e5e" onEvent={() => {}} />
)
}
```

### Employee.StateTaxes

_See component documentation in the Employee Onboarding section for a complete list of props and events since this component is used in both employee onboarding and employee self onboarding._

Provides required form inputs for employee state tax configuration.

```jsx
import { Employee } from '@gusto/embedded-react-sdk'

function MyComponent() {
return <Employee.Taxes employeeId="4b3f930f-82cd-48a8-b797-798686e12e5e" onEvent={() => {}} />
return (
<Employee.StateTaxes employeeId="4b3f930f-82cd-48a8-b797-798686e12e5e" onEvent={() => {}} />
)
}
```

Expand Down
2 changes: 1 addition & 1 deletion src/components/Employee/Compensation/Compensation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { type Job } from '@gusto/embedded-api/models/components/job'
import type { FlsaStatusType } from '@gusto/embedded-api/models/components/flsastatustype'
import { useFederalTaxDetailsGetSuspense } from '@gusto/embedded-api/react-query/federalTaxDetailsGet'
import { useEmployeesGetSuspense } from '@gusto/embedded-api/react-query/employeesGet'
import type { OnboardingContextInterface } from '../OnboardingFlow/OnboardingFlow'
import type { OnboardingContextInterface } from '../OnboardingFlow/OnboardingFlowComponents'
import { List } from './List'
import { Head } from './Head'
import { Edit } from './Edit'
Expand Down
2 changes: 1 addition & 1 deletion src/components/Employee/Deductions/Deductions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useTranslation } from 'react-i18next'
import { createMachine } from 'robot3'
import { useGarnishmentsListSuspense } from '@gusto/embedded-api/react-query/garnishmentsList'
import { useMemo } from 'react'
import type { OnboardingContextInterface } from '../OnboardingFlow/OnboardingFlow'
import type { OnboardingContextInterface } from '../OnboardingFlow/OnboardingFlowComponents'
import {
IncludeDeductionsFormContextual,
type DeductionsContextInterface,
Expand Down
2 changes: 1 addition & 1 deletion src/components/Employee/EmployeeList/EmployeeList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useEmployeesListSuspense } from '@gusto/embedded-api/react-query/employ
import type { OnboardingStatus } from '@gusto/embedded-api/models/operations/putv1employeesemployeeidonboardingstatus'
import { useEmployeesDeleteMutation } from '@gusto/embedded-api/react-query/employeesDelete'
import { useEmployeesUpdateOnboardingStatusMutation } from '@gusto/embedded-api/react-query/employeesUpdateOnboardingStatus'
import type { OnboardingContextInterface } from '../OnboardingFlow/OnboardingFlow'
import type { OnboardingContextInterface } from '../OnboardingFlow/OnboardingFlowComponents'
import { EmployeeListProvider } from './useEmployeeList'
import { Actions } from './Actions'
import {
Expand Down
18 changes: 18 additions & 0 deletions src/components/Employee/FederalTaxes/Actions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useTranslation } from 'react-i18next'
import { useFederalTaxes } from './useFederalTaxes'
import { ActionsLayout } from '@/components/Common'
import { useComponentContext } from '@/contexts/ComponentAdapter/useComponentContext'

export function Actions() {
const { t } = useTranslation('Employee.FederalTaxes')
const { isPending } = useFederalTaxes()
const Components = useComponentContext()

return (
<ActionsLayout>
<Components.Button type="submit" isLoading={isPending}>
{t('submitCta')}
</Components.Button>
</ActionsLayout>
)
}
92 changes: 92 additions & 0 deletions src/components/Employee/FederalTaxes/FederalForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { Trans, useTranslation } from 'react-i18next'
import { z } from 'zod'
import { SelectField, RadioGroupField, NumberInputField } from '@/components/Common'
import { useComponentContext } from '@/contexts/ComponentAdapter/useComponentContext'

export const FederalFormSchema = z.object({
filingStatus: z.string().min(1),
twoJobs: z.string().min(1),
dependentsAmount: z.number().transform(String),
otherIncome: z.number().transform(String),
deductions: z.number().transform(String),
extraWithholding: z.number().transform(String),
w4DataType: z.enum(['pre_2020_w4', 'rev_2020_w4']),
})

export type FederalFormInputs = z.input<typeof FederalFormSchema>
export type FederalFormPayload = z.output<typeof FederalFormSchema>

export function FederalForm() {
const { t } = useTranslation('Employee.FederalTaxes')
const Components = useComponentContext()

const filingStatusCategories = [
{ value: 'Single', label: t('filingStatusSingle') },
{ value: 'Married', label: t('filingStatusMarried') },
{ value: 'Head of Household', label: t('filingStatusHeadOfHousehold') },
{ value: 'Exempt from withholding', label: t('filingStatusExemptFromWithholding') },
]

return (
<>
<SelectField
name="filingStatus"
label={t('federalFilingStatus1c')}
placeholder={t('federalFillingStatusPlaceholder')}
description={t('selectWithholdingDescription')}
options={filingStatusCategories}
isRequired
errorMessage={t('validations.federalFilingStatus')}
/>
<RadioGroupField
name="twoJobs"
isRequired
label={t('multipleJobs2c')}
errorMessage={t('validations.federalTwoJobs')}
description={
<Trans
i18nKey={'includesSpouseExplanation'}
t={t}
components={{
irs_link: <Components.Link />,
}}
/>
}
options={[
{ value: 'true', label: t('twoJobYesLabel') },
{ value: 'false', label: t('twoJobNoLabel') },
]}
/>
<NumberInputField
name="dependentsAmount"
isRequired
label={t('dependentsTotalIfApplicable')}
errorMessage={t('fieldIsRequired')}
/>
<NumberInputField
name="otherIncome"
isRequired
label={t('otherIncome')}
format="currency"
min={0}
errorMessage={t('fieldIsRequired')}
/>
<NumberInputField
name="deductions"
isRequired
label={t('deductions')}
format="currency"
min={0}
errorMessage={t('fieldIsRequired')}
/>
<NumberInputField
name="extraWithholding"
isRequired
label={t('extraWithholding')}
format="currency"
min={0}
errorMessage={t('fieldIsRequired')}
/>
</>
)
}
117 changes: 117 additions & 0 deletions src/components/Employee/FederalTaxes/FederalTaxes.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { zodResolver } from '@hookform/resolvers/zod'
import { FormProvider, useForm, type SubmitHandler } from 'react-hook-form'
import { useEffect } from 'react'
import { useEmployeeTaxSetupGetFederalTaxesSuspense } from '@gusto/embedded-api/react-query/employeeTaxSetupGetFederalTaxes'
import { useEmployeeTaxSetupUpdateFederalTaxesMutation } from '@gusto/embedded-api/react-query/employeeTaxSetupUpdateFederalTaxes'
import { FederalForm } from './FederalForm'
import { FederalFormSchema, type FederalFormInputs, type FederalFormPayload } from './FederalForm'
import { Head } from './Head'
import { Actions } from './Actions'
import { FederalTaxesProvider } from './useFederalTaxes'
import {
useBase,
BaseComponent,
type BaseComponentInterface,
type CommonComponentInterface,
} from '@/components/Base'
import { useI18n } from '@/i18n'
import { componentEvents } from '@/shared/constants'
import { Form } from '@/components/Common/Form'
import { useComponentDictionary } from '@/i18n/I18n'

interface FederalTaxesProps extends CommonComponentInterface<'Employee.FederalTaxes'> {
employeeId: string
}

export function FederalTaxes(props: FederalTaxesProps & BaseComponentInterface) {
return (
<BaseComponent<'Employee.FederalTaxes'> {...props}>
<Root {...props} />
</BaseComponent>
)
}

const Root = (props: FederalTaxesProps) => {
const { employeeId, className, children, dictionary } = props
const { onEvent, fieldErrors, baseSubmitHandler } = useBase()
useI18n('Employee.FederalTaxes')
useComponentDictionary('Employee.FederalTaxes', dictionary)

const { data: fedData } = useEmployeeTaxSetupGetFederalTaxesSuspense({
employeeUuid: employeeId,
})
const employeeFederalTax = fedData.employeeFederalTax!

const { mutateAsync: updateFederalTaxes, isPending } =
useEmployeeTaxSetupUpdateFederalTaxesMutation()

const defaultValues = {
filingStatus: employeeFederalTax.filingStatus ?? undefined,
twoJobs: employeeFederalTax.twoJobs ? ('true' as const) : ('false' as const),
deductions: employeeFederalTax.deductions ? Number(employeeFederalTax.deductions) : 0,
dependentsAmount: employeeFederalTax.dependentsAmount
? Number(employeeFederalTax.dependentsAmount)
: 0,
otherIncome: employeeFederalTax.otherIncome ? Number(employeeFederalTax.otherIncome) : 0,
extraWithholding: employeeFederalTax.extraWithholding
? Number(employeeFederalTax.extraWithholding)
: 0,
w4DataType: employeeFederalTax.w4DataType,
}

const formMethods = useForm<FederalFormInputs, unknown, FederalFormPayload>({
resolver: zodResolver(FederalFormSchema),
defaultValues,
})
const { handleSubmit, setError: _setError } = formMethods

useEffect(() => {
if (fieldErrors && fieldErrors.length > 0) {
fieldErrors.forEach(msgObject => {
const key = msgObject.key.replace('.value', '')
_setError(key as keyof FederalFormInputs, { type: 'custom', message: msgObject.message })
})
}
}, [fieldErrors, _setError])

const onSubmit: SubmitHandler<FederalFormPayload> = async data => {
await baseSubmitHandler(data, async payload => {
const federalTaxesResponse = await updateFederalTaxes({
request: {
employeeUuid: employeeId,
requestBody: {
...payload,
twoJobs: payload.twoJobs === 'true',
version: employeeFederalTax.version,
},
},
})
onEvent(componentEvents.EMPLOYEE_FEDERAL_TAXES_UPDATED, federalTaxesResponse)
onEvent(componentEvents.EMPLOYEE_FEDERAL_TAXES_DONE)
})
}

return (
<section className={className}>
<FederalTaxesProvider
value={{
isPending,
}}
>
<FormProvider {...formMethods}>
<Form onSubmit={handleSubmit(onSubmit)}>
{children ? (
children
) : (
<>
<Head />
<FederalForm />
<Actions />
</>
)}
</Form>
</FormProvider>
</FederalTaxesProvider>
</section>
)
}
Loading