diff --git a/docs/reference/endpoint-inventory.json b/docs/reference/endpoint-inventory.json index e46c5d267..2d73108ee 100644 --- a/docs/reference/endpoint-inventory.json +++ b/docs/reference/endpoint-inventory.json @@ -2636,17 +2636,16 @@ "Employee.EmployeeListFlow": { "blocks": [ "Employee.DashboardFlow", - "Employee.OnboardingFlow", + "Employee.OnboardingExecutionFlow", "Employee.TerminationFlow", "EmployeeManagement.EmployeeList" ] }, - "Employee.OnboardingFlow": { + "Employee.OnboardingExecutionFlow": { "blocks": [ "Employee.Compensation", "Employee.Deductions", "Employee.EmployeeDocuments", - "Employee.EmployeeList", "Employee.OnboardingSummary", "Employee.PaymentMethod", "Employee.Profile", @@ -2654,6 +2653,12 @@ "EmployeeOnboarding.StateTaxes" ] }, + "Employee.OnboardingFlow": { + "blocks": [ + "Employee.EmployeeList", + "Employee.OnboardingExecutionFlow" + ] + }, "Employee.SelfOnboardingFlow": { "blocks": [ "Employee.DocumentSigner", @@ -2692,7 +2697,7 @@ "EmployeeManagement.DashboardFlow", "EmployeeManagement.EmployeeList", "EmployeeManagement.TerminationFlow", - "EmployeeOnboarding.OnboardingFlow" + "EmployeeOnboarding.OnboardingExecutionFlow" ] }, "EmployeeManagement.TerminationFlow": { @@ -2703,19 +2708,24 @@ "Payroll.PayrollLanding" ] }, - "EmployeeOnboarding.OnboardingFlow": { + "EmployeeOnboarding.OnboardingExecutionFlow": { "blocks": [ "Employee.EmployeeDocuments", "Employee.PaymentMethod", "EmployeeOnboarding.Compensation", "EmployeeOnboarding.Deductions", - "EmployeeOnboarding.EmployeeList", "EmployeeOnboarding.FederalTaxes", "EmployeeOnboarding.OnboardingSummary", "EmployeeOnboarding.Profile", "EmployeeOnboarding.StateTaxes" ] }, + "EmployeeOnboarding.OnboardingFlow": { + "blocks": [ + "EmployeeOnboarding.EmployeeList", + "EmployeeOnboarding.OnboardingExecutionFlow" + ] + }, "EmployeeOnboarding.SelfOnboardingFlow": { "blocks": [ "Employee.PaymentMethod", diff --git a/docs/reference/endpoint-reference.md b/docs/reference/endpoint-reference.md index 39aaed4eb..d6ce5daa3 100644 --- a/docs/reference/endpoint-reference.md +++ b/docs/reference/endpoint-reference.md @@ -489,14 +489,16 @@ Flows compose multiple blocks into a single workflow. The endpoint list for a fl | **Contractor.PaymentFlow** | Contractor.CreatePayment, Contractor.PaymentHistory, Contractor.PaymentStatement, Contractor.PaymentSummary, Contractor.PaymentsList, InformationRequests.InformationRequestsFlow | | **ContractorOnboarding.OnboardingFlow** | ContractorOnboarding.Address, ContractorOnboarding.ContractorList, ContractorOnboarding.ContractorProfile, ContractorOnboarding.ContractorSubmit, ContractorOnboarding.NewHireReport, ContractorOnboarding.PaymentMethod | | **Employee.DashboardFlow** | Employee.Compensation, Employee.Deductions, Employee.HomeAddress, Employee.PaymentMethod, Employee.WorkAddress, EmployeeManagement.DocumentManager, EmployeeManagement.FederalTaxes, EmployeeManagement.Profile, EmployeeManagement.StateTaxes, EmployeeOnboarding.PaymentMethod | -| **Employee.EmployeeListFlow** | Employee.DashboardFlow, Employee.OnboardingFlow, Employee.TerminationFlow, EmployeeManagement.EmployeeList | -| **Employee.OnboardingFlow** | Employee.Compensation, Employee.Deductions, Employee.EmployeeDocuments, Employee.EmployeeList, Employee.OnboardingSummary, Employee.PaymentMethod, Employee.Profile, EmployeeOnboarding.FederalTaxes, EmployeeOnboarding.StateTaxes | +| **Employee.EmployeeListFlow** | Employee.DashboardFlow, Employee.OnboardingExecutionFlow, Employee.TerminationFlow, EmployeeManagement.EmployeeList | +| **Employee.OnboardingExecutionFlow** | Employee.Compensation, Employee.Deductions, Employee.EmployeeDocuments, Employee.OnboardingSummary, Employee.PaymentMethod, Employee.Profile, EmployeeOnboarding.FederalTaxes, EmployeeOnboarding.StateTaxes | +| **Employee.OnboardingFlow** | Employee.EmployeeList, Employee.OnboardingExecutionFlow | | **Employee.SelfOnboardingFlow** | Employee.DocumentSigner, Employee.Landing, Employee.OnboardingSummary, Employee.PaymentMethod, Employee.Profile, EmployeeOnboarding.FederalTaxes, EmployeeOnboarding.StateTaxes | | **Employee.TerminationFlow** | Employee.TerminateEmployee, Employee.TerminationSummary, Payroll.DismissalFlow, Payroll.PayrollLanding | | **EmployeeManagement.DashboardFlow** | Employee.PaymentMethod, EmployeeManagement.DocumentManager, EmployeeManagement.FederalTaxes, EmployeeManagement.HomeAddress, EmployeeManagement.Profile, EmployeeManagement.StateTaxes, EmployeeManagement.WorkAddress, EmployeeOnboarding.Compensation, EmployeeOnboarding.Deductions, EmployeeOnboarding.PaymentMethod | -| **EmployeeManagement.EmployeeListFlow** | EmployeeManagement.DashboardFlow, EmployeeManagement.EmployeeList, EmployeeManagement.TerminationFlow, EmployeeOnboarding.OnboardingFlow | +| **EmployeeManagement.EmployeeListFlow** | EmployeeManagement.DashboardFlow, EmployeeManagement.EmployeeList, EmployeeManagement.TerminationFlow, EmployeeOnboarding.OnboardingExecutionFlow | | **EmployeeManagement.TerminationFlow** | EmployeeManagement.TerminateEmployee, EmployeeManagement.TerminationSummary, Payroll.DismissalFlow, Payroll.PayrollLanding | -| **EmployeeOnboarding.OnboardingFlow** | Employee.EmployeeDocuments, Employee.PaymentMethod, EmployeeOnboarding.Compensation, EmployeeOnboarding.Deductions, EmployeeOnboarding.EmployeeList, EmployeeOnboarding.FederalTaxes, EmployeeOnboarding.OnboardingSummary, EmployeeOnboarding.Profile, EmployeeOnboarding.StateTaxes | +| **EmployeeOnboarding.OnboardingExecutionFlow** | Employee.EmployeeDocuments, Employee.PaymentMethod, EmployeeOnboarding.Compensation, EmployeeOnboarding.Deductions, EmployeeOnboarding.FederalTaxes, EmployeeOnboarding.OnboardingSummary, EmployeeOnboarding.Profile, EmployeeOnboarding.StateTaxes | +| **EmployeeOnboarding.OnboardingFlow** | EmployeeOnboarding.EmployeeList, EmployeeOnboarding.OnboardingExecutionFlow | | **EmployeeOnboarding.SelfOnboardingFlow** | Employee.PaymentMethod, EmployeeOnboarding.DocumentSigner, EmployeeOnboarding.FederalTaxes, EmployeeOnboarding.Landing, EmployeeOnboarding.OnboardingSummary, EmployeeOnboarding.Profile, EmployeeOnboarding.StateTaxes | | **InformationRequests.InformationRequestsFlow** | InformationRequests.InformationRequestForm, InformationRequests.InformationRequestList | | **Payroll.DismissalFlow** | Payroll.PayrollExecutionFlow | diff --git a/sdk-app/src/generated-registry-data.ts b/sdk-app/src/generated-registry-data.ts index 2c251b0a7..8cde8c827 100644 --- a/sdk-app/src/generated-registry-data.ts +++ b/sdk-app/src/generated-registry-data.ts @@ -55,6 +55,7 @@ export const ENTITY_REQUIREMENTS: Record = { 'EmployeeOnboarding.EmploymentEligibility': ['employeeId'], 'EmployeeOnboarding.FederalTaxes': ['employeeId'], 'EmployeeOnboarding.Landing': ['employeeId', 'companyId'], + 'EmployeeOnboarding.OnboardingExecutionFlow': ['companyId'], 'EmployeeOnboarding.OnboardingFlow': ['companyId'], 'EmployeeOnboarding.OnboardingSummary': ['employeeId'], 'EmployeeOnboarding.PaymentMethod': ['employeeId'], diff --git a/src/components/Employee/EmployeeListFlow/EmployeeListFlow.test.tsx b/src/components/Employee/EmployeeListFlow/EmployeeListFlow.test.tsx new file mode 100644 index 000000000..540cd15f7 --- /dev/null +++ b/src/components/Employee/EmployeeListFlow/EmployeeListFlow.test.tsx @@ -0,0 +1,58 @@ +import { beforeEach, describe, it, expect } from 'vitest' +import { render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import { EmployeeListFlow } from './EmployeeListFlow' +import { server } from '@/test/mocks/server' +import { GustoProvider } from '@/contexts' +import { API_BASE_URL } from '@/test/constants' +import { + createEmployee, + getCompanyEmployees, + getEmployee, + getEmployeeOnboardingStatus, +} from '@/test/mocks/apis/employees' +import { getCompany } from '@/test/mocks/apis/company' +import { getCompanyLocations } from '@/test/mocks/apis/company_locations' +import { getEmployeeWorkAddresses } from '@/test/mocks/apis/employee_work_addresses' +import { getEmployeeHomeAddresses } from '@/test/mocks/apis/employee_home_addresses' + +describe('EmployeeListFlow', () => { + beforeEach(() => { + server.use( + getCompanyEmployees('123'), + getEmployee, + getCompany, + getCompanyLocations, + getEmployeeWorkAddresses, + getEmployeeHomeAddresses, + getEmployeeOnboardingStatus, + createEmployee, + ) + }) + + it('renders the management list initially', async () => { + render( + + {}} /> + , + ) + + await screen.findByRole('button', { name: /Add/i }) + expect(await screen.findByText(/Maximus/i)).toBeInTheDocument() + }) + + it('lands on the Profile step when adding an employee — skips the redundant onboarding list', async () => { + const user = userEvent.setup() + render( + + {}} /> + , + ) + + await user.click(await screen.findByRole('button', { name: /Add/i })) + + await screen.findByLabelText(/social/i) + expect(screen.getByLabelText(/first name/i)).toBeInTheDocument() + expect(screen.getByLabelText(/last name/i)).toBeInTheDocument() + }) +}) diff --git a/src/components/Employee/EmployeeListFlow/EmployeeListFlowComponents.tsx b/src/components/Employee/EmployeeListFlow/EmployeeListFlowComponents.tsx index f7705aab4..5c85b712f 100644 --- a/src/components/Employee/EmployeeListFlow/EmployeeListFlowComponents.tsx +++ b/src/components/Employee/EmployeeListFlow/EmployeeListFlowComponents.tsx @@ -1,7 +1,7 @@ import { ManagementEmployeeList } from '../EmployeeList/management/ManagementEmployeeList' import { DashboardFlow } from '../Dashboard' import { TerminationFlow } from '../Terminations/TerminationFlow/TerminationFlow' -import { OnboardingFlow } from '../OnboardingFlow/OnboardingFlow' +import { OnboardingExecutionFlow } from '../OnboardingExecutionFlow/OnboardingExecutionFlow' import { useFlow, type FlowContextInterface } from '@/components/Flow/useFlow' import type { BaseComponentInterface } from '@/components/Base' import { ensureRequired } from '@/helpers/ensureRequired' @@ -36,7 +36,7 @@ export function TerminationFlowContextual() { ) } -export function OnboardingFlowContextual() { +export function OnboardingExecutionFlowContextual() { const { companyId, onEvent } = useFlow() - return + return } diff --git a/src/components/Employee/EmployeeListFlow/employeeListStateMachine.ts b/src/components/Employee/EmployeeListFlow/employeeListStateMachine.ts index 16de4e73c..08a83b0e3 100644 --- a/src/components/Employee/EmployeeListFlow/employeeListStateMachine.ts +++ b/src/components/Employee/EmployeeListFlow/employeeListStateMachine.ts @@ -2,7 +2,7 @@ import { transition, reduce, state } from 'robot3' import { DashboardFlowContextual, EmployeeListContextual, - OnboardingFlowContextual, + OnboardingExecutionFlowContextual, TerminationFlowContextual, type EmployeeListFlowContextInterface, } from './EmployeeListFlowComponents' @@ -71,7 +71,7 @@ export const employeeListStateMachine = { reduce( (ctx: EmployeeListFlowContextInterface): EmployeeListFlowContextInterface => ({ ...ctx, - component: OnboardingFlowContextual, + component: OnboardingExecutionFlowContextual, header: backToListHeader, }), ), @@ -85,5 +85,7 @@ export const employeeListStateMachine = { ), onboard: state( transition(componentEvents.EMPLOYEE_RETURN_TO_LIST, 'list', returnToList), + transition(componentEvents.EMPLOYEES_LIST, 'list', returnToList), + transition(componentEvents.CANCEL, 'list', returnToList), ), } diff --git a/src/components/Employee/OnboardingExecutionFlow/OnboardingExecutionFlow.tsx b/src/components/Employee/OnboardingExecutionFlow/OnboardingExecutionFlow.tsx new file mode 100644 index 000000000..73eabd753 --- /dev/null +++ b/src/components/Employee/OnboardingExecutionFlow/OnboardingExecutionFlow.tsx @@ -0,0 +1,69 @@ +import { createMachine } from 'robot3' +import { useMemo } from 'react' +import { + onboardingExecutionMachine, + INITIAL_COMPONENT_MAP, + type OnboardingExecutionInitialState, +} from './onboardingExecutionStateMachine' +import { + type OnboardingContextInterface, + type OnboardingDefaultValues, +} from './OnboardingExecutionFlowComponents' +import { Flow } from '@/components/Flow/Flow' +import type { OnEventType } from '@/components/Base/useBase' +import type { EventType, EmployeeOnboardingStatus } from '@/shared/constants' + +export interface OnboardingExecutionFlowProps { + companyId: string + onEvent: OnEventType + initialState?: OnboardingExecutionInitialState + initialEmployeeId?: string + initialOnboardingStatus?: (typeof EmployeeOnboardingStatus)[keyof typeof EmployeeOnboardingStatus] + defaultValues?: OnboardingDefaultValues + isAdmin?: boolean + isSelfOnboardingEnabled?: boolean + withEmployeeI9?: boolean +} + +export function OnboardingExecutionFlow({ + companyId, + onEvent, + initialState = 'employeeProfile', + initialEmployeeId, + initialOnboardingStatus, + defaultValues, + isAdmin = true, + isSelfOnboardingEnabled = true, + withEmployeeI9 = false, +}: OnboardingExecutionFlowProps) { + const machine = useMemo( + () => + createMachine( + initialState, + onboardingExecutionMachine, + (initialContext: OnboardingContextInterface) => ({ + ...initialContext, + component: INITIAL_COMPONENT_MAP[initialState], + companyId, + employeeId: initialEmployeeId, + onboardingStatus: initialOnboardingStatus, + defaultValues, + isAdmin, + isSelfOnboardingEnabled, + withEmployeeI9, + }), + ), + [ + companyId, + initialState, + initialEmployeeId, + initialOnboardingStatus, + defaultValues, + isAdmin, + isSelfOnboardingEnabled, + withEmployeeI9, + ], + ) + + return +} diff --git a/src/components/Employee/OnboardingExecutionFlow/OnboardingExecutionFlowComponents.tsx b/src/components/Employee/OnboardingExecutionFlow/OnboardingExecutionFlowComponents.tsx new file mode 100644 index 000000000..947c55253 --- /dev/null +++ b/src/components/Employee/OnboardingExecutionFlow/OnboardingExecutionFlowComponents.tsx @@ -0,0 +1,42 @@ +import type { PaymentMethodBankAccount } from '@gusto/embedded-api-v-2025-11-15/models/components/paymentmethodbankaccount' +import { Deductions } from '../Deductions/Deductions' +import { FederalTaxes } from '../FederalTaxes/onboarding/FederalTaxes' +import { StateTaxes } from '../StateTaxes/onboarding/StateTaxes' +import type { ProfileDefaultValues } from '../Profile/onboarding/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 + withEmployeeI9?: boolean +} + +export function FederalTaxesContextual() { + const { employeeId, onEvent } = useFlow() + return +} + +export function StateTaxesContextual() { + const { employeeId, onEvent, isAdmin } = useFlow() + return +} + +export function DeductionsContextual() { + const { employeeId, onEvent } = useFlow() + return +} diff --git a/src/components/Employee/OnboardingExecutionFlow/index.ts b/src/components/Employee/OnboardingExecutionFlow/index.ts new file mode 100644 index 000000000..d7fbb061b --- /dev/null +++ b/src/components/Employee/OnboardingExecutionFlow/index.ts @@ -0,0 +1,12 @@ +export { + OnboardingExecutionFlow, + type OnboardingExecutionFlowProps, +} from './OnboardingExecutionFlow' +export { + onboardingExecutionMachine, + type OnboardingExecutionInitialState, +} from './onboardingExecutionStateMachine' +export type { + OnboardingContextInterface, + OnboardingDefaultValues, +} from './OnboardingExecutionFlowComponents' diff --git a/src/components/Employee/OnboardingExecutionFlow/onboardingExecutionStateMachine.ts b/src/components/Employee/OnboardingExecutionFlow/onboardingExecutionStateMachine.ts new file mode 100644 index 000000000..80291e2fe --- /dev/null +++ b/src/components/Employee/OnboardingExecutionFlow/onboardingExecutionStateMachine.ts @@ -0,0 +1,142 @@ +import { transition, reduce, state, guard } from 'robot3' +import { + FederalTaxesContextual, + StateTaxesContextual, + DeductionsContextual, + type OnboardingContextInterface, +} from './OnboardingExecutionFlowComponents' +import { + componentEvents, + EmployeeSelfOnboardingStatuses, + EmployeeOnboardingStatus, +} from '@/shared/constants' +import type { MachineEventType, MachineTransition } from '@/types/Helpers' +import { CompensationContextual } from '@/components/Employee/Compensation' +import { EmployeeDocumentsContextual } from '@/components/Employee/Documents/onboarding/EmployeeDocuments' +import { PaymentMethodContextual } from '@/components/Employee/PaymentMethod' +import { ProfileContextual } from '@/components/Employee/Profile/onboarding/Profile' +import { OnboardingSummaryContextual } from '@/components/Employee/OnboardingSummary' + +type EventPayloads = { + [componentEvents.EMPLOYEE_PROFILE_DONE]: { + uuid: string + onboardingStatus: (typeof EmployeeOnboardingStatus)[keyof typeof EmployeeOnboardingStatus] + startDate: string + } +} + +const createReducer = (props: Partial) => { + return (ctx: OnboardingContextInterface): OnboardingContextInterface => ({ + ...ctx, + ...props, + }) +} + +const employeeDocumentsConfigCompletedStatuses: Set< + (typeof EmployeeOnboardingStatus)[keyof typeof EmployeeOnboardingStatus] +> = new Set([ + EmployeeOnboardingStatus.SELF_ONBOARDING_COMPLETED_BY_EMPLOYEE, + EmployeeOnboardingStatus.SELF_ONBOARDING_AWAITING_ADMIN_REVIEW, + EmployeeOnboardingStatus.ONBOARDING_COMPLETED, +]) + +const employeeDocumentsGuard = (ctx: OnboardingContextInterface) => { + if (!ctx.withEmployeeI9) return false + if (ctx.onboardingStatus && employeeDocumentsConfigCompletedStatuses.has(ctx.onboardingStatus)) + return false + return true +} + +const selfOnboardingGuard = (ctx: OnboardingContextInterface) => + ctx.onboardingStatus + ? !( + // prettier-ignore + // @ts-expect-error: onboarding_status during runtime can be one of self onboarding statuses + (EmployeeSelfOnboardingStatuses.has(ctx.onboardingStatus) || + ctx.onboardingStatus === EmployeeOnboardingStatus.SELF_ONBOARDING_PENDING_INVITE) + ) + : true + +export const onboardingExecutionMachine = { + employeeProfile: state( + transition( + componentEvents.EMPLOYEE_PROFILE_DONE, + 'compensation', + reduce( + ( + ctx: OnboardingContextInterface, + ev: MachineEventType, + ): OnboardingContextInterface => ({ + ...ctx, + component: CompensationContextual, + employeeId: ev.payload.uuid, + onboardingStatus: ev.payload.onboardingStatus, + startDate: ev.payload.startDate, + }), + ), + ), + ), + compensation: state( + transition( + componentEvents.EMPLOYEE_COMPENSATION_DONE, + 'federalTaxes', + reduce(createReducer({ component: FederalTaxesContextual })), + guard(selfOnboardingGuard), + ), + transition( + componentEvents.EMPLOYEE_COMPENSATION_DONE, + 'deductions', + reduce(createReducer({ component: DeductionsContextual })), + ), + ), + federalTaxes: state( + transition( + componentEvents.EMPLOYEE_FEDERAL_TAXES_DONE, + 'stateTaxes', + reduce(createReducer({ component: StateTaxesContextual })), + guard(selfOnboardingGuard), + ), + ), + stateTaxes: state( + transition( + componentEvents.EMPLOYEE_STATE_TAXES_DONE, + 'paymentMethod', + reduce(createReducer({ component: PaymentMethodContextual })), + guard(selfOnboardingGuard), + ), + ), + paymentMethod: state( + transition( + componentEvents.EMPLOYEE_PAYMENT_METHOD_DONE, + 'deductions', + reduce(createReducer({ component: DeductionsContextual })), + ), + ), + deductions: state( + transition( + componentEvents.EMPLOYEE_DEDUCTION_DONE, + 'employeeDocuments', + reduce(createReducer({ component: EmployeeDocumentsContextual })), + guard(employeeDocumentsGuard), + ), + transition( + componentEvents.EMPLOYEE_DEDUCTION_DONE, + 'summary', + reduce(createReducer({ component: OnboardingSummaryContextual })), + ), + ), + employeeDocuments: state( + transition( + componentEvents.EMPLOYEE_DOCUMENTS_DONE, + 'summary', + reduce(createReducer({ component: OnboardingSummaryContextual })), + ), + ), + summary: state(), +} + +export const INITIAL_COMPONENT_MAP = { + employeeProfile: ProfileContextual, +} as const + +export type OnboardingExecutionInitialState = keyof typeof INITIAL_COMPONENT_MAP diff --git a/src/components/Employee/OnboardingFlow/OnboardingFlowComponents.tsx b/src/components/Employee/OnboardingFlow/OnboardingFlowComponents.tsx index 45c119737..ed009ac62 100644 --- a/src/components/Employee/OnboardingFlow/OnboardingFlowComponents.tsx +++ b/src/components/Employee/OnboardingFlow/OnboardingFlowComponents.tsx @@ -1,48 +1,46 @@ -import type { PaymentMethodBankAccount } from '@gusto/embedded-api-v-2025-11-15/models/components/paymentmethodbankaccount' -import { Deductions } from '../Deductions/Deductions' -import { FederalTaxes } from '../FederalTaxes/onboarding/FederalTaxes' -import { StateTaxes } from '../StateTaxes/onboarding/StateTaxes' -import type { ProfileDefaultValues } from '../Profile/onboarding/Profile' -import type { CompensationDefaultValues } from '../Compensation' +import { OnboardingExecutionFlow } from '../OnboardingExecutionFlow/OnboardingExecutionFlow' import { EmployeeList } from '../EmployeeList/onboarding/EmployeeList' +import type { OnboardingContextInterface } from '../OnboardingExecutionFlow/OnboardingExecutionFlowComponents' +import { useFlow } from '@/components/Flow/useFlow' 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 - withEmployeeI9?: boolean -} - -export function FederalTaxesContextual() { - const { employeeId, onEvent } = useFlow() - return -} - -export function StateTaxesContextual() { - const { employeeId, onEvent, isAdmin } = useFlow() - return -} +export type { + OnboardingContextInterface, + OnboardingDefaultValues, +} from '../OnboardingExecutionFlow/OnboardingExecutionFlowComponents' +export { + FederalTaxesContextual, + StateTaxesContextual, + DeductionsContextual, +} from '../OnboardingExecutionFlow/OnboardingExecutionFlowComponents' export const EmployeeListContextual = () => { const { companyId, onEvent } = useFlow() - return + return } -export function DeductionsContextual() { - const { employeeId, onEvent } = useFlow() - return +export function OnboardingExecutionFlowContextual() { + const { + companyId, + employeeId, + onEvent, + onboardingStatus, + defaultValues, + isAdmin, + isSelfOnboardingEnabled, + withEmployeeI9, + } = useFlow() + + return ( + + ) } diff --git a/src/components/Employee/OnboardingFlow/onboardingStateMachine.ts b/src/components/Employee/OnboardingFlow/onboardingStateMachine.ts index 53be31fd9..972005ae7 100644 --- a/src/components/Employee/OnboardingFlow/onboardingStateMachine.ts +++ b/src/components/Employee/OnboardingFlow/onboardingStateMachine.ts @@ -1,188 +1,61 @@ -import { transition, reduce, state, guard } from 'robot3' +import { transition, reduce, state } from 'robot3' import { - FederalTaxesContextual, - StateTaxesContextual, - type OnboardingContextInterface, EmployeeListContextual, + OnboardingExecutionFlowContextual, + type OnboardingContextInterface, } from './OnboardingFlowComponents' -import { - componentEvents, - EmployeeSelfOnboardingStatuses, - EmployeeOnboardingStatus, -} from '@/shared/constants' +import { componentEvents, type EmployeeOnboardingStatus } from '@/shared/constants' import type { MachineEventType, MachineTransition } from '@/types/Helpers' -import { CompensationContextual } from '@/components/Employee/Compensation' -import { DeductionsContextual } from '@/components/Employee/OnboardingFlow/OnboardingFlowComponents' -import { EmployeeDocumentsContextual } from '@/components/Employee/Documents/onboarding/EmployeeDocuments' -import { PaymentMethodContextual } from '@/components/Employee/PaymentMethod' -import { ProfileContextual } from '@/components/Employee/Profile/onboarding/Profile' -import { OnboardingSummaryContextual } from '@/components/Employee/OnboardingSummary' type EventPayloads = { [componentEvents.EMPLOYEE_UPDATE]: { employeeId: string onboardingStatus: (typeof EmployeeOnboardingStatus)[keyof typeof EmployeeOnboardingStatus] } - [componentEvents.EMPLOYEE_PROFILE_DONE]: { - uuid: string - onboardingStatus: (typeof EmployeeOnboardingStatus)[keyof typeof EmployeeOnboardingStatus] - startDate: string - } } -const createReducer = (props: Partial) => { - return (ctx: OnboardingContextInterface): OnboardingContextInterface => ({ +const returnToIndex = reduce( + (ctx: OnboardingContextInterface): OnboardingContextInterface => ({ ...ctx, - ...props, - }) -} - -const cancelTransition = (target: string, component?: React.ComponentType) => - transition( - componentEvents.CANCEL, - target, - reduce(createReducer({ component: component ?? EmployeeListContextual })), - ) - -const employeeDocumentsConfigCompletedStatuses: Set< - (typeof EmployeeOnboardingStatus)[keyof typeof EmployeeOnboardingStatus] -> = new Set([ - EmployeeOnboardingStatus.SELF_ONBOARDING_COMPLETED_BY_EMPLOYEE, - EmployeeOnboardingStatus.SELF_ONBOARDING_AWAITING_ADMIN_REVIEW, - EmployeeOnboardingStatus.ONBOARDING_COMPLETED, -]) - -const employeeDocumentsGuard = (ctx: OnboardingContextInterface) => { - if (!ctx.withEmployeeI9) return false - if (ctx.onboardingStatus && employeeDocumentsConfigCompletedStatuses.has(ctx.onboardingStatus)) - return false - return true -} - -const selfOnboardingGuard = (ctx: OnboardingContextInterface) => - ctx.onboardingStatus - ? !( - // prettier-ignore - // @ts-expect-error: onboarding_status during runtime can be one of self onboarding statuses - (EmployeeSelfOnboardingStatuses.has(ctx.onboardingStatus) || - ctx.onboardingStatus === EmployeeOnboardingStatus.SELF_ONBOARDING_PENDING_INVITE) - ) - : true + component: EmployeeListContextual, + employeeId: undefined, + onboardingStatus: undefined, + }), +) export const employeeOnboardingMachine = { index: state( transition( componentEvents.EMPLOYEE_CREATE, - 'employeeProfile', - reduce(createReducer({ component: ProfileContextual, employeeId: undefined })), - ), - transition( - componentEvents.EMPLOYEE_UPDATE, - 'employeeProfile', - + 'executing', reduce( - ( - ctx: OnboardingContextInterface, - ev: MachineEventType, - ): OnboardingContextInterface => { - return { - ...ctx, - component: ProfileContextual, - employeeId: ev.payload.employeeId, - onboardingStatus: ev.payload.onboardingStatus, - } - }, + (ctx: OnboardingContextInterface): OnboardingContextInterface => ({ + ...ctx, + component: OnboardingExecutionFlowContextual, + employeeId: undefined, + }), ), ), - transition(componentEvents.EMPLOYEE_ONBOARDING_DONE, 'final'), - ), - employeeProfile: state( transition( - componentEvents.EMPLOYEE_PROFILE_DONE, - 'compensation', + componentEvents.EMPLOYEE_UPDATE, + 'executing', reduce( ( ctx: OnboardingContextInterface, - ev: MachineEventType, + ev: MachineEventType, ): OnboardingContextInterface => ({ ...ctx, - component: CompensationContextual, - employeeId: ev.payload.uuid, + component: OnboardingExecutionFlowContextual, + employeeId: ev.payload.employeeId, onboardingStatus: ev.payload.onboardingStatus, - startDate: ev.payload.startDate, }), ), ), - cancelTransition('index'), - ), - compensation: state( - transition( - componentEvents.EMPLOYEE_COMPENSATION_DONE, - 'federalTaxes', - reduce(createReducer({ component: FederalTaxesContextual })), - guard(selfOnboardingGuard), - ), - transition( - componentEvents.EMPLOYEE_COMPENSATION_DONE, - 'deductions', - reduce(createReducer({ component: DeductionsContextual })), - ), - cancelTransition('index'), - ), - federalTaxes: state( - transition( - componentEvents.EMPLOYEE_FEDERAL_TAXES_DONE, - 'stateTaxes', - reduce(createReducer({ component: StateTaxesContextual })), - guard(selfOnboardingGuard), - ), - cancelTransition('index'), - ), - stateTaxes: state( - transition( - componentEvents.EMPLOYEE_STATE_TAXES_DONE, - 'paymentMethod', - reduce(createReducer({ component: PaymentMethodContextual })), - guard(selfOnboardingGuard), - ), - cancelTransition('index'), - ), - paymentMethod: state( - transition( - componentEvents.EMPLOYEE_PAYMENT_METHOD_DONE, - 'deductions', - reduce(createReducer({ component: DeductionsContextual })), - ), - cancelTransition('index'), - ), - deductions: state( - transition( - componentEvents.EMPLOYEE_DEDUCTION_DONE, - 'employeeDocuments', - reduce(createReducer({ component: EmployeeDocumentsContextual })), - guard(employeeDocumentsGuard), - ), - transition( - componentEvents.EMPLOYEE_DEDUCTION_DONE, - 'summary', - reduce(createReducer({ component: OnboardingSummaryContextual })), - ), - cancelTransition('index'), - ), - employeeDocuments: state( - transition( - componentEvents.EMPLOYEE_DOCUMENTS_DONE, - 'summary', - reduce(createReducer({ component: OnboardingSummaryContextual })), - ), - cancelTransition('index'), + transition(componentEvents.EMPLOYEE_ONBOARDING_DONE, 'final'), ), - summary: state( - transition( - componentEvents.EMPLOYEES_LIST, - 'index', - reduce(createReducer({ component: EmployeeListContextual, employeeId: undefined })), - ), + executing: state( + transition(componentEvents.EMPLOYEES_LIST, 'index', returnToIndex), + transition(componentEvents.CANCEL, 'index', returnToIndex), ), final: state(), } diff --git a/src/components/Employee/exports/employeeOnboarding.ts b/src/components/Employee/exports/employeeOnboarding.ts index f105f7bcc..4ad610413 100644 --- a/src/components/Employee/exports/employeeOnboarding.ts +++ b/src/components/Employee/exports/employeeOnboarding.ts @@ -1,4 +1,9 @@ export { OnboardingFlow } from '../OnboardingFlow/OnboardingFlow' +export { + OnboardingExecutionFlow, + type OnboardingExecutionFlowProps, + type OnboardingExecutionInitialState, +} from '../OnboardingExecutionFlow' export { SelfOnboardingFlow } from '../SelfOnboardingFlow/SelfOnboardingFlow' export { EmployeeList } from '../EmployeeList/onboarding/EmployeeList' export { OnboardingSummary } from '../OnboardingSummary' diff --git a/src/components/Employee/index.ts b/src/components/Employee/index.ts index 3e9a1da58..cb5945942 100644 --- a/src/components/Employee/index.ts +++ b/src/components/Employee/index.ts @@ -10,6 +10,11 @@ export { PaymentMethod } from './PaymentMethod' export { Landing } from './Landing' export { DocumentSigner } from './Documents/onboarding/DocumentSigner' export { OnboardingFlow } from './OnboardingFlow/OnboardingFlow' +export { + OnboardingExecutionFlow, + type OnboardingExecutionFlowProps, + type OnboardingExecutionInitialState, +} from './OnboardingExecutionFlow' export { SelfOnboardingFlow } from './SelfOnboardingFlow/SelfOnboardingFlow' export { EmployeeDocuments } from './Documents/onboarding/EmployeeDocuments' export { DashboardFlow } from './Dashboard'