diff --git a/docs/workflows-overview/employee-onboarding/employee-onboarding.md b/docs/workflows-overview/employee-onboarding/employee-onboarding.md index 79f0cbbc0..c6eb76828 100644 --- a/docs/workflows-overview/employee-onboarding/employee-onboarding.md +++ b/docs/workflows-overview/employee-onboarding/employee-onboarding.md @@ -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 @@ -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 {}} /> + return ( + {}} /> + ) +} +``` + +#### 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 ( + {}} /> + ) } ``` @@ -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 diff --git a/docs/workflows-overview/employee-onboarding/employee-self-onboarding.md b/docs/workflows-overview/employee-onboarding/employee-self-onboarding.md index 407adf523..50ab860f1 100644 --- a/docs/workflows-overview/employee-onboarding/employee-self-onboarding.md +++ b/docs/workflows-overview/employee-onboarding/employee-self-onboarding.md @@ -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 ( - {}} @@ -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.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 {}} /> + return ( + {}} /> + ) } ``` diff --git a/src/components/Employee/Compensation/Compensation.tsx b/src/components/Employee/Compensation/Compensation.tsx index 74ecf4b0d..80ce42137 100644 --- a/src/components/Employee/Compensation/Compensation.tsx +++ b/src/components/Employee/Compensation/Compensation.tsx @@ -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' diff --git a/src/components/Employee/Deductions/Deductions.tsx b/src/components/Employee/Deductions/Deductions.tsx index 9be3d8bb0..2b40dd25e 100644 --- a/src/components/Employee/Deductions/Deductions.tsx +++ b/src/components/Employee/Deductions/Deductions.tsx @@ -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, diff --git a/src/components/Employee/EmployeeList/EmployeeList.tsx b/src/components/Employee/EmployeeList/EmployeeList.tsx index 713de7c67..f19481fd7 100644 --- a/src/components/Employee/EmployeeList/EmployeeList.tsx +++ b/src/components/Employee/EmployeeList/EmployeeList.tsx @@ -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 { diff --git a/src/components/Employee/FederalTaxes/Actions.tsx b/src/components/Employee/FederalTaxes/Actions.tsx new file mode 100644 index 000000000..bcf2b73c7 --- /dev/null +++ b/src/components/Employee/FederalTaxes/Actions.tsx @@ -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 ( + + + {t('submitCta')} + + + ) +} diff --git a/src/components/Employee/FederalTaxes/FederalForm.tsx b/src/components/Employee/FederalTaxes/FederalForm.tsx new file mode 100644 index 000000000..3aad228a8 --- /dev/null +++ b/src/components/Employee/FederalTaxes/FederalForm.tsx @@ -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 +export type FederalFormPayload = z.output + +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 ( + <> + + , + }} + /> + } + options={[ + { value: 'true', label: t('twoJobYesLabel') }, + { value: 'false', label: t('twoJobNoLabel') }, + ]} + /> + + + + + + ) +} diff --git a/src/components/Employee/FederalTaxes/FederalTaxes.tsx b/src/components/Employee/FederalTaxes/FederalTaxes.tsx new file mode 100644 index 000000000..426e90686 --- /dev/null +++ b/src/components/Employee/FederalTaxes/FederalTaxes.tsx @@ -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 ( + {...props}> + + + ) +} + +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({ + 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 = 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 ( +
+ + +
+ {children ? ( + children + ) : ( + <> + + + + + )} + +
+
+
+ ) +} diff --git a/src/components/Employee/FederalTaxes/Head.tsx b/src/components/Employee/FederalTaxes/Head.tsx new file mode 100644 index 000000000..e1571bf95 --- /dev/null +++ b/src/components/Employee/FederalTaxes/Head.tsx @@ -0,0 +1,23 @@ +import { Trans, useTranslation } from 'react-i18next' +import { useComponentContext } from '@/contexts/ComponentAdapter/useComponentContext' + +export function Head() { + const { t } = useTranslation('Employee.FederalTaxes') + const Components = useComponentContext() + + return ( + <> + {t('federalTaxesTitle')} + + , + help_center: , + }} + /> + + + ) +} diff --git a/src/components/Employee/FederalTaxes/index.ts b/src/components/Employee/FederalTaxes/index.ts new file mode 100644 index 000000000..d00d20b7a --- /dev/null +++ b/src/components/Employee/FederalTaxes/index.ts @@ -0,0 +1 @@ +export { FederalTaxes } from './FederalTaxes' diff --git a/src/components/Employee/FederalTaxes/useFederalTaxes.ts b/src/components/Employee/FederalTaxes/useFederalTaxes.ts new file mode 100644 index 000000000..26ebb7abe --- /dev/null +++ b/src/components/Employee/FederalTaxes/useFederalTaxes.ts @@ -0,0 +1,9 @@ +import { createCompoundContext } from '@/components/Base' + +type FederalTaxesContextType = { + isPending: boolean +} + +const [useFederalTaxes, FederalTaxesProvider] = + createCompoundContext('FederalTaxesContext') +export { useFederalTaxes, FederalTaxesProvider } diff --git a/src/components/Employee/OnboardingFlow/OnboardingFlow.test.tsx b/src/components/Employee/OnboardingFlow/OnboardingFlow.test.tsx index 2dfeb8fa3..b404a5f76 100644 --- a/src/components/Employee/OnboardingFlow/OnboardingFlow.test.tsx +++ b/src/components/Employee/OnboardingFlow/OnboardingFlow.test.tsx @@ -103,27 +103,37 @@ describe('EmployeeOnboardingFlow', () => { await user.click(await screen.findByRole('button', { name: /Add/i })) - // Page - Personal Details + // Page - Personal Details (Admin) await screen.findByLabelText(/social/i) // Wait for page to load await user.type(await screen.findByLabelText(/social/i), '456789012') await user.type(await screen.findByLabelText(/first name/i), 'john') await user.type(await screen.findByLabelText(/last name/i), 'silver') - await user.type(await screen.findByLabelText(/email/i), 'someone@definitely-not-gusto.com') - // Work address - await user.click(await screen.findByLabelText(/work address/i)) - await user.click(await screen.findByRole('option', { name: /123 Main St/i })) - await fillDate({ date: { month: 1, day: 1, year: 2025 }, name: 'Start date', user }) + const emailField = screen.queryByLabelText(/email/i) + if (emailField) { + await user.type(emailField, 'someone@definitely-not-gusto.com') + } + + // Work address (required for admin profile) + const workAddressField = screen.queryByLabelText(/work address/i) + if (workAddressField) { + await user.click(workAddressField) + await user.click(await screen.findByRole('option', { name: /123 Main St/i })) + } + + // Dates + const hasStartDate = screen.queryByLabelText('Start date') + if (hasStartDate) { + await fillDate({ date: { month: 1, day: 1, year: 2025 }, name: 'Start date', user }) + } await fillDate({ date: { month: 1, day: 1, year: 2000 }, name: 'Date of birth', user }) + + // Home address await user.type(await screen.findByLabelText('Street 1'), '123 Any St') await user.type(await screen.findByLabelText(/city/i), 'Redmond') - - // State await user.click(await screen.findByLabelText('State')) await user.click(await screen.findByRole('option', { name: 'Washington' })) - - // Zip const zip = await screen.findByLabelText(/zip/i) await user.clear(zip) await user.type(zip, '98074') @@ -141,24 +151,26 @@ describe('EmployeeOnboardingFlow', () => { // Page - Compensation pt 2 await screen.findByRole('button', { name: 'Continue' }) // Wait for the page to load - await user.click(await screen.findByRole('button', { name: 'Continue' })) - // Page - Federal / State Taxes - await screen.findByLabelText(/Withholding Allowance/i) // Wait for page to load + // Page - Federal Taxes (separate step) + await screen.findByRole('heading', { name: /Federal tax withholdings/i }) + await user.click(await screen.findByLabelText(/Filing status/i)) + await user.click(await screen.findByRole('option', { name: 'Single' })) + await user.click(await screen.findByRole('button', { name: 'Continue' })) - await user.type(await screen.findByLabelText(/Withholding Allowance/i), '3') + // Page - State Taxes (separate step) + const taxReqHeadings = await screen.findAllByText(/Tax Requirements/i) + expect(taxReqHeadings.length).toBeGreaterThan(0) await user.click(await screen.findByRole('button', { name: 'Continue' })) // Page - Payment method await screen.findByText('Check') // Wait for page to load - await user.click(await screen.findByText('Check')) await user.click(await screen.findByRole('button', { name: 'Continue' })) // Page - Deductions await screen.findByLabelText('No') // Wait for page to load - await user.click(await screen.findByLabelText('No')) await user.click(await screen.findByRole('button', { name: 'Continue' })) diff --git a/src/components/Employee/OnboardingFlow/OnboardingFlow.tsx b/src/components/Employee/OnboardingFlow/OnboardingFlow.tsx index 639a8a1d9..91bccb45a 100644 --- a/src/components/Employee/OnboardingFlow/OnboardingFlow.tsx +++ b/src/components/Employee/OnboardingFlow/OnboardingFlow.tsx @@ -1,34 +1,19 @@ import { createMachine } from 'robot3' -import type { PaymentMethodBankAccount } from '@gusto/embedded-api/models/components/paymentmethodbankaccount' -import type { ProfileDefaultValues } from '../Profile' -import type { CompensationDefaultValues } from '../Compensation' import { EmployeeListContextual } from '../EmployeeList/EmployeeList' import { employeeOnboardingMachine } from './onboardingStateMachine' +import type { + OnboardingDefaultValues, + OnboardingContextInterface, +} from './OnboardingFlowComponents' import { Flow } from '@/components/Flow/Flow' import type { BaseComponentInterface } from '@/components/Base' -import type { EmployeeOnboardingStatus } from '@/shared/constants' import type { RequireAtLeastOne } from '@/types/Helpers' -import type { FlowContextInterface } from '@/components/Flow/useFlow' -export type OnboardingDefaultValues = RequireAtLeastOne<{ - profile?: ProfileDefaultValues - compensation?: CompensationDefaultValues -}> export interface OnboardingFlowProps extends BaseComponentInterface { companyId: string defaultValues?: RequireAtLeastOne isSelfOnboardingEnabled?: boolean } -export interface OnboardingContextInterface extends FlowContextInterface { - companyId: string - employeeId?: string - isAdmin?: boolean - onboardingStatus?: (typeof EmployeeOnboardingStatus)[keyof typeof EmployeeOnboardingStatus] - startDate?: string - paymentMethod?: PaymentMethodBankAccount - defaultValues?: OnboardingDefaultValues - isSelfOnboardingEnabled?: boolean -} export const OnboardingFlow = ({ companyId, diff --git a/src/components/Employee/OnboardingFlow/OnboardingFlowComponents.tsx b/src/components/Employee/OnboardingFlow/OnboardingFlowComponents.tsx new file mode 100644 index 000000000..0e6af9e52 --- /dev/null +++ b/src/components/Employee/OnboardingFlow/OnboardingFlowComponents.tsx @@ -0,0 +1,35 @@ +import type { PaymentMethodBankAccount } from '@gusto/embedded-api/models/components/paymentmethodbankaccount' +import { FederalTaxes } from '../FederalTaxes/FederalTaxes' +import { StateTaxes } from '../StateTaxes/StateTaxes' +import type { ProfileDefaultValues } from '../Profile' +import type { CompensationDefaultValues } from '../Compensation' +import { ensureRequired } from '@/helpers/ensureRequired' +import { useFlow, type FlowContextInterface } from '@/components/Flow/useFlow' +import type { EmployeeOnboardingStatus } from '@/shared/constants' +import type { RequireAtLeastOne } from '@/types/Helpers' + +export type OnboardingDefaultValues = RequireAtLeastOne<{ + profile?: ProfileDefaultValues + compensation?: CompensationDefaultValues +}> + +export interface OnboardingContextInterface extends FlowContextInterface { + companyId: string + employeeId?: string + isAdmin?: boolean + onboardingStatus?: (typeof EmployeeOnboardingStatus)[keyof typeof EmployeeOnboardingStatus] + startDate?: string + paymentMethod?: PaymentMethodBankAccount + defaultValues?: OnboardingDefaultValues + isSelfOnboardingEnabled?: boolean +} + +export function FederalTaxesContextual() { + const { employeeId, onEvent } = useFlow() + return +} + +export function StateTaxesContextual() { + const { employeeId, onEvent, isAdmin } = useFlow() + return +} diff --git a/src/components/Employee/OnboardingFlow/onboardingStateMachine.ts b/src/components/Employee/OnboardingFlow/onboardingStateMachine.ts index bf4fdda51..05bb29e5f 100644 --- a/src/components/Employee/OnboardingFlow/onboardingStateMachine.ts +++ b/src/components/Employee/OnboardingFlow/onboardingStateMachine.ts @@ -1,9 +1,13 @@ import { transition, reduce, state, guard } from 'robot3' -import type { OnboardingContextInterface } from './OnboardingFlow' import { - EmployeeOnboardingStatus, - EmployeeSelfOnboardingStatuses, + FederalTaxesContextual, + StateTaxesContextual, + type OnboardingContextInterface, +} from './OnboardingFlowComponents' +import { componentEvents, + EmployeeSelfOnboardingStatuses, + EmployeeOnboardingStatus, } from '@/shared/constants' import { type MachineEventType } from '@/types/Helpers' import { CompensationContextual } from '@/components/Employee/Compensation' @@ -11,7 +15,6 @@ import { DeductionsContextual } from '@/components/Employee/Deductions' import { EmployeeListContextual } from '@/components/Employee/EmployeeList/EmployeeList' import { PaymentMethodContextual } from '@/components/Employee/PaymentMethod' import { ProfileContextual } from '@/components/Employee/Profile' -import { TaxesContextual } from '@/components/Employee/Taxes' import { OnboardingSummaryContextual } from '@/components/Employee/OnboardingSummary' type EventPayloads = { @@ -99,8 +102,8 @@ export const employeeOnboardingMachine = { compensation: state( transition( componentEvents.EMPLOYEE_COMPENSATION_DONE, - 'employeeTaxes', - reduce(createReducer({ component: TaxesContextual })), + 'federalTaxes', + reduce(createReducer({ component: FederalTaxesContextual })), guard(selfOnboardingGuard), ), transition( @@ -110,9 +113,18 @@ export const employeeOnboardingMachine = { ), cancelTransition('index'), ), - employeeTaxes: state( + federalTaxes: state( + transition( + componentEvents.EMPLOYEE_FEDERAL_TAXES_DONE, + 'stateTaxes', + reduce(createReducer({ component: StateTaxesContextual })), + guard(selfOnboardingGuard), + ), + cancelTransition('index'), + ), + stateTaxes: state( transition( - componentEvents.EMPLOYEE_TAXES_DONE, + componentEvents.EMPLOYEE_STATE_TAXES_DONE, 'paymentMethod', reduce(createReducer({ component: PaymentMethodContextual })), guard(selfOnboardingGuard), diff --git a/src/components/Employee/OnboardingSummary/OnboardingSummary.tsx b/src/components/Employee/OnboardingSummary/OnboardingSummary.tsx index 758ad8c1a..2e951cf8e 100644 --- a/src/components/Employee/OnboardingSummary/OnboardingSummary.tsx +++ b/src/components/Employee/OnboardingSummary/OnboardingSummary.tsx @@ -4,7 +4,7 @@ import { useEmployeesGetSuspense } from '@gusto/embedded-api/react-query/employe import { useEmployeesGetOnboardingStatusSuspense } from '@gusto/embedded-api/react-query/employeesGetOnboardingStatus' import DOMPurify from 'dompurify' import { useMemo } from 'react' -import type { OnboardingContextInterface } from '../OnboardingFlow/OnboardingFlow' +import type { OnboardingContextInterface } from '../OnboardingFlow/OnboardingFlowComponents' import styles from './OnboardingSummary.module.scss' import { BaseComponent, diff --git a/src/components/Employee/PaymentMethod/PaymentMethod.tsx b/src/components/Employee/PaymentMethod/PaymentMethod.tsx index 7cba4745d..bd3631583 100644 --- a/src/components/Employee/PaymentMethod/PaymentMethod.tsx +++ b/src/components/Employee/PaymentMethod/PaymentMethod.tsx @@ -9,7 +9,7 @@ import { useQueryClient } from '@tanstack/react-query' import { useEffect, useMemo, useState } from 'react' import { FormProvider, useForm, type DefaultValues, type SubmitHandler } from 'react-hook-form' import { useTranslation } from 'react-i18next' -import type { OnboardingContextInterface } from '../OnboardingFlow/OnboardingFlow' +import type { OnboardingContextInterface } from '../OnboardingFlow/OnboardingFlowComponents' import { CombinedSchema, type CombinedSchemaInputs, diff --git a/src/components/Employee/Profile/Profile.tsx b/src/components/Employee/Profile/Profile.tsx index 0cadb892f..084293e87 100644 --- a/src/components/Employee/Profile/Profile.tsx +++ b/src/components/Employee/Profile/Profile.tsx @@ -17,7 +17,7 @@ import type { EmployeeWorkAddress } from '@gusto/embedded-api/models/components/ import { useEmployeeAddressesCreateWorkAddressMutation } from '@gusto/embedded-api/react-query/employeeAddressesCreateWorkAddress' import { RFCDate } from '@gusto/embedded-api/types/rfcdate' import { useEmployeesUpdateOnboardingStatusMutation } from '@gusto/embedded-api/react-query/employeesUpdateOnboardingStatus' -import type { OnboardingContextInterface } from '../OnboardingFlow/OnboardingFlow' +import type { OnboardingContextInterface } from '../OnboardingFlow/OnboardingFlowComponents' import { AdminPersonalDetails, AdminPersonalDetailsSchema, diff --git a/src/components/Employee/SelfOnboardingFlow/SelfOnboardingComponents.tsx b/src/components/Employee/SelfOnboardingFlow/SelfOnboardingComponents.tsx index 0580de139..373ec2929 100644 --- a/src/components/Employee/SelfOnboardingFlow/SelfOnboardingComponents.tsx +++ b/src/components/Employee/SelfOnboardingFlow/SelfOnboardingComponents.tsx @@ -5,7 +5,8 @@ import { useFlow } from '@/components/Flow/useFlow' import type { BaseComponentInterface } from '@/components/Base' import { Landing as LandingComponent } from '@/components/Employee/Landing' import { Profile as ProfileComponent } from '@/components/Employee/Profile' -import { Taxes as TaxesComponent } from '@/components/Employee/Taxes' +import { FederalTaxes as FederalTaxesComponent } from '@/components/Employee/FederalTaxes' +import { StateTaxes as StateTaxesComponent } from '@/components/Employee/StateTaxes' import { PaymentMethod as PaymentMethodComponent } from '@/components/Employee/PaymentMethod' import { OnboardingSummary as OnboardingSummaryComponent } from '@/components/Employee/OnboardingSummary' @@ -42,9 +43,20 @@ export function Profile() { ) } -export function Taxes() { +export function FederalTaxes() { const { employeeId, onEvent } = useFlow() - return + return +} + +export function StateTaxes() { + const { employeeId, onEvent } = useFlow() + return ( + + ) } export function PaymentMethod() { diff --git a/src/components/Employee/SelfOnboardingFlow/SelfOnboardingFlow.test.tsx b/src/components/Employee/SelfOnboardingFlow/SelfOnboardingFlow.test.tsx index 829a4d841..811e0586e 100644 --- a/src/components/Employee/SelfOnboardingFlow/SelfOnboardingFlow.test.tsx +++ b/src/components/Employee/SelfOnboardingFlow/SelfOnboardingFlow.test.tsx @@ -101,24 +101,27 @@ describe('EmployeeSelfOnboardingFlow', () => { await user.type(zip, '98074') await user.click(await screen.findByRole('button', { name: 'Continue' })) - // Page 3 - Federal / State Taxes - await screen.findByLabelText(/Withholding Allowance/i) // Wait for page to load + // Page 3 - Federal Taxes + await screen.findByRole('heading', { name: /Federal tax withholdings/i }) + await user.click(await screen.findByLabelText(/Filing status/i)) + await user.click(await screen.findByRole('option', { name: 'Single' })) + await user.click(await screen.findByRole('button', { name: 'Continue' })) - await user.type(await screen.findByLabelText(/Withholding Allowance/i), '3') + // Page 4 - State Taxes + const stateTaxHeadings = await screen.findAllByText(/Tax Requirements/i) + expect(stateTaxHeadings.length).toBeGreaterThan(0) await user.click(await screen.findByRole('button', { name: 'Continue' })) - // Page 4 - Payment method + // Page 5 - Payment method await screen.findByText('Check') // Wait for page to load - await user.click(await screen.findByText('Check')) await user.click(await screen.findByRole('button', { name: 'Continue' })) - // Page 5 - Sign documents + // Page 6 - Sign documents await screen.findByRole('button', { name: 'Continue' }) // Wait for page to load - await user.click(await screen.findByRole('button', { name: 'Continue' })) - // Page 6 - Completed + // Page 7 - Completed await screen.findByText("You've completed setup!") }, 20000) }) diff --git a/src/components/Employee/SelfOnboardingFlow/selfOnboardingMachine.ts b/src/components/Employee/SelfOnboardingFlow/selfOnboardingMachine.ts index fa8ae63e5..013ac7f56 100644 --- a/src/components/Employee/SelfOnboardingFlow/selfOnboardingMachine.ts +++ b/src/components/Employee/SelfOnboardingFlow/selfOnboardingMachine.ts @@ -1,6 +1,12 @@ import { transition, reduce, state, invoke, createMachine } from 'robot3' import type { SelfOnboardingContextInterface } from './SelfOnboardingComponents' -import { Profile, Taxes, PaymentMethod, OnboardingSummary } from './SelfOnboardingComponents' +import { + Profile, + FederalTaxes, + StateTaxes, + PaymentMethod, + OnboardingSummary, +} from './SelfOnboardingComponents' import { componentEvents } from '@/shared/constants' import type { DocumentSignerContextInterface } from '@/components/Employee/DocumentSigner/documentSignerStateMachine' import { DocumentListContextual } from '@/components/Employee/DocumentSigner/documentSignerStateMachine' @@ -31,18 +37,30 @@ export const employeeSelfOnboardingMachine = { employeeProfile: state( transition( componentEvents.EMPLOYEE_PROFILE_DONE, - 'employeeTaxes', + 'employeeFederalTaxes', reduce( (ctx: SelfOnboardingContextInterface): SelfOnboardingContextInterface => ({ ...ctx, - component: Taxes, + component: FederalTaxes, }), ), ), ), - employeeTaxes: state( + employeeFederalTaxes: state( transition( - componentEvents.EMPLOYEE_TAXES_DONE, + componentEvents.EMPLOYEE_FEDERAL_TAXES_DONE, + 'employeeStateTaxes', + reduce( + (ctx: SelfOnboardingContextInterface): SelfOnboardingContextInterface => ({ + ...ctx, + component: StateTaxes, + }), + ), + ), + ), + employeeStateTaxes: state( + transition( + componentEvents.EMPLOYEE_STATE_TAXES_DONE, 'employeePaymentMethod', reduce((ctx: SelfOnboardingContextInterface) => ({ ...ctx, diff --git a/src/components/Employee/StateTaxes/Actions.tsx b/src/components/Employee/StateTaxes/Actions.tsx new file mode 100644 index 000000000..f92931baa --- /dev/null +++ b/src/components/Employee/StateTaxes/Actions.tsx @@ -0,0 +1,18 @@ +import { useTranslation } from 'react-i18next' +import { useStateTaxes } from './useStateTaxes' +import { ActionsLayout } from '@/components/Common' +import { useComponentContext } from '@/contexts/ComponentAdapter/useComponentContext' + +export function Actions() { + const { t } = useTranslation('Employee.StateTaxes') + const { isPending } = useStateTaxes() + const Components = useComponentContext() + + return ( + + + {t('submitCta')} + + + ) +} diff --git a/src/components/Employee/StateTaxes/StateForm.tsx b/src/components/Employee/StateTaxes/StateForm.tsx new file mode 100644 index 000000000..5f042c66b --- /dev/null +++ b/src/components/Employee/StateTaxes/StateForm.tsx @@ -0,0 +1,46 @@ +import { Fragment } from 'react/jsx-runtime' +import { useTranslation } from 'react-i18next' +import { z } from 'zod' +import { useStateTaxes } from './useStateTaxes' +import type { STATES_ABBR } from '@/shared/constants' +import { snakeCaseToCamelCase } from '@/helpers/formattedStrings' +import { QuestionInput } from '@/components/Common/TaxInputs/TaxInputs' +import { useComponentContext } from '@/contexts/ComponentAdapter/useComponentContext' + +export const StateFormSchema = z.object({ + states: z.record(z.string(), z.record(z.string(), z.unknown())).optional(), +}) + +export type StateFormPayload = z.output + +export const StateForm = () => { + const Components = useComponentContext() + const { employeeStateTaxes, isAdmin } = useStateTaxes() + const { t } = useTranslation('Employee.StateTaxes') + const { t: statesHash } = useTranslation('common', { keyPrefix: 'statesHash' }) + + return employeeStateTaxes.map(({ state, questions }) => + Array.isArray(questions) && (isAdmin || questions.find(q => !q.isQuestionForAdminOnly)) ? ( + + + {t('stateTaxesTitle', { state: statesHash(state as (typeof STATES_ABBR)[number]) })} + + {questions.map(question => { + if (question.isQuestionForAdminOnly && !isAdmin) return null + return ( + + ) + })} + + ) : null, + ) +} diff --git a/src/components/Employee/StateTaxes/StateTaxes.tsx b/src/components/Employee/StateTaxes/StateTaxes.tsx new file mode 100644 index 000000000..602a9d8bb --- /dev/null +++ b/src/components/Employee/StateTaxes/StateTaxes.tsx @@ -0,0 +1,156 @@ +import { zodResolver } from '@hookform/resolvers/zod' +import { FormProvider, useForm, type SubmitHandler } from 'react-hook-form' +import { useEffect } from 'react' +import { useEmployeeTaxSetupGetStateTaxesSuspense } from '@gusto/embedded-api/react-query/employeeTaxSetupGetStateTaxes' +import { useEmployeeTaxSetupUpdateStateTaxesMutation } from '@gusto/embedded-api/react-query/employeeTaxSetupUpdateStateTaxes' +import { StateForm } from './StateForm' +import { StateFormSchema, type StateFormPayload } from './StateForm' +import { Actions } from './Actions' +import { StateTaxesProvider } from './useStateTaxes' +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' +import { snakeCaseToCamelCase } from '@/helpers/formattedStrings' + +const DEFAULT_TAX_VALID_FROM = '2010-01-01' + +interface StateTaxesProps extends CommonComponentInterface<'Employee.StateTaxes'> { + employeeId: string + isAdmin?: boolean +} + +export function StateTaxes(props: StateTaxesProps & BaseComponentInterface) { + return ( + {...props}> + + + ) +} + +const Root = (props: StateTaxesProps) => { + const { employeeId, className, children, isAdmin = false, dictionary } = props + const { onEvent, fieldErrors, baseSubmitHandler } = useBase() + useI18n('Employee.StateTaxes') + useComponentDictionary('Employee.StateTaxes', dictionary) + + const { data: stateData } = useEmployeeTaxSetupGetStateTaxesSuspense({ + employeeUuid: employeeId, + }) + const employeeStateTaxes = stateData.employeeStateTaxesList! + const { mutateAsync: updateStateTaxes, isPending } = useEmployeeTaxSetupUpdateStateTaxesMutation() + + const defaultValues = { + states: employeeStateTaxes.reduce((acc: Record, state) => { + if (state.state) { + acc[state.state] = state.questions?.reduce((acc: Record, question) => { + const value = question.answers[0]?.value + const key = snakeCaseToCamelCase(question.key) + // Default new hire report to true if not specified + if (key === 'fileNewHireReport') { + acc[key] = typeof value === 'undefined' ? true : value + } else { + acc[key] = value + } + return acc + }, {}) + } + return acc + }, {}), + } + + const formMethods = useForm, unknown, StateFormPayload>({ + resolver: zodResolver(StateFormSchema), + defaultValues, + }) + const { handleSubmit, setError: _setError } = formMethods + + useEffect(() => { + if (fieldErrors && fieldErrors.length > 0) { + fieldErrors.forEach(msgObject => { + const key = msgObject.key.replace('.value', '') + const message = typeof msgObject.message === 'string' ? msgObject.message : 'Unknown error' + _setError(key, { type: 'custom', message }) + }) + } + }, [fieldErrors, _setError]) + + const onSubmit: SubmitHandler = async data => { + await baseSubmitHandler(data, async payload => { + const { states: statesPayload } = payload + + if (statesPayload && Object.keys(statesPayload).length > 0) { + const states = [] + + for (const state of employeeStateTaxes) { + const stateName = state.state + + if (stateName && state.questions !== undefined) { + states.push({ + state: stateName, + questions: state.questions + .map(question => { + if (question.isQuestionForAdminOnly && !isAdmin) { + return null + } + const formValue = statesPayload[stateName]?.[snakeCaseToCamelCase(question.key)] + return { + key: question.key, + answers: [ + { + validFrom: question.answers[0]?.validFrom ?? DEFAULT_TAX_VALID_FROM, + validUpTo: question.answers[0]?.validUpTo ?? null, + value: + formValue == null || (typeof formValue === 'number' && isNaN(formValue)) + ? '' + : (formValue as string | number | boolean), + }, + ], + } + }) + .filter(q => q !== null), + }) + } + } + + const stateTaxesResponse = await updateStateTaxes({ + request: { employeeUuid: employeeId, employeeStateTaxesRequest: { states } }, + }) + onEvent(componentEvents.EMPLOYEE_STATE_TAXES_UPDATED, stateTaxesResponse) + } + + onEvent(componentEvents.EMPLOYEE_STATE_TAXES_DONE) + }) + } + + return ( +
+ + +
+ {children ? ( + children + ) : ( + <> + + + + )} + +
+
+
+ ) +} diff --git a/src/components/Employee/StateTaxes/index.ts b/src/components/Employee/StateTaxes/index.ts new file mode 100644 index 000000000..f526b2a8b --- /dev/null +++ b/src/components/Employee/StateTaxes/index.ts @@ -0,0 +1 @@ +export { StateTaxes } from './StateTaxes' diff --git a/src/components/Employee/StateTaxes/useStateTaxes.ts b/src/components/Employee/StateTaxes/useStateTaxes.ts new file mode 100644 index 000000000..b86c53441 --- /dev/null +++ b/src/components/Employee/StateTaxes/useStateTaxes.ts @@ -0,0 +1,12 @@ +import type { EmployeeStateTaxesList } from '@gusto/embedded-api/models/components/employeestatetaxeslist' +import { createCompoundContext } from '@/components/Base' + +type StateTaxesContextType = { + employeeStateTaxes: EmployeeStateTaxesList[] + isPending: boolean + isAdmin: boolean +} + +const [useStateTaxes, StateTaxesProvider] = + createCompoundContext('StateTaxesContext') +export { useStateTaxes, StateTaxesProvider } diff --git a/src/components/Employee/Taxes/Taxes.tsx b/src/components/Employee/Taxes/Taxes.tsx index 76b6582c3..0b2d7f373 100644 --- a/src/components/Employee/Taxes/Taxes.tsx +++ b/src/components/Employee/Taxes/Taxes.tsx @@ -6,7 +6,7 @@ import { useEmployeeTaxSetupGetFederalTaxesSuspense } from '@gusto/embedded-api/ import { useEmployeeTaxSetupUpdateFederalTaxesMutation } from '@gusto/embedded-api/react-query/employeeTaxSetupUpdateFederalTaxes' import { useEmployeeTaxSetupGetStateTaxesSuspense } from '@gusto/embedded-api/react-query/employeeTaxSetupGetStateTaxes' import { useEmployeeTaxSetupUpdateStateTaxesMutation } from '@gusto/embedded-api/react-query/employeeTaxSetupUpdateStateTaxes' -import type { OnboardingContextInterface } from '../OnboardingFlow/OnboardingFlow' +import type { OnboardingContextInterface } from '../OnboardingFlow/OnboardingFlowComponents' import { Actions } from './Actions' import { FederalForm, @@ -37,6 +37,11 @@ interface TaxesProps extends CommonComponentInterface<'Employee.Taxes'> { isAdmin?: boolean } +/** + * @deprecated The Taxes component has been deprecated and will be removed in a future release. + * The component has been split into separate components for state and federal. Use Employee.FederalTaxes + * and Employee.StateTaxes components instead. + */ export function Taxes(props: TaxesProps & BaseComponentInterface) { return ( diff --git a/src/components/Employee/index.ts b/src/components/Employee/index.ts index 3a949dea6..74c983e4b 100644 --- a/src/components/Employee/index.ts +++ b/src/components/Employee/index.ts @@ -3,9 +3,12 @@ export { Deductions } from './Deductions' export { OnboardingSummary } from './OnboardingSummary' export { Profile } from './Profile' export { Compensation } from './Compensation' -export { Taxes } from './Taxes' +export { FederalTaxes } from './FederalTaxes' +export { StateTaxes } from './StateTaxes' export { PaymentMethod } from './PaymentMethod' export { Landing } from './Landing' export { DocumentSigner } from './DocumentSigner' export { OnboardingFlow } from './OnboardingFlow/OnboardingFlow' export { SelfOnboardingFlow } from './SelfOnboardingFlow/SelfOnboardingFlow' +// TODO: Remove once we have migrated partners to use the new FederalTaxes and StateTaxes components +export { Taxes } from './Taxes' diff --git a/src/i18n/en/Employee.FederalTaxes.json b/src/i18n/en/Employee.FederalTaxes.json new file mode 100644 index 000000000..c0aa9f90b --- /dev/null +++ b/src/i18n/en/Employee.FederalTaxes.json @@ -0,0 +1,25 @@ +{ + "deductions": "Step 4b: Deductions", + "dependentsTotalIfApplicable": "Step 3: Dependents (if applicable)", + "extraWithholding": "Step 4c: Extra withholding", + "federalFilingStatus1c": "Step 1c: Federal filing status (1c)", + "federalFillingStatusPlaceholder": "Select filing status...", + "federalTaxesTitle": "Federal tax withholdings (Form W-4)", + "irs_calculator": "We'll use this information to withhold the appropriate federal taxes from each paycheck. If you’re unsure what to enter here, refer to Form W4 to calculate the values, visit our Help Center, or consult your tax advisor.", + "filingStatusExemptFromWithholding": "Exempt from withholding", + "filingStatusHeadOfHousehold": "Head of household", + "filingStatusMarried": "Married", + "filingStatusSingle": "Single", + "fieldIsRequired": "This field is a required field. Please enter a value.", + "includesSpouseExplanation": "Includes spouse, if applicable. Answering 2c will result in a higher withholding, but to preserve privacy, this can be left unchecked. Learn more on the IRS website.", + "multipleJobs2c": "Step 2c: Multiple jobs (2c)", + "otherIncome": "Step 4a: Other income", + "selectWithholdingDescription": "If you select Exempt from withholding, we wont withhold federal income taxes, but well still report taxable wages on a W-2. Keep in mind that anyone who claims exemption from withholding needs to submit a new W-4 each year.", + "submitCta": "Continue", + "twoJobYesLabel": "Yes", + "twoJobNoLabel": "No", + "validations": { + "federalFilingStatus": "Please select filing status", + "federalTwoJobs": "Please select an option" + } +} diff --git a/src/i18n/en/Employee.StateTaxes.json b/src/i18n/en/Employee.StateTaxes.json index aa39ad2ba..a04c20fa6 100644 --- a/src/i18n/en/Employee.StateTaxes.json +++ b/src/i18n/en/Employee.StateTaxes.json @@ -1,6 +1,4 @@ { - "title": "State tax", - "filingStatusLabel": "Filing Status", - "cancelCta": "Cancel", - "submitCta": "Submit" + "stateTaxesTitle": "{{state}} Tax Requirements", + "submitCta": "Continue" } diff --git a/src/i18n/en/Employee.Taxes.json b/src/i18n/en/Employee.Taxes.json index 133b7684d..a0ae71040 100644 --- a/src/i18n/en/Employee.Taxes.json +++ b/src/i18n/en/Employee.Taxes.json @@ -12,7 +12,7 @@ "filingStatusSingle": "Single", "fieldIsRequired": "This field is a required field. Please enter a value.", "includesSpouseExplanation": "Includes spouse, if applicable. Answering 2c will result in a higher withholding, but to preserve privacy, this can be left unchecked. Learn more on the IRS website.", - "irs_calculator": "We’ll use this info to withhold the appropriate federal taxes from each paycheck. If you’re unsure what to enter here, refer to Form W4 to calculate the values, visit our Help Center, or consult your tax advisor.", + "irs_calculator": "We’ll use this information to withhold the appropriate federal taxes from each paycheck. If you’re unsure what to enter here, refer to Form W4 to calculate the values, visit our Help Center, or consult your tax advisor.", "irsCalculatorOrW4Line": "Enter the results for {{lineNum}} from the IRS Calculator or form W-4.", "multipleJobs2c": "Step 2c: Multiple jobs (2c)", "otherIncome": "Step 4a: Other income", diff --git a/src/shared/constants.ts b/src/shared/constants.ts index 039d2402e..08b84dd19 100644 --- a/src/shared/constants.ts +++ b/src/shared/constants.ts @@ -34,9 +34,10 @@ export const employeeEvents = { EMPLOYEE_BANK_ACCOUNT_CREATE: 'employee/bankAccount/create', EMPLOYEE_BANK_ACCOUNT_CREATED: 'employee/bankAccount/created', EMPLOYEE_BANK_ACCOUNT_DELETED: 'employee/bankAccount/deleted', - EMPLOYEE_FEDERAL_TAXES_VIEW: 'employee/federalTaxes/view', EMPLOYEE_FEDERAL_TAXES_UPDATED: 'employee/federalTaxes/updated', + EMPLOYEE_FEDERAL_TAXES_DONE: 'employee/federalTaxes/done', EMPLOYEE_STATE_TAXES_UPDATED: 'employee/stateTaxes/updated', + EMPLOYEE_STATE_TAXES_DONE: 'employee/stateTaxes/done', EMPLOYEE_TAXES_DONE: 'employee/taxes/done', EMPLOYEE_SPLIT_PAYCHECK: 'employee/bankAccount/split', EMPLOYEE_JOB_CREATED: 'employee/job/created', diff --git a/src/types/i18next.d.ts b/src/types/i18next.d.ts index 3b8de7919..a8c5533c9 100644 --- a/src/types/i18next.d.ts +++ b/src/types/i18next.d.ts @@ -678,6 +678,31 @@ export interface EmployeeEmployeeList{ "statusLabel":string; "title":string; }; +export interface EmployeeFederalTaxes{ +"deductions":string; +"dependentsTotalIfApplicable":string; +"extraWithholding":string; +"federalFilingStatus1c":string; +"federalFillingStatusPlaceholder":string; +"federalTaxesTitle":string; +"irs_calculator":string; +"filingStatusExemptFromWithholding":string; +"filingStatusHeadOfHousehold":string; +"filingStatusMarried":string; +"filingStatusSingle":string; +"fieldIsRequired":string; +"includesSpouseExplanation":string; +"multipleJobs2c":string; +"otherIncome":string; +"selectWithholdingDescription":string; +"submitCta":string; +"twoJobYesLabel":string; +"twoJobNoLabel":string; +"validations":{ +"federalFilingStatus":string; +"federalTwoJobs":string; +}; +}; export interface EmployeeHomeAddress{ "formTitle":string; "desc":string; @@ -865,9 +890,7 @@ export interface EmployeeSplitPaycheck{ "submitCta":string; }; export interface EmployeeStateTaxes{ -"title":string; -"filingStatusLabel":string; -"cancelCta":string; +"stateTaxesTitle":string; "submitCta":string; }; export interface EmployeeTaxes{ @@ -1088,6 +1111,6 @@ export interface common{ interface CustomTypeOptions { defaultNS: 'common'; - resources: { 'Company.AddBank': CompanyAddBank, 'Company.Addresses': CompanyAddresses, 'Company.AssignSignatory': CompanyAssignSignatory, 'Company.BankAccount': CompanyBankAccount, 'Company.DocumentList': CompanyDocumentList, 'Company.FederalTaxes': CompanyFederalTaxes, 'Company.Industry': CompanyIndustry, 'Company.Locations': CompanyLocations, 'Company.OnboardingOverview': CompanyOnboardingOverview, 'Company.PaySchedule': CompanyPaySchedule, 'Company.SignatureForm': CompanySignatureForm, 'Company.StateTaxes': CompanyStateTaxes, 'Contractor.Address': ContractorAddress, 'Contractor.ContractorList': ContractorContractorList, 'Contractor.NewHireReport': ContractorNewHireReport, 'Contractor.PaymentMethod': ContractorPaymentMethod, 'Contractor.Profile': ContractorProfile, 'Contractor.Submit': ContractorSubmit, 'Employee.BankAccount': EmployeeBankAccount, 'Employee.Compensation': EmployeeCompensation, 'Employee.Deductions': EmployeeDeductions, 'Employee.DocumentSigner': EmployeeDocumentSigner, 'Employee.EmployeeList': EmployeeEmployeeList, 'Employee.HomeAddress': EmployeeHomeAddress, 'Employee.Landing': EmployeeLanding, 'Employee.OnboardingSummary': EmployeeOnboardingSummary, 'Employee.PaySchedules': EmployeePaySchedules, 'Employee.PaymentMethod': EmployeePaymentMethod, 'Employee.Profile': EmployeeProfile, 'Employee.SplitPaycheck': EmployeeSplitPaycheck, 'Employee.StateTaxes': EmployeeStateTaxes, 'Employee.Taxes': EmployeeTaxes, 'Payroll.PayrollHistoryList': PayrollPayrollHistoryList, 'Payroll.PayrollSchedule': PayrollPayrollSchedule, 'common': common, } + resources: { 'Company.AddBank': CompanyAddBank, 'Company.Addresses': CompanyAddresses, 'Company.AssignSignatory': CompanyAssignSignatory, 'Company.BankAccount': CompanyBankAccount, 'Company.DocumentList': CompanyDocumentList, 'Company.FederalTaxes': CompanyFederalTaxes, 'Company.Industry': CompanyIndustry, 'Company.Locations': CompanyLocations, 'Company.OnboardingOverview': CompanyOnboardingOverview, 'Company.PaySchedule': CompanyPaySchedule, 'Company.SignatureForm': CompanySignatureForm, 'Company.StateTaxes': CompanyStateTaxes, 'Contractor.Address': ContractorAddress, 'Contractor.ContractorList': ContractorContractorList, 'Contractor.NewHireReport': ContractorNewHireReport, 'Contractor.PaymentMethod': ContractorPaymentMethod, 'Contractor.Profile': ContractorProfile, 'Contractor.Submit': ContractorSubmit, 'Employee.BankAccount': EmployeeBankAccount, 'Employee.Compensation': EmployeeCompensation, 'Employee.Deductions': EmployeeDeductions, 'Employee.DocumentSigner': EmployeeDocumentSigner, 'Employee.EmployeeList': EmployeeEmployeeList, 'Employee.FederalTaxes': EmployeeFederalTaxes, 'Employee.HomeAddress': EmployeeHomeAddress, 'Employee.Landing': EmployeeLanding, 'Employee.OnboardingSummary': EmployeeOnboardingSummary, 'Employee.PaySchedules': EmployeePaySchedules, 'Employee.PaymentMethod': EmployeePaymentMethod, 'Employee.Profile': EmployeeProfile, 'Employee.SplitPaycheck': EmployeeSplitPaycheck, 'Employee.StateTaxes': EmployeeStateTaxes, 'Employee.Taxes': EmployeeTaxes, 'Payroll.PayrollHistoryList': PayrollPayrollHistoryList, 'Payroll.PayrollSchedule': PayrollPayrollSchedule, 'common': common, } }; } \ No newline at end of file