From fb9173d613690127ec5e97487feb2a35a296701b Mon Sep 17 00:00:00 2001 From: Marie Chatfield Date: Thu, 23 Jul 2026 09:58:29 -0700 Subject: [PATCH 01/10] fix(e2e): assert submission completion instead of a coincidental heading match `assertPaymentSummary` and the duplicate check in 93-payment-submit.spec.ts asserted on "Payment summary," which also appears on the review page's own totals grid, so the assertion could pass before submission actually completed. Assert the review heading disappears instead. Also give FlowRenderer's onEvent handler a real state update so e2e exercises the same re-render churn a partner app produces, instead of masking bugs behind a no-op handler. --- e2e/main.tsx | 9 +++++++-- e2e/tests/contractor/93-payment-submit.spec.ts | 4 +++- e2e/utils/contractorFlowDrivers.ts | 4 +++- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/e2e/main.tsx b/e2e/main.tsx index 91cc2d9aa5..f343f96cf7 100644 --- a/e2e/main.tsx +++ b/e2e/main.tsx @@ -1,4 +1,4 @@ -import { StrictMode } from 'react' +import { StrictMode, useState } from 'react' import { createRoot } from 'react-dom/client' import { GustoProvider } from '@/contexts' import { OnboardingFlow } from '@/components/Employee/OnboardingFlow/OnboardingFlow' @@ -71,7 +71,12 @@ function getConfigFromUrl(): E2EConfig { function FlowRenderer({ config }: { config: E2EConfig }) { const { flow, companyId, employeeId, contractorId, startDate, endDate, payScheduleUuid, state } = config - const handleEvent = () => {} + // Real partner apps may update their own state in response to SDK events, which could + // re-render the SDK's ancestor tree on every event, the same way sdk-app's event log does. + // A no-op handler here would mask bugs that only surface under that re-render churn + // (e.g. unmemoized props breaking a nested flow's useMemo). + const [, setEventCount] = useState(0) + const handleEvent = () => setEventCount(count => count + 1) switch (flow) { case 'employee-onboarding': diff --git a/e2e/tests/contractor/93-payment-submit.spec.ts b/e2e/tests/contractor/93-payment-submit.spec.ts index 25f5b23b31..ef06ce0142 100644 --- a/e2e/tests/contractor/93-payment-submit.spec.ts +++ b/e2e/tests/contractor/93-payment-submit.spec.ts @@ -18,7 +18,9 @@ test.describe.serial('ContractorCanary 03 — contractor payment end-to-end', () await runContractorPayment(page, scenario) - await expect(page.getByRole('heading', { name: /^payment summary$/i })).toBeVisible({ + // "Payment summary" is also the review page's own totals-grid heading, so + // asserting on it alone can pass before submission actually completes. + await expect(page.getByRole('heading', { name: /^review and submit$/i })).toBeHidden({ timeout: 60_000, }) }) diff --git a/e2e/utils/contractorFlowDrivers.ts b/e2e/utils/contractorFlowDrivers.ts index 72d2edafe3..e5b50bf4ff 100644 --- a/e2e/utils/contractorFlowDrivers.ts +++ b/e2e/utils/contractorFlowDrivers.ts @@ -574,7 +574,9 @@ async function reviewAndSubmitPayment(page: Page): Promise { } async function assertPaymentSummary(page: Page): Promise { - await expect(page.getByRole('heading', { name: /^payment summary$/i })).toBeVisible({ + // "Payment summary" is also the review page's own totals-grid heading, so + // asserting on it alone can pass before submission actually completes. + await expect(page.getByRole('heading', { name: /^review and submit$/i })).toBeHidden({ timeout: LONG_WAIT, }) await expect( From 2f3a2910941451b1b61e0166605a6e0b77d7b4fd Mon Sep 17 00:00:00 2001 From: Marie Chatfield Date: Thu, 23 Jul 2026 09:59:17 -0700 Subject: [PATCH 02/10] test(PaymentFlow): add flow-integration tests ahead of the hub+spoke refactor (SDK-1127) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Renders the full PaymentFlow tree — the only test layer that can catch a wiring-level bug between the hub and a future spoke, unlike the machine unit tests or e2e's happy-path-only coverage. Covers the history -> statement -> breadcrumb-back -> cancel path, breadcrumb navigation back to the list from both history and createPayment, and a forced parent re-render mid-flow, so the upcoming CreatePaymentFlow/ViewHistoryFlow split has a regression net before any code moves. --- .../Payments/PaymentFlow/PaymentFlow.test.tsx | 181 ++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 src/components/Contractor/Payments/PaymentFlow/PaymentFlow.test.tsx diff --git a/src/components/Contractor/Payments/PaymentFlow/PaymentFlow.test.tsx b/src/components/Contractor/Payments/PaymentFlow/PaymentFlow.test.tsx new file mode 100644 index 0000000000..bb3571d9c1 --- /dev/null +++ b/src/components/Contractor/Payments/PaymentFlow/PaymentFlow.test.tsx @@ -0,0 +1,181 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import { useState } from 'react' +import { http, HttpResponse } from 'msw' +import { PaymentFlow } from './PaymentFlow' +import type { PaymentFlowProps } from './PaymentFlowComponents' +import { server } from '@/test/mocks/server' +import { setupApiTestMocks } from '@/test/mocks/apiServer' +import { renderWithProviders } from '@/test-utils/renderWithProviders' +import { API_BASE_URL } from '@/test/constants' +import { handleGetContractorPaymentGroup } from '@/test/mocks/apis/contractor_payment_groups' +import { componentEvents } from '@/shared/constants' + +const COMPANY_ID = 'company-123' + +/** + * `CreatePayment` (reached via the createPayment breadcrumb test) queries payment + * speed and the contractor payment receipt; neither has a default handler in + * `handlers.ts`, so both need an explicit mock to avoid an unhandled request. + */ +const paymentConfigsMock = http.get( + `${API_BASE_URL}/v1/companies/:company_uuid/payment_configs`, + () => HttpResponse.json({ payment_speed: '2-day', fast_payment_limit: 5000000 }), +) + +const contractorPaymentReceiptNotAvailable = http.get( + `${API_BASE_URL}/v1/contractor_payments/:contractor_payment_uuid/receipt`, + () => new HttpResponse(null, { status: 404 }), +) + +/** + * Overrides the default `getContractorPaymentGroup` fixture with a contractor + * payment that carries `may_cancel: true`, so the history row's action menu + * surfaces both "View" and "Cancel payment". + */ +const paymentGroupWithCancelableContractorPayment = handleGetContractorPaymentGroup(() => + HttpResponse.json({ + uuid: 'payment-group-123', + company_uuid: '123', + check_date: '2025-01-30', + debit_date: '2025-01-28', + status: 'Funded', + contractor_payments: [ + { + uuid: 'contractor-payment-1', + contractor_uuid: 'contractor-123', + wage_type: 'Hourly', + hourly_rate: '50.00', + hours: '10.00', + bonus: '0.00', + reimbursement: '0.00', + wage_total: '500.00', + payment_method: 'Direct Deposit', + status: 'Funded', + may_cancel: true, + }, + ], + totals: { + wages: '500.00', + reimbursements: '0.00', + benefits: '0.00', + taxes: '0.00', + net_pay: '500.00', + debit_amount: '500.00', + }, + }), +) + +/** + * Wraps `PaymentFlow` behind a sibling button that forces a parent re-render. + * Simulates a partner app re-rendering its own tree on every bubbled `onEvent` + * call -- independent of anything `PaymentFlow` itself controls -- to check + * that the flow's in-progress state survives being re-rendered from above. + */ +function PaymentFlowHost({ onEvent }: { onEvent: PaymentFlowProps['onEvent'] }) { + const [, setTick] = useState(0) + return ( +
+ + +
+ ) +} + +describe('PaymentFlow', () => { + beforeEach(() => { + setupApiTestMocks() + server.use(paymentConfigsMock, contractorPaymentReceiptNotAvailable) + }) + + it('walks from the payments list through history and a payment statement, back via breadcrumb, then cancels', async () => { + const user = userEvent.setup() + const onEvent = vi.fn() + server.use(paymentGroupWithCancelableContractorPayment) + + renderWithProviders() + + // Landing -> History + await user.click(await screen.findByRole('button', { name: 'View payment' })) + await screen.findByRole('heading', { name: 'Contractor payment history' }) + expect(onEvent).toHaveBeenCalledWith(componentEvents.CONTRACTOR_PAYMENT_VIEW, { + paymentId: 'payment-group-123', + }) + + // History -> Statement + await user.click(screen.getByRole('button', { name: 'Action' })) + await user.click(await screen.findByRole('menuitem', { name: 'View' })) + await screen.findByRole('heading', { name: 'Payment statement for John Contractor' }) + expect(onEvent).toHaveBeenCalledWith( + componentEvents.CONTRACTOR_PAYMENT_VIEW_DETAILS, + expect.objectContaining({ paymentGroupId: 'payment-group-123' }), + ) + + // Statement -> History, via the "Payment history" breadcrumb + await user.click(screen.getByRole('button', { name: 'Payment history' })) + await screen.findByRole('heading', { name: 'Contractor payment history' }) + + // History -> Landing, via cancelling the payment + await user.click(screen.getByRole('button', { name: 'Action' })) + await user.click(await screen.findByRole('menuitem', { name: 'Cancel payment' })) + + await screen.findByRole('heading', { name: 'Contractor payments' }) + expect(await screen.findByText('Contractor payment cancelled successfully')).toBeInTheDocument() + expect(onEvent).toHaveBeenCalledWith(componentEvents.CONTRACTOR_PAYMENT_CANCEL, { + paymentId: 'contractor-payment-1', + }) + }) + + it('returns to the payments list when the breadcrumb is clicked from history', async () => { + const user = userEvent.setup() + const onEvent = vi.fn() + server.use(paymentGroupWithCancelableContractorPayment) + + renderWithProviders() + + await user.click(await screen.findByRole('button', { name: 'View payment' })) + await screen.findByRole('heading', { name: 'Contractor payment history' }) + + await user.click(screen.getByRole('button', { name: 'Contractor payments' })) + + await screen.findByRole('heading', { name: 'Contractor payments' }) + expect(screen.getByRole('button', { name: 'View payment' })).toBeInTheDocument() + }) + + it('returns to the payments list when the breadcrumb is clicked from createPayment', async () => { + const user = userEvent.setup() + const onEvent = vi.fn() + + renderWithProviders() + + await screen.findByRole('heading', { name: 'Contractor payments' }) + await user.click(screen.getByRole('button', { name: 'New payment' })) + + await screen.findByRole('heading', { name: 'Pay contractors' }) + await user.click(screen.getByRole('button', { name: 'Contractor payments' })) + + await screen.findByRole('heading', { name: 'Contractor payments' }) + }) + + it("does not reset the flow's current step when the partner app re-renders its own tree mid-flow", async () => { + const user = userEvent.setup() + const onEvent = vi.fn() + server.use(paymentGroupWithCancelableContractorPayment) + + renderWithProviders() + + await user.click(await screen.findByRole('button', { name: 'View payment' })) + await screen.findByRole('heading', { name: 'Contractor payment history' }) + + await user.click(screen.getByRole('button', { name: 'force parent re-render' })) + + expect(screen.getByRole('heading', { name: 'Contractor payment history' })).toBeInTheDocument() + }) +}) From 66f953eaf6313bb8c2b216722e7e70f1413e534a Mon Sep 17 00:00:00 2001 From: Marie Chatfield Date: Thu, 23 Jul 2026 10:37:52 -0700 Subject: [PATCH 03/10] refactor(PaymentFlow): extract CreatePaymentFlow and ViewHistoryFlow spokes (SDK-1127) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Carves the create-payment and view-history steps out of PaymentFlow into their own hub+spoke flows, mirroring the PayrollFlow/PayrollExecutionFlow pattern: each spoke owns its own robot3 machine and breadcrumb trail, prefixed with the hub's landing breadcrumb, and bubbles terminal events (exit, cancel, landing navigation) back up to the hub. Prepares the fourth spoke slot for the upcoming HistoricalPaymentFlow. Pure structural refactor — no behavior change, all events unchanged; the flow-integration and machine tests added alongside this confirm the split preserves the flat machine's exact transitions. Each spoke builds its machine via a lazy useState initializer rather than useMemo, and passes prefixBreadcrumbs straight into its trail without stripping onNavigate — both fix real regressions found in an earlier attempt at this same split (a useMemo keyed on an unmemoized breadcrumbs array silently reset in-flight flow state on re-render; stripped onNavigate broke "click breadcrumb to return to the hub"). CreatePaymentFlow and ViewHistoryFlow are marked @alpha rather than promoted to public: PaymentFlow keeps composing them internally, but partners on the public surface still see the underlying blocks (CreatePayment, PaymentSummary, PaymentHistory, PaymentStatement) documented as before. Co-Authored-By: Claude Sonnet 5 --- .../CreatePaymentFlow/CreatePaymentFlow.tsx | 118 ++++++++++++ .../CreatePaymentFlowComponents.tsx | 44 +++++ .../Payments/CreatePaymentFlow/GUIDE.md | 16 ++ .../createPaymentMachine.test.ts | 78 ++++++++ .../CreatePaymentFlow/createPaymentMachine.ts | 91 +++++++++ .../Payments/CreatePaymentFlow/index.ts | 2 + .../PaymentFlow/PaymentFlowComponents.tsx | 49 ++--- .../PaymentFlow/paymentStateMachine.test.ts | 173 ++++++++++++++++++ .../PaymentFlow/paymentStateMachine.ts | 165 ++++------------- .../Payments/ViewHistoryFlow/GUIDE.md | 16 ++ .../ViewHistoryFlow/ViewHistoryFlow.tsx | 116 ++++++++++++ .../ViewHistoryFlowComponents.tsx | 40 ++++ .../Payments/ViewHistoryFlow/index.ts | 2 + .../viewHistoryMachine.test.ts | 91 +++++++++ .../ViewHistoryFlow/viewHistoryMachine.ts | 76 ++++++++ .../exports/contractorManagement.ts | 2 + 16 files changed, 916 insertions(+), 163 deletions(-) create mode 100644 src/components/Contractor/Payments/CreatePaymentFlow/CreatePaymentFlow.tsx create mode 100644 src/components/Contractor/Payments/CreatePaymentFlow/CreatePaymentFlowComponents.tsx create mode 100644 src/components/Contractor/Payments/CreatePaymentFlow/GUIDE.md create mode 100644 src/components/Contractor/Payments/CreatePaymentFlow/createPaymentMachine.test.ts create mode 100644 src/components/Contractor/Payments/CreatePaymentFlow/createPaymentMachine.ts create mode 100644 src/components/Contractor/Payments/CreatePaymentFlow/index.ts create mode 100644 src/components/Contractor/Payments/PaymentFlow/paymentStateMachine.test.ts create mode 100644 src/components/Contractor/Payments/ViewHistoryFlow/GUIDE.md create mode 100644 src/components/Contractor/Payments/ViewHistoryFlow/ViewHistoryFlow.tsx create mode 100644 src/components/Contractor/Payments/ViewHistoryFlow/ViewHistoryFlowComponents.tsx create mode 100644 src/components/Contractor/Payments/ViewHistoryFlow/index.ts create mode 100644 src/components/Contractor/Payments/ViewHistoryFlow/viewHistoryMachine.test.ts create mode 100644 src/components/Contractor/Payments/ViewHistoryFlow/viewHistoryMachine.ts diff --git a/src/components/Contractor/Payments/CreatePaymentFlow/CreatePaymentFlow.tsx b/src/components/Contractor/Payments/CreatePaymentFlow/CreatePaymentFlow.tsx new file mode 100644 index 0000000000..821c9fbda1 --- /dev/null +++ b/src/components/Contractor/Payments/CreatePaymentFlow/CreatePaymentFlow.tsx @@ -0,0 +1,118 @@ +import { createMachine } from 'robot3' +import { useState } from 'react' +import { createPaymentBreadcrumbsNodes, createPaymentMachine } from './createPaymentMachine' +import { + CreatePaymentContextual, + type CreatePaymentFlowContextInterface, + type CreatePaymentFlowProps, +} from './CreatePaymentFlowComponents' +import { Flow } from '@/components/Flow/Flow' +import type { FlowBreadcrumb } from '@/components/Common/FlowBreadcrumbs/FlowBreadcrumbsTypes' +import { buildBreadcrumbs, updateBreadcrumbs } from '@/helpers/breadcrumbHelpers' + +const EMPTY_BREADCRUMBS: FlowBreadcrumb[] = [] + +/** + * Props for the flow-internal {@link CreatePaymentInternalFlow}, which layers a parent flow's + * prefix breadcrumbs on top of the public {@link CreatePaymentFlowProps}. + * + * @internal + */ +export interface CreatePaymentInternalFlowProps extends CreatePaymentFlowProps { + /** + * Breadcrumbs prepended to the flow's own breadcrumb trail. Set by a parent flow (e.g. + * `PaymentFlow`) so the breadcrumb history remains coherent across the handoff. + */ + prefixBreadcrumbs?: FlowBreadcrumb[] +} + +/** + * Guided flow to create a contractor payment and review the resulting summary. + * + * @remarks + * This is the inner flow that powers the create-payment spoke of `ContractorManagement.PaymentFlow`. + * Render it directly when you have built your own payments landing page and want to hand the user + * off to the standard create-payment experience without re-implementing it. The flow ships with + * breadcrumb navigation and handles Fast ACH blockers and wire transfer requirements inline. + * + * @events + * | Event | Description | Data | + * | ----- | ----------- | ---- | + * | `contractor/payments/created` | Fired when a payment group is successfully created | The created `ContractorPaymentGroup` | + * | `contractor/payments/exit` | Fired when the user completes the payment flow | `{ uuid?: string \| null }` | + * | `payroll/wire/form/done` | Fired when wire transfer details are submitted | `{ wireInRequest: WireInRequest, confirmationAlert: { title: string, content?: string } }` | + * | `breadcrumb/navigate` | Fired when the user clicks a breadcrumb to navigate back | `{ key: string, onNavigate: (ctx) => ctx }` | + * + * @components + * - {@link CreatePayment} + * - {@link PaymentSummary} + * + * @param props - See {@link CreatePaymentFlowProps}. + * @returns The composed create-payment flow. + * @alpha + * + * @example + * ```tsx title="App.tsx" + * import { ContractorManagement } from '@gusto/embedded-react-sdk' + * + * function MyApp() { + * return ( + * {}} + * /> + * ) + * } + * ``` + */ +export function CreatePaymentFlow(props: CreatePaymentFlowProps) { + return +} + +/** + * Flow-internal entry point for {@link CreatePaymentFlow} that additionally accepts + * flow-injected `prefixBreadcrumbs`. Partners use {@link CreatePaymentFlow}; `PaymentFlow` renders + * this directly to prepend its own breadcrumb trail. + * + * @internal + */ +export function CreatePaymentInternalFlow({ + companyId, + onEvent, + prefixBreadcrumbs = EMPTY_BREADCRUMBS, +}: CreatePaymentInternalFlowProps) { + // Built once via a lazy useState initializer, not useMemo: the machine's identity must survive + // re-renders no matter what, and useMemo is only a performance hint React may discard, not an + // identity guarantee. A useMemo keyed on `prefixBreadcrumbs` previously recreated this machine + // (and reset in-flight state) whenever the parent app re-rendered in response to a bubbled + // `onEvent` call, since an inline array literal upstream gets a new reference every render. + const [createPaymentFlow] = useState(() => { + const baseBreadcrumbs = buildBreadcrumbs(createPaymentBreadcrumbsNodes) + const breadcrumbs = Object.fromEntries( + Object.entries(baseBreadcrumbs).map(([stateKey, trail]) => [ + stateKey, + [...prefixBreadcrumbs, ...trail], + ]), + ) + + const initialBreadcrumbContext = updateBreadcrumbs('createPayment', { + header: { + type: 'breadcrumbs' as const, + breadcrumbs, + }, + }) + + return createMachine( + 'createPayment', + createPaymentMachine, + (initialContext: CreatePaymentFlowContextInterface) => ({ + ...initialContext, + ...initialBreadcrumbContext, + component: CreatePaymentContextual, + companyId, + }), + ) + }) + + return +} diff --git a/src/components/Contractor/Payments/CreatePaymentFlow/CreatePaymentFlowComponents.tsx b/src/components/Contractor/Payments/CreatePaymentFlow/CreatePaymentFlowComponents.tsx new file mode 100644 index 0000000000..bc2c1301c5 --- /dev/null +++ b/src/components/Contractor/Payments/CreatePaymentFlow/CreatePaymentFlowComponents.tsx @@ -0,0 +1,44 @@ +import { CreatePayment } from '../CreatePayment/CreatePayment' +import { PaymentSummaryInternal } from '../PaymentSummary/PaymentSummary' +import type { InternalAlert } from '../types' +import { useFlow, type FlowContextInterface } from '@/components/Flow/useFlow' +import type { BaseComponentInterface } from '@/components/Base' +import { ensureRequired } from '@/helpers/ensureRequired' + +/** + * Props for {@link CreatePaymentFlow}. + * + * @alpha + */ +export interface CreatePaymentFlowProps extends BaseComponentInterface { + /** The associated company identifier. */ + companyId: string +} + +/** @internal */ +export interface CreatePaymentFlowContextInterface extends FlowContextInterface { + companyId: string + createdPaymentGroupId?: string + alerts?: InternalAlert[] +} + +/** @internal */ +export function CreatePaymentContextual() { + const { companyId, onEvent } = useFlow() + return +} + +/** @internal */ +export function PaymentSummaryContextual() { + const { createdPaymentGroupId, companyId, onEvent, alerts } = + useFlow() + + return ( + + ) +} diff --git a/src/components/Contractor/Payments/CreatePaymentFlow/GUIDE.md b/src/components/Contractor/Payments/CreatePaymentFlow/GUIDE.md new file mode 100644 index 0000000000..6028134882 --- /dev/null +++ b/src/components/Contractor/Payments/CreatePaymentFlow/GUIDE.md @@ -0,0 +1,16 @@ + + +# CreatePaymentFlow + +## Step flow + +`CreatePaymentFlow` has no hub of its own — it's a straight line from creating a payment to reviewing the result. `CreatePayment` handles selecting a date, editing per-contractor amounts, and submitting; Fast ACH blockers and wire transfer requirements are handled inline. On success (`contractor/payments/created`) the flow hands off to `PaymentSummary`, which shows the created group, debit details, and wire instructions when required. + +```mermaid +flowchart LR + start@{ shape: sm-circ } --> CreatePayment + CreatePayment -->|"contractor/payments/created"| PaymentSummary + PaymentSummary -->|"contractor/payments/exit"| done@{ shape: fr-circ, label: " " } +``` + +The breadcrumb header (`breadcrumb/navigate`) returns to the payments list; submitting wire-transfer details (`payroll/wire/form/done`) surfaces a success alert on `PaymentSummary` without leaving the step. diff --git a/src/components/Contractor/Payments/CreatePaymentFlow/createPaymentMachine.test.ts b/src/components/Contractor/Payments/CreatePaymentFlow/createPaymentMachine.test.ts new file mode 100644 index 0000000000..165a6aef6b --- /dev/null +++ b/src/components/Contractor/Payments/CreatePaymentFlow/createPaymentMachine.test.ts @@ -0,0 +1,78 @@ +import { describe, expect, it } from 'vitest' +import { createMachine, interpret, type SendFunction } from 'robot3' +import { createPaymentMachine, createPaymentBreadcrumbsNodes } from './createPaymentMachine' +import type { CreatePaymentFlowContextInterface } from './CreatePaymentFlowComponents' +import { componentEvents, payrollWireEvents } from '@/shared/constants' +import { buildBreadcrumbs } from '@/helpers/breadcrumbHelpers' + +function createTestMachine() { + return createMachine( + 'createPayment', + createPaymentMachine, + (initialContext: CreatePaymentFlowContextInterface): CreatePaymentFlowContextInterface => ({ + ...initialContext, + component: () => null, + companyId: 'test-company', + header: { + type: 'breadcrumbs', + breadcrumbs: buildBreadcrumbs(createPaymentBreadcrumbsNodes), + currentBreadcrumbId: 'createPayment', + }, + }), + ) +} + +function createService() { + const machine = createTestMachine() + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const service = interpret(machine, () => {}, {} as any) + return service +} + +function send(service: ReturnType, type: string, payload?: unknown) { + ;(service.send as SendFunction)({ type, payload }) +} + +function currentBreadcrumbId(service: ReturnType) { + return service.context.header?.type === 'breadcrumbs' + ? service.context.header.currentBreadcrumbId + : undefined +} + +describe('createPaymentMachine', () => { + describe('createPayment state', () => { + it('transitions to paymentSummary on CONTRACTOR_PAYMENT_CREATED with the created group id', () => { + const service = createService() + expect(service.machine.current).toBe('createPayment') + + send(service, componentEvents.CONTRACTOR_PAYMENT_CREATED, { uuid: 'group-456' }) + + expect(service.machine.current).toBe('paymentSummary') + expect(service.context.createdPaymentGroupId).toBe('group-456') + expect(currentBreadcrumbId(service)).toBe('paymentSummary') + expect(service.context.alerts).toBeUndefined() + }) + }) + + describe('paymentSummary state', () => { + function toPaymentSummary(service: ReturnType) { + send(service, componentEvents.CONTRACTOR_PAYMENT_CREATED, { uuid: 'group-456' }) + expect(service.machine.current).toBe('paymentSummary') + } + + it('sets a success alert on PAYROLL_WIRE_FORM_DONE and stays on paymentSummary', () => { + const service = createService() + toPaymentSummary(service) + + send(service, payrollWireEvents.PAYROLL_WIRE_FORM_DONE, { + wireInRequest: {}, + confirmationAlert: { title: 'wireDetailsSubmitted', content: 'Wire submitted' }, + }) + + expect(service.machine.current).toBe('paymentSummary') + expect(service.context.alerts).toEqual([ + { type: 'success', title: 'wireDetailsSubmitted', content: 'Wire submitted' }, + ]) + }) + }) +}) diff --git a/src/components/Contractor/Payments/CreatePaymentFlow/createPaymentMachine.ts b/src/components/Contractor/Payments/CreatePaymentFlow/createPaymentMachine.ts new file mode 100644 index 0000000000..e7c3a6a1c7 --- /dev/null +++ b/src/components/Contractor/Payments/CreatePaymentFlow/createPaymentMachine.ts @@ -0,0 +1,91 @@ +import { reduce, state, transition } from 'robot3' +import type { ContractorPaymentGroup } from '@gusto/embedded-api/models/components/contractorpaymentgroup' +import type { WireInRequest } from '@gusto/embedded-api/models/components/wireinrequest' +import { + PaymentSummaryContextual, + type CreatePaymentFlowContextInterface, +} from './CreatePaymentFlowComponents' +import { payrollWireEvents, componentEvents } from '@/shared/constants' +import type { MachineEventType, MachineTransition } from '@/types/Helpers' +import { updateBreadcrumbs } from '@/helpers/breadcrumbHelpers' +import type { BreadcrumbNodes } from '@/components/Common/FlowBreadcrumbs/FlowBreadcrumbsTypes' + +type EventPayloads = { + [componentEvents.CONTRACTOR_PAYMENT_CREATED]: ContractorPaymentGroup + [payrollWireEvents.PAYROLL_WIRE_FORM_DONE]: { + wireInRequest: WireInRequest + confirmationAlert: { + title: string + content?: string + } + } +} + +/** @internal */ +export const createPaymentBreadcrumbsNodes: BreadcrumbNodes = { + createPayment: { + parent: null, + item: { + id: 'createPayment', + label: 'breadcrumbLabel', + namespace: 'Contractor.Payments.CreatePayment', + onNavigate: ((ctx: CreatePaymentFlowContextInterface) => ({ + ...updateBreadcrumbs('createPayment', ctx), + })) as (context: unknown) => unknown, + }, + }, + paymentSummary: { + parent: null, + item: { + id: 'paymentSummary', + label: 'breadcrumbLabel', + namespace: 'Contractor.Payments.PaymentSummary', + }, + }, +} + +/** @internal */ +export const createPaymentMachine = { + createPayment: state( + transition( + componentEvents.CONTRACTOR_PAYMENT_CREATED, + 'paymentSummary', + reduce( + ( + ctx: CreatePaymentFlowContextInterface, + ev: MachineEventType, + ): CreatePaymentFlowContextInterface => { + return { + ...updateBreadcrumbs('paymentSummary', ctx), + component: PaymentSummaryContextual, + createdPaymentGroupId: ev.payload.uuid, + alerts: undefined, + } + }, + ), + ), + ), + paymentSummary: state( + transition( + payrollWireEvents.PAYROLL_WIRE_FORM_DONE, + 'paymentSummary', + reduce( + ( + ctx: CreatePaymentFlowContextInterface, + ev: MachineEventType, + ): CreatePaymentFlowContextInterface => { + return { + ...ctx, + alerts: [ + { + type: 'success', + title: 'wireDetailsSubmitted', + content: ev.payload.confirmationAlert.content, + }, + ], + } + }, + ), + ), + ), +} diff --git a/src/components/Contractor/Payments/CreatePaymentFlow/index.ts b/src/components/Contractor/Payments/CreatePaymentFlow/index.ts new file mode 100644 index 0000000000..d1853f13d1 --- /dev/null +++ b/src/components/Contractor/Payments/CreatePaymentFlow/index.ts @@ -0,0 +1,2 @@ +export { CreatePaymentFlow } from './CreatePaymentFlow' +export type { CreatePaymentFlowProps } from './CreatePaymentFlowComponents' diff --git a/src/components/Contractor/Payments/PaymentFlow/PaymentFlowComponents.tsx b/src/components/Contractor/Payments/PaymentFlow/PaymentFlowComponents.tsx index b1d192e1fb..858be30bc5 100644 --- a/src/components/Contractor/Payments/PaymentFlow/PaymentFlowComponents.tsx +++ b/src/components/Contractor/Payments/PaymentFlow/PaymentFlowComponents.tsx @@ -1,13 +1,10 @@ import { PaymentsListInternal } from '../PaymentsList/PaymentsList' -import { CreatePayment } from '../CreatePayment/CreatePayment' -import { PaymentHistory } from '../PaymentHistory/PaymentHistory' -import { PaymentStatement } from '../PaymentStatement/PaymentStatement' -import { PaymentSummaryInternal } from '../PaymentSummary/PaymentSummary' +import { CreatePaymentInternalFlow } from '../CreatePaymentFlow/CreatePaymentFlow' +import { ViewHistoryInternalFlow } from '../ViewHistoryFlow/ViewHistoryFlow' import type { InternalAlert } from '../types' import { InformationRequestsFlow } from '@/components/InformationRequests' import { useFlow, type FlowContextInterface } from '@/components/Flow/useFlow' import type { BaseComponentInterface } from '@/components/Base' -import type { BreadcrumbTrail } from '@/components/Common/FlowBreadcrumbs/FlowBreadcrumbsTypes' import { ensureRequired } from '@/helpers/ensureRequired' /** @@ -23,10 +20,7 @@ export interface PaymentFlowProps extends BaseComponentInterface { /** @internal */ export interface PaymentFlowContextInterface extends FlowContextInterface { companyId: string - breadcrumbs?: BreadcrumbTrail currentPaymentId?: string - currentContractorUuid?: string - createdPaymentGroupId?: string alerts?: InternalAlert[] } @@ -38,42 +32,37 @@ export function PaymentListContextual() { ) } -/** @internal */ -export function CreatePaymentContextual() { - const { companyId, onEvent } = useFlow() - return +function useLandingPrefixBreadcrumbs() { + const { header } = useFlow() + const landingBreadcrumb = + header?.type === 'breadcrumbs' ? header.breadcrumbs?.landing?.[0] : undefined + return landingBreadcrumb ? [landingBreadcrumb] : undefined } /** @internal */ -export function PaymentHistoryContextual() { - const { currentPaymentId, onEvent } = useFlow() - return -} +export function CreatePaymentFlowContextual() { + const { companyId, onEvent } = useFlow() + const prefixBreadcrumbs = useLandingPrefixBreadcrumbs() -/** @internal */ -export function PaymentStatementContextual() { - const { currentPaymentId, currentContractorUuid, onEvent } = - useFlow() return ( - ) } /** @internal */ -export function PaymentSummaryContextual() { - const { createdPaymentGroupId, companyId, onEvent, alerts } = - useFlow() +export function ViewHistoryFlowContextual() { + const { currentPaymentId, onEvent } = useFlow() + const prefixBreadcrumbs = useLandingPrefixBreadcrumbs() return ( - ) } diff --git a/src/components/Contractor/Payments/PaymentFlow/paymentStateMachine.test.ts b/src/components/Contractor/Payments/PaymentFlow/paymentStateMachine.test.ts new file mode 100644 index 0000000000..51912663ea --- /dev/null +++ b/src/components/Contractor/Payments/PaymentFlow/paymentStateMachine.test.ts @@ -0,0 +1,173 @@ +import { describe, expect, it } from 'vitest' +import { createMachine, interpret, type SendFunction } from 'robot3' +import { paymentMachine, paymentFlowBreadcrumbsNodes } from './paymentStateMachine' +import type { PaymentFlowContextInterface } from './PaymentFlowComponents' +import { componentEvents, informationRequestEvents, payrollWireEvents } from '@/shared/constants' +import { buildBreadcrumbs } from '@/helpers/breadcrumbHelpers' +import { ensureRequired } from '@/helpers/ensureRequired' + +function createTestMachine() { + return createMachine( + 'landing', + paymentMachine, + (initialContext: PaymentFlowContextInterface): PaymentFlowContextInterface => ({ + ...initialContext, + component: () => null, + companyId: 'test-company', + header: { + type: 'breadcrumbs', + breadcrumbs: buildBreadcrumbs(paymentFlowBreadcrumbsNodes), + }, + }), + ) +} + +function createService() { + const machine = createTestMachine() + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const service = interpret(machine, () => {}, {} as any) + return service +} + +function send(service: ReturnType, type: string, payload?: unknown) { + ;(service.send as SendFunction)({ type, payload }) +} + +function navigateToLanding(service: ReturnType) { + send(service, componentEvents.BREADCRUMB_NAVIGATE, { + key: 'landing', + onNavigate: ensureRequired(paymentFlowBreadcrumbsNodes.landing).item.onNavigate, + }) +} + +describe('paymentMachine', () => { + describe('landing state', () => { + it('transitions to createPayment on CONTRACTOR_PAYMENT_CREATE', () => { + const service = createService() + expect(service.machine.current).toBe('landing') + + send(service, componentEvents.CONTRACTOR_PAYMENT_CREATE) + + expect(service.machine.current).toBe('createPayment') + expect(service.context.alerts).toBeUndefined() + }) + + it('transitions to history on CONTRACTOR_PAYMENT_VIEW with paymentId from event', () => { + const service = createService() + + send(service, componentEvents.CONTRACTOR_PAYMENT_VIEW, { paymentId: 'payment-123' }) + + expect(service.machine.current).toBe('history') + expect(service.context.currentPaymentId).toBe('payment-123') + expect(service.context.alerts).toBeUndefined() + }) + + it('transitions to informationRequests on CONTRACTOR_PAYMENT_RFI_RESPOND', () => { + const service = createService() + + send(service, componentEvents.CONTRACTOR_PAYMENT_RFI_RESPOND) + + expect(service.machine.current).toBe('informationRequests') + }) + + it('sets a success alert on PAYROLL_WIRE_FORM_DONE and stays on landing', () => { + const service = createService() + + send(service, payrollWireEvents.PAYROLL_WIRE_FORM_DONE, { + wireInRequest: {}, + confirmationAlert: { title: 'wireDetailsSubmitted', content: 'Wire submitted' }, + }) + + expect(service.machine.current).toBe('landing') + expect(service.context.alerts).toEqual([ + { type: 'success', title: 'wireDetailsSubmitted', content: 'Wire submitted' }, + ]) + }) + }) + + describe('createPayment state', () => { + function toCreatePayment(service: ReturnType) { + send(service, componentEvents.CONTRACTOR_PAYMENT_CREATE) + expect(service.machine.current).toBe('createPayment') + } + + it('stays active for the full lifetime of the CreatePaymentFlow spoke (no internal transitions)', () => { + const service = createService() + toCreatePayment(service) + + expect(service.context.component).toBeDefined() + }) + + it('transitions to landing on CONTRACTOR_PAYMENT_EXIT bubbled from the spoke', () => { + const service = createService() + toCreatePayment(service) + + send(service, componentEvents.CONTRACTOR_PAYMENT_EXIT, { uuid: 'group-456' }) + + expect(service.machine.current).toBe('landing') + expect(service.context.alerts).toBeUndefined() + }) + + it('transitions to landing on the bubbled landing BREADCRUMB_NAVIGATE', () => { + const service = createService() + toCreatePayment(service) + + navigateToLanding(service) + + expect(service.machine.current).toBe('landing') + }) + }) + + describe('history state', () => { + function toHistory(service: ReturnType) { + send(service, componentEvents.CONTRACTOR_PAYMENT_VIEW, { paymentId: 'payment-123' }) + expect(service.machine.current).toBe('history') + } + + it('transitions to landing with a success alert on CONTRACTOR_PAYMENT_CANCEL bubbled from the spoke', () => { + const service = createService() + toHistory(service) + + send(service, componentEvents.CONTRACTOR_PAYMENT_CANCEL, { paymentId: 'payment-123' }) + + expect(service.machine.current).toBe('landing') + expect(service.context.alerts).toEqual([ + { type: 'success', title: 'paymentCancelledSuccessfully' }, + ]) + }) + + it('transitions to landing on the bubbled landing BREADCRUMB_NAVIGATE', () => { + const service = createService() + toHistory(service) + + navigateToLanding(service) + + expect(service.machine.current).toBe('landing') + }) + }) + + describe('informationRequests state', () => { + function toInformationRequests(service: ReturnType) { + send(service, componentEvents.CONTRACTOR_PAYMENT_RFI_RESPOND) + expect(service.machine.current).toBe('informationRequests') + } + + it('transitions to landing on INFORMATION_REQUEST_FORM_DONE', () => { + const service = createService() + toInformationRequests(service) + + send(service, informationRequestEvents.INFORMATION_REQUEST_FORM_DONE) + + expect(service.machine.current).toBe('landing') + }) + + it('transitions to landing on INFORMATION_REQUEST_FORM_CANCEL', () => { + const service = createService() + toInformationRequests(service) + + send(service, informationRequestEvents.INFORMATION_REQUEST_FORM_CANCEL) + + expect(service.machine.current).toBe('landing') + }) + }) +}) diff --git a/src/components/Contractor/Payments/PaymentFlow/paymentStateMachine.ts b/src/components/Contractor/Payments/PaymentFlow/paymentStateMachine.ts index 7494cc4c40..7ff55bde4d 100644 --- a/src/components/Contractor/Payments/PaymentFlow/paymentStateMachine.ts +++ b/src/components/Contractor/Payments/PaymentFlow/paymentStateMachine.ts @@ -1,32 +1,22 @@ import { reduce, state, transition } from 'robot3' -import type { ContractorPaymentGroup } from '@gusto/embedded-api/models/components/contractorpaymentgroup' -import type { Contractor } from '@gusto/embedded-api/models/components/contractor' import type { WireInRequest } from '@gusto/embedded-api/models/components/wireinrequest' -import { getContractorDisplayName } from '../CreatePayment/helpers' import { - CreatePaymentContextual, - type PaymentFlowContextInterface, - PaymentHistoryContextual, - PaymentListContextual, - PaymentStatementContextual, - PaymentSummaryContextual, + CreatePaymentFlowContextual, InformationRequestsContextual, + PaymentListContextual, + type PaymentFlowContextInterface, + ViewHistoryFlowContextual, } from './PaymentFlowComponents' import { componentEvents, informationRequestEvents, payrollWireEvents } from '@/shared/constants' import type { MachineEventType, MachineTransition } from '@/types/Helpers' -import { patchBreadcrumbsHeader, updateBreadcrumbs } from '@/helpers/breadcrumbHelpers' +import { patchBreadcrumbsHeader } from '@/helpers/breadcrumbHelpers' import type { BreadcrumbNodes } from '@/components/Common/FlowBreadcrumbs/FlowBreadcrumbsTypes' import { createBreadcrumbNavigateTransition } from '@/components/Common/FlowBreadcrumbs/breadcrumbTransitionHelpers' type EventPayloads = { [componentEvents.CONTRACTOR_PAYMENT_CREATE]: undefined - [componentEvents.CONTRACTOR_PAYMENT_CREATED]: ContractorPaymentGroup [componentEvents.CONTRACTOR_PAYMENT_EXIT]: { uuid?: string | null } [componentEvents.CONTRACTOR_PAYMENT_VIEW]: { paymentId: string } - [componentEvents.CONTRACTOR_PAYMENT_VIEW_DETAILS]: { - contractor: Contractor - paymentGroupId: string - } [componentEvents.CONTRACTOR_PAYMENT_CANCEL]: { paymentId: string } [componentEvents.CONTRACTOR_PAYMENT_RFI_RESPOND]: undefined [componentEvents.BREADCRUMB_NAVIGATE]: { @@ -44,7 +34,16 @@ type EventPayloads = { } } -/** @internal */ +/** + * Hub-level breadcrumb nodes for {@link PaymentFlow}. + * + * @remarks + * Only `landing` is defined here — the `createPayment` and `history` spokes own and render their + * own breadcrumb trails (via `CreatePaymentFlow`/`ViewHistoryFlow`), prefixed with this `landing` + * item so the trail reads continuously across the hub/spoke boundary. + * + * @internal + */ export const paymentFlowBreadcrumbsNodes: BreadcrumbNodes = { landing: { parent: null, @@ -58,54 +57,23 @@ export const paymentFlowBreadcrumbsNodes: BreadcrumbNodes = { })) as (context: unknown) => unknown, }, }, - createPayment: { - parent: 'landing', - item: { - id: 'createPayment', - label: 'breadcrumbLabel', - namespace: 'Contractor.Payments.CreatePayment', - onNavigate: ((ctx: PaymentFlowContextInterface) => ({ - ...updateBreadcrumbs('createPayment', ctx), - })) as (context: unknown) => unknown, - }, - }, - paymentSummary: { - parent: 'landing', - item: { - id: 'paymentSummary', - label: 'breadcrumbLabel', - namespace: 'Contractor.Payments.PaymentSummary', - onNavigate: ((ctx: PaymentFlowContextInterface) => ({ - ...updateBreadcrumbs('paymentSummary', ctx), - })) as (context: unknown) => unknown, - }, - }, - history: { - parent: 'landing', - item: { - id: 'history', - label: 'breadcrumbLabel', - namespace: 'Contractor.Payments.PaymentHistory', - onNavigate: ((ctx: PaymentFlowContextInterface) => ({ - ...updateBreadcrumbs('history', ctx), - component: PaymentHistoryContextual, - })) as (context: unknown) => unknown, - }, - }, - statement: { - parent: 'history', - item: { - id: 'statement', - label: 'breadcrumbLabel', - namespace: 'Contractor.Payments.PaymentStatement', - }, - }, -} as const +} const breadcrumbNavigateTransition = createBreadcrumbNavigateTransition() -/** @internal */ +/** + * Hub machine for {@link PaymentFlow}. + * + * @remarks + * `createPayment` and `history` each stay active for the full lifetime of their respective spoke + * (`CreatePaymentFlow`, `ViewHistoryFlow`) rather than tracking the spoke's internal screen. The + * spoke's own machine drives its internal steps and breadcrumb trail; events it can't handle + * locally (e.g. the `landing` breadcrumb, or a terminal exit/cancel event) bubble up here to + * transition the hub back to `landing`. + * + * @internal + */ export const paymentMachine = { landing: state( transition( @@ -113,8 +81,8 @@ export const paymentMachine = { 'createPayment', reduce((ctx: PaymentFlowContextInterface): PaymentFlowContextInterface => { return { - ...updateBreadcrumbs('createPayment', ctx), - component: CreatePaymentContextual, + ...ctx, + component: CreatePaymentFlowContextual, alerts: undefined, } }), @@ -128,8 +96,8 @@ export const paymentMachine = { ev: MachineEventType, ): PaymentFlowContextInterface => { return { - ...updateBreadcrumbs('history', ctx), - component: PaymentHistoryContextual, + ...ctx, + component: ViewHistoryFlowContextual, currentPaymentId: ev.payload.paymentId, alerts: undefined, } @@ -169,26 +137,6 @@ export const paymentMachine = { ), ), createPayment: state( - transition( - componentEvents.CONTRACTOR_PAYMENT_CREATED, - 'paymentSummary', - reduce( - ( - ctx: PaymentFlowContextInterface, - ev: MachineEventType, - ): PaymentFlowContextInterface => { - return { - ...updateBreadcrumbs('paymentSummary', ctx), - component: PaymentSummaryContextual, - createdPaymentGroupId: ev.payload.uuid, - alerts: undefined, - } - }, - ), - ), - breadcrumbNavigateTransition('landing'), - ), - paymentSummary: state( transition( componentEvents.CONTRACTOR_PAYMENT_EXIT, 'landing', @@ -196,58 +144,13 @@ export const paymentMachine = { return { ...patchBreadcrumbsHeader(ctx, { currentBreadcrumbId: undefined }), component: PaymentListContextual, - createdPaymentGroupId: undefined, alerts: undefined, } }), ), - transition( - payrollWireEvents.PAYROLL_WIRE_FORM_DONE, - 'paymentSummary', - reduce( - ( - ctx: PaymentFlowContextInterface, - ev: MachineEventType, - ): PaymentFlowContextInterface => { - return { - ...ctx, - alerts: [ - { - type: 'success', - title: 'wireDetailsSubmitted', - content: ev.payload.confirmationAlert.content, - }, - ], - } - }, - ), - ), breadcrumbNavigateTransition('landing'), ), history: state( - transition( - componentEvents.CONTRACTOR_PAYMENT_VIEW_DETAILS, - 'statement', - reduce( - ( - ctx: PaymentFlowContextInterface, - ev: MachineEventType< - EventPayloads, - typeof componentEvents.CONTRACTOR_PAYMENT_VIEW_DETAILS - >, - ): PaymentFlowContextInterface => { - return { - ...updateBreadcrumbs('statement', ctx, { - contractorName: getContractorDisplayName(ev.payload.contractor), - }), - component: PaymentStatementContextual, - currentContractorUuid: ev.payload.contractor.uuid, - currentPaymentId: ev.payload.paymentGroupId, - alerts: undefined, - } - }, - ), - ), transition( componentEvents.CONTRACTOR_PAYMENT_CANCEL, 'landing', @@ -266,10 +169,6 @@ export const paymentMachine = { ), breadcrumbNavigateTransition('landing'), ), - statement: state( - breadcrumbNavigateTransition('landing'), - breadcrumbNavigateTransition('history'), - ), informationRequests: state( transition( informationRequestEvents.INFORMATION_REQUEST_FORM_DONE, diff --git a/src/components/Contractor/Payments/ViewHistoryFlow/GUIDE.md b/src/components/Contractor/Payments/ViewHistoryFlow/GUIDE.md new file mode 100644 index 0000000000..f68e4fc656 --- /dev/null +++ b/src/components/Contractor/Payments/ViewHistoryFlow/GUIDE.md @@ -0,0 +1,16 @@ + + +# ViewHistoryFlow + +## Step flow + +`ViewHistoryFlow` centers on `PaymentHistory` as its hub: it shows a payment group's details and can either drill into an individual contractor's statement (`contractor/payments/view/details` → `PaymentStatement`) or cancel the group outright (`contractor/payments/cancel`), which exits the flow. + +```mermaid +flowchart LR + start@{ shape: sm-circ } --> PaymentHistory + PaymentHistory <-->|"contractor/payments/view/details"| PaymentStatement + PaymentHistory -->|"contractor/payments/cancel"| done@{ shape: fr-circ, label: " " } +``` + +The breadcrumb header (`breadcrumb/navigate`) returns from `PaymentStatement` to `PaymentHistory`, or exits the flow entirely from either step. diff --git a/src/components/Contractor/Payments/ViewHistoryFlow/ViewHistoryFlow.tsx b/src/components/Contractor/Payments/ViewHistoryFlow/ViewHistoryFlow.tsx new file mode 100644 index 0000000000..535c2fa002 --- /dev/null +++ b/src/components/Contractor/Payments/ViewHistoryFlow/ViewHistoryFlow.tsx @@ -0,0 +1,116 @@ +import { createMachine } from 'robot3' +import { useState } from 'react' +import { viewHistoryBreadcrumbsNodes, viewHistoryMachine } from './viewHistoryMachine' +import { + PaymentHistoryContextual, + type ViewHistoryFlowContextInterface, + type ViewHistoryFlowProps, +} from './ViewHistoryFlowComponents' +import { Flow } from '@/components/Flow/Flow' +import type { FlowBreadcrumb } from '@/components/Common/FlowBreadcrumbs/FlowBreadcrumbsTypes' +import { buildBreadcrumbs, updateBreadcrumbs } from '@/helpers/breadcrumbHelpers' + +const EMPTY_BREADCRUMBS: FlowBreadcrumb[] = [] + +/** + * Props for the flow-internal {@link ViewHistoryInternalFlow}, which layers a parent flow's + * prefix breadcrumbs on top of the public {@link ViewHistoryFlowProps}. + * + * @internal + */ +export interface ViewHistoryInternalFlowProps extends ViewHistoryFlowProps { + /** + * Breadcrumbs prepended to the flow's own breadcrumb trail. Set by a parent flow (e.g. + * `PaymentFlow`) so the breadcrumb history remains coherent across the handoff. + */ + prefixBreadcrumbs?: FlowBreadcrumb[] +} + +/** + * Guided flow to inspect a contractor payment group's history and drill into an individual + * contractor's payment statement. + * + * @remarks + * This is the inner flow that powers the view-history spoke of `ContractorManagement.PaymentFlow`. + * Render it directly when you have built your own payments landing page and want to hand the user + * off to the standard history-viewing experience without re-implementing it. The flow ships with + * breadcrumb navigation and lets the user cancel an individual payment from the history screen. + * + * @events + * | Event | Description | Data | + * | ----- | ----------- | ---- | + * | `contractor/payments/view/details` | Fired when the user views a specific contractor payment | `{ contractor: Contractor, paymentGroupId: string }` | + * | `contractor/payments/cancel` | Fired when a payment is cancelled | `{ paymentId: string }` | + * | `breadcrumb/navigate` | Fired when the user clicks a breadcrumb to navigate back | `{ key: string, onNavigate: (ctx) => ctx }` | + * + * @components + * - {@link PaymentHistory} + * - {@link PaymentStatement} + * + * @param props - See {@link ViewHistoryFlowProps}. + * @returns The composed view-history flow. + * @alpha + * + * @example + * ```tsx title="App.tsx" + * import { ContractorManagement } from '@gusto/embedded-react-sdk' + * + * function MyApp() { + * return ( + * {}} + * /> + * ) + * } + * ``` + */ +export function ViewHistoryFlow(props: ViewHistoryFlowProps) { + return +} + +/** + * Flow-internal entry point for {@link ViewHistoryFlow} that additionally accepts + * flow-injected `prefixBreadcrumbs`. Partners use {@link ViewHistoryFlow}; `PaymentFlow` renders + * this directly to prepend its own breadcrumb trail. + * + * @internal + */ +export function ViewHistoryInternalFlow({ + paymentId, + onEvent, + prefixBreadcrumbs = EMPTY_BREADCRUMBS, +}: ViewHistoryInternalFlowProps) { + // Built once via a lazy useState initializer, not useMemo -- see CreatePaymentFlow.tsx for why: + // a useMemo keyed on `prefixBreadcrumbs` doesn't guarantee the machine's identity survives a + // re-render triggered by a bubbled `onEvent` call. + const [viewHistoryFlow] = useState(() => { + const baseBreadcrumbs = buildBreadcrumbs(viewHistoryBreadcrumbsNodes) + const breadcrumbs = Object.fromEntries( + Object.entries(baseBreadcrumbs).map(([stateKey, trail]) => [ + stateKey, + [...prefixBreadcrumbs, ...trail], + ]), + ) + + const initialBreadcrumbContext = updateBreadcrumbs('history', { + header: { + type: 'breadcrumbs' as const, + breadcrumbs, + }, + }) + + return createMachine( + 'history', + viewHistoryMachine, + (initialContext: ViewHistoryFlowContextInterface) => ({ + ...initialContext, + ...initialBreadcrumbContext, + component: PaymentHistoryContextual, + currentPaymentId: paymentId, + }), + ) + }) + + return +} diff --git a/src/components/Contractor/Payments/ViewHistoryFlow/ViewHistoryFlowComponents.tsx b/src/components/Contractor/Payments/ViewHistoryFlow/ViewHistoryFlowComponents.tsx new file mode 100644 index 0000000000..fe52cade14 --- /dev/null +++ b/src/components/Contractor/Payments/ViewHistoryFlow/ViewHistoryFlowComponents.tsx @@ -0,0 +1,40 @@ +import { PaymentHistory } from '../PaymentHistory/PaymentHistory' +import { PaymentStatement } from '../PaymentStatement/PaymentStatement' +import { useFlow, type FlowContextInterface } from '@/components/Flow/useFlow' +import type { BaseComponentInterface } from '@/components/Base' +import { ensureRequired } from '@/helpers/ensureRequired' + +/** + * Props for {@link ViewHistoryFlow}. + * + * @alpha + */ +export interface ViewHistoryFlowProps extends BaseComponentInterface { + /** Identifier of the payment group to inspect. */ + paymentId: string +} + +/** @internal */ +export interface ViewHistoryFlowContextInterface extends FlowContextInterface { + currentPaymentId?: string + currentContractorUuid?: string +} + +/** @internal */ +export function PaymentHistoryContextual() { + const { currentPaymentId, onEvent } = useFlow() + return +} + +/** @internal */ +export function PaymentStatementContextual() { + const { currentPaymentId, currentContractorUuid, onEvent } = + useFlow() + return ( + + ) +} diff --git a/src/components/Contractor/Payments/ViewHistoryFlow/index.ts b/src/components/Contractor/Payments/ViewHistoryFlow/index.ts new file mode 100644 index 0000000000..9701b077d3 --- /dev/null +++ b/src/components/Contractor/Payments/ViewHistoryFlow/index.ts @@ -0,0 +1,2 @@ +export { ViewHistoryFlow } from './ViewHistoryFlow' +export type { ViewHistoryFlowProps } from './ViewHistoryFlowComponents' diff --git a/src/components/Contractor/Payments/ViewHistoryFlow/viewHistoryMachine.test.ts b/src/components/Contractor/Payments/ViewHistoryFlow/viewHistoryMachine.test.ts new file mode 100644 index 0000000000..84ac514c1f --- /dev/null +++ b/src/components/Contractor/Payments/ViewHistoryFlow/viewHistoryMachine.test.ts @@ -0,0 +1,91 @@ +import { describe, expect, it } from 'vitest' +import { createMachine, interpret, type SendFunction } from 'robot3' +import { viewHistoryMachine, viewHistoryBreadcrumbsNodes } from './viewHistoryMachine' +import type { ViewHistoryFlowContextInterface } from './ViewHistoryFlowComponents' +import { componentEvents } from '@/shared/constants' +import { buildBreadcrumbs } from '@/helpers/breadcrumbHelpers' +import { ensureRequired } from '@/helpers/ensureRequired' + +function createTestMachine() { + return createMachine( + 'history', + viewHistoryMachine, + (initialContext: ViewHistoryFlowContextInterface): ViewHistoryFlowContextInterface => ({ + ...initialContext, + component: () => null, + currentPaymentId: 'payment-123', + header: { + type: 'breadcrumbs', + breadcrumbs: buildBreadcrumbs(viewHistoryBreadcrumbsNodes), + currentBreadcrumbId: 'history', + }, + }), + ) +} + +function createService() { + const machine = createTestMachine() + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const service = interpret(machine, () => {}, {} as any) + return service +} + +function send(service: ReturnType, type: string, payload?: unknown) { + ;(service.send as SendFunction)({ type, payload }) +} + +function currentBreadcrumbId(service: ReturnType) { + return service.context.header?.type === 'breadcrumbs' + ? service.context.header.currentBreadcrumbId + : undefined +} + +describe('viewHistoryMachine', () => { + describe('history state', () => { + it('transitions to statement on CONTRACTOR_PAYMENT_VIEW_DETAILS with contractor and payment ids', () => { + const service = createService() + const contractor = { uuid: 'contractor-789', firstName: 'Ada', lastName: 'Lovelace' } + + send(service, componentEvents.CONTRACTOR_PAYMENT_VIEW_DETAILS, { + contractor, + paymentGroupId: 'group-456', + }) + + expect(service.machine.current).toBe('statement') + expect(service.context.currentContractorUuid).toBe('contractor-789') + expect(service.context.currentPaymentId).toBe('group-456') + expect(currentBreadcrumbId(service)).toBe('statement') + }) + }) + + describe('statement state', () => { + function toStatement(service: ReturnType) { + send(service, componentEvents.CONTRACTOR_PAYMENT_VIEW_DETAILS, { + contractor: { uuid: 'contractor-789', firstName: 'Ada', lastName: 'Lovelace' }, + paymentGroupId: 'group-456', + }) + expect(service.machine.current).toBe('statement') + } + + it('transitions to history on BREADCRUMB_NAVIGATE with history key', () => { + const service = createService() + toStatement(service) + + send(service, componentEvents.BREADCRUMB_NAVIGATE, { + key: 'history', + onNavigate: ensureRequired(viewHistoryBreadcrumbsNodes.history).item.onNavigate, + }) + + expect(service.machine.current).toBe('history') + }) + + it('ignores BREADCRUMB_NAVIGATE with an unrelated key', () => { + const service = createService() + toStatement(service) + + send(service, componentEvents.BREADCRUMB_NAVIGATE, { key: 'landing' }) + + expect(service.machine.current).toBe('statement') + }) + }) +}) diff --git a/src/components/Contractor/Payments/ViewHistoryFlow/viewHistoryMachine.ts b/src/components/Contractor/Payments/ViewHistoryFlow/viewHistoryMachine.ts new file mode 100644 index 0000000000..45a74bfe96 --- /dev/null +++ b/src/components/Contractor/Payments/ViewHistoryFlow/viewHistoryMachine.ts @@ -0,0 +1,76 @@ +import { reduce, state, transition } from 'robot3' +import type { Contractor } from '@gusto/embedded-api/models/components/contractor' +import { getContractorDisplayName } from '../CreatePayment/helpers' +import { + PaymentHistoryContextual, + PaymentStatementContextual, + type ViewHistoryFlowContextInterface, +} from './ViewHistoryFlowComponents' +import { componentEvents } from '@/shared/constants' +import type { MachineEventType, MachineTransition } from '@/types/Helpers' +import { updateBreadcrumbs } from '@/helpers/breadcrumbHelpers' +import type { BreadcrumbNodes } from '@/components/Common/FlowBreadcrumbs/FlowBreadcrumbsTypes' +import { createBreadcrumbNavigateTransition } from '@/components/Common/FlowBreadcrumbs/breadcrumbTransitionHelpers' + +type EventPayloads = { + [componentEvents.CONTRACTOR_PAYMENT_VIEW_DETAILS]: { + contractor: Contractor + paymentGroupId: string + } +} + +const breadcrumbNavigateTransition = + createBreadcrumbNavigateTransition() + +/** @internal */ +export const viewHistoryBreadcrumbsNodes: BreadcrumbNodes = { + history: { + parent: null, + item: { + id: 'history', + label: 'breadcrumbLabel', + namespace: 'Contractor.Payments.PaymentHistory', + onNavigate: ((ctx: ViewHistoryFlowContextInterface) => ({ + ...updateBreadcrumbs('history', ctx), + component: PaymentHistoryContextual, + })) as (context: unknown) => unknown, + }, + }, + statement: { + parent: 'history', + item: { + id: 'statement', + label: 'breadcrumbLabel', + namespace: 'Contractor.Payments.PaymentStatement', + }, + }, +} + +/** @internal */ +export const viewHistoryMachine = { + history: state( + transition( + componentEvents.CONTRACTOR_PAYMENT_VIEW_DETAILS, + 'statement', + reduce( + ( + ctx: ViewHistoryFlowContextInterface, + ev: MachineEventType< + EventPayloads, + typeof componentEvents.CONTRACTOR_PAYMENT_VIEW_DETAILS + >, + ): ViewHistoryFlowContextInterface => { + return { + ...updateBreadcrumbs('statement', ctx, { + contractorName: getContractorDisplayName(ev.payload.contractor), + }), + component: PaymentStatementContextual, + currentContractorUuid: ev.payload.contractor.uuid, + currentPaymentId: ev.payload.paymentGroupId, + } + }, + ), + ), + ), + statement: state(breadcrumbNavigateTransition('history')), +} diff --git a/src/components/Contractor/exports/contractorManagement.ts b/src/components/Contractor/exports/contractorManagement.ts index 06611a9ab5..bdca363e8a 100644 --- a/src/components/Contractor/exports/contractorManagement.ts +++ b/src/components/Contractor/exports/contractorManagement.ts @@ -4,6 +4,8 @@ export { type ContractorTab, } from '../ContractorList/management/ManagementContractorList' export { PaymentFlow, type PaymentFlowProps } from '../Payments/PaymentFlow' +export { CreatePaymentFlow, type CreatePaymentFlowProps } from '../Payments/CreatePaymentFlow' +export { ViewHistoryFlow, type ViewHistoryFlowProps } from '../Payments/ViewHistoryFlow' export { PaymentsList, type PaymentsListProps } from '../Payments/PaymentsList/PaymentsList' export { CreatePayment, type CreatePaymentProps } from '../Payments/CreatePayment/CreatePayment' export { PaymentHistory, type PaymentHistoryProps } from '../Payments/PaymentHistory/PaymentHistory' From 92228beeacf5a55c80b5c3c9af33294151c08574 Mon Sep 17 00:00:00 2001 From: Marie Chatfield Date: Thu, 23 Jul 2026 12:31:13 -0700 Subject: [PATCH 04/10] test(PaymentFlow): cover the create-payment and information-requests spokes (SDK-1127) Adds hub-level integration coverage for the two remaining untested paths through PaymentFlow: submitting a new payment through CreatePaymentFlow to PaymentSummary, and entering/exiting the information-requests spoke via both cancel and done. Co-Authored-By: Claude Sonnet 5 --- .../Payments/PaymentFlow/PaymentFlow.test.tsx | 132 +++++++++++++++++- 1 file changed, 130 insertions(+), 2 deletions(-) diff --git a/src/components/Contractor/Payments/PaymentFlow/PaymentFlow.test.tsx b/src/components/Contractor/Payments/PaymentFlow/PaymentFlow.test.tsx index bb3571d9c1..13917a10cf 100644 --- a/src/components/Contractor/Payments/PaymentFlow/PaymentFlow.test.tsx +++ b/src/components/Contractor/Payments/PaymentFlow/PaymentFlow.test.tsx @@ -1,5 +1,5 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' -import { screen } from '@testing-library/react' +import { screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { useState } from 'react' import { http, HttpResponse } from 'msw' @@ -10,7 +10,12 @@ import { setupApiTestMocks } from '@/test/mocks/apiServer' import { renderWithProviders } from '@/test-utils/renderWithProviders' import { API_BASE_URL } from '@/test/constants' import { handleGetContractorPaymentGroup } from '@/test/mocks/apis/contractor_payment_groups' -import { componentEvents } from '@/shared/constants' +import { handleGetContractorsList } from '@/test/mocks/apis/contractors' +import { + handleGetInformationRequests, + handleSubmitInformationRequest, +} from '@/test/mocks/apis/information_requests' +import { componentEvents, informationRequestEvents } from '@/shared/constants' const COMPANY_ID = 'company-123' @@ -67,6 +72,38 @@ const paymentGroupWithCancelableContractorPayment = handleGetContractorPaymentGr }), ) +const hourlyContractor = { + uuid: 'contractor-hourly', + company_uuid: COMPANY_ID, + wage_type: 'Hourly', + type: 'Individual', + first_name: 'Ada', + last_name: 'Lovelace', + is_active: true, + onboarding_status: 'onboarding_completed', + hourly_rate: '50.00', + payment_method: 'Direct Deposit', +} + +const singleTextInformationRequest = handleGetInformationRequests(() => + HttpResponse.json([ + { + uuid: 'rfi-1', + company_uuid: COMPANY_ID, + type: 'company_onboarding', + status: 'pending_response', + blocking_payroll: false, + required_questions: [ + { + question_uuid: 'q-1', + question_text: 'Please confirm.', + response_type: 'text', + }, + ], + }, + ]), +) + /** * Wraps `PaymentFlow` behind a sibling button that forces a parent re-render. * Simulates a partner app re-rendering its own tree on every bubbled `onEvent` @@ -164,6 +201,97 @@ describe('PaymentFlow', () => { await screen.findByRole('heading', { name: 'Contractor payments' }) }) + it('creates a payment via CreatePaymentFlow and lands on the summary', async () => { + const user = userEvent.setup() + const onEvent = vi.fn() + server.use( + handleGetContractorsList(() => + HttpResponse.json([hourlyContractor], { + headers: { 'x-total-pages': '1', 'x-total-count': '1' }, + }), + ), + ) + + renderWithProviders() + + await user.click(await screen.findByRole('button', { name: 'New payment' })) + await screen.findByRole('heading', { name: 'Pay contractors' }) + + await user.click(await screen.findByRole('button', { name: 'Edit contractor payment' })) + await user.click(await screen.findByRole('menuitem', { name: 'Edit contractor payment' })) + await screen.findByRole('heading', { name: 'Edit contractor pay' }) + await user.type(screen.getByLabelText('Hours'), '8') + await user.click(screen.getByRole('button', { name: 'Done' })) + await waitFor(() => { + expect(screen.queryByRole('heading', { name: 'Edit contractor pay' })).not.toBeInTheDocument() + }) + + await user.click(screen.getByRole('button', { name: 'Continue' })) + await screen.findByRole('heading', { name: 'Review and submit' }) + + await user.click(screen.getByRole('button', { name: 'Submit' })) + + await screen.findByRole('heading', { name: 'Payment summary' }) + expect(onEvent).toHaveBeenCalledWith( + componentEvents.CONTRACTOR_PAYMENT_CREATED, + expect.objectContaining({ uuid: 'new-payment-group-uuid' }), + ) + }) + + it('enters the information-requests flow and cancels back to the payments list', async () => { + const user = userEvent.setup() + const onEvent = vi.fn() + server.use(singleTextInformationRequest) + + renderWithProviders() + + await user.click(await screen.findByRole('button', { name: 'Respond' })) + await screen.findByRole('heading', { name: 'Information requests' }) + + await user.click(await screen.findByRole('button', { name: 'Respond' })) + await screen.findByText('Request for information') + await user.click(screen.getByRole('button', { name: 'Cancel' })) + + await screen.findByRole('heading', { name: 'Contractor payments' }) + expect(onEvent).toHaveBeenCalledWith( + informationRequestEvents.INFORMATION_REQUEST_FORM_CANCEL, + undefined, + ) + }) + + it('completes the information-requests flow and returns to the payments list', async () => { + const user = userEvent.setup() + const onEvent = vi.fn() + server.use( + singleTextInformationRequest, + handleSubmitInformationRequest(() => + HttpResponse.json({ + uuid: 'rfi-1', + company_uuid: COMPANY_ID, + type: 'company_onboarding', + status: 'pending_review', + blocking_payroll: false, + }), + ), + ) + + renderWithProviders() + + await user.click(await screen.findByRole('button', { name: 'Respond' })) + await screen.findByRole('heading', { name: 'Information requests' }) + + await user.click(await screen.findByRole('button', { name: 'Respond' })) + await screen.findByText('Request for information') + await user.type(screen.getByPlaceholderText('Your answer'), 'Confirm') + await user.click(screen.getByRole('button', { name: 'Submit response' })) + + await screen.findByRole('heading', { name: 'Contractor payments' }) + expect(onEvent).toHaveBeenCalledWith( + informationRequestEvents.INFORMATION_REQUEST_FORM_DONE, + expect.objectContaining({ uuid: 'rfi-1', status: 'pending_review' }), + ) + }) + it("does not reset the flow's current step when the partner app re-renders its own tree mid-flow", async () => { const user = userEvent.setup() const onEvent = vi.fn() From aa2a84291c5a552ed572615aee4e79c9e852980d Mon Sep 17 00:00:00 2001 From: Marie Chatfield Date: Thu, 23 Jul 2026 12:37:53 -0700 Subject: [PATCH 05/10] fix(InformationRequests): attach the response form's Footer to its contextual wrapper InformationRequestFormContextual never got the Footer static-property assignment that every other Flow-composed form uses, and the barrel export was missing Footer entirely. The response modal rendered with no Cancel or Submit button whenever InformationRequestsFlow drove it, and closing via the modal's X never notified a parent hub (e.g. PaymentFlow) either. Co-Authored-By: Claude Sonnet 5 --- .../InformationRequests/InformationRequestForm/index.ts | 6 +++++- .../InformationRequests/InformationRequestsComponents.tsx | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/InformationRequests/InformationRequestForm/index.ts b/src/components/InformationRequests/InformationRequestForm/index.ts index a6dad9f258..2e8760a708 100644 --- a/src/components/InformationRequests/InformationRequestForm/index.ts +++ b/src/components/InformationRequests/InformationRequestForm/index.ts @@ -1 +1,5 @@ -export { InformationRequestForm, type InformationRequestFormProps } from './InformationRequestForm' +export { + InformationRequestForm, + Footer, + type InformationRequestFormProps, +} from './InformationRequestForm' diff --git a/src/components/InformationRequests/InformationRequestsComponents.tsx b/src/components/InformationRequests/InformationRequestsComponents.tsx index 7fc6a86247..7d78d56a7d 100644 --- a/src/components/InformationRequests/InformationRequestsComponents.tsx +++ b/src/components/InformationRequests/InformationRequestsComponents.tsx @@ -1,4 +1,4 @@ -import { InformationRequestForm } from './InformationRequestForm' +import { InformationRequestForm, Footer } from './InformationRequestForm' import { useFlow } from '@/components/Flow/useFlow' import { ensureRequired } from '@/helpers/ensureRequired' import type { FlowContextInterface } from '@/components/Flow/useFlow' @@ -31,3 +31,4 @@ export function InformationRequestFormContextual() { /> ) } +InformationRequestFormContextual.Footer = Footer From c7082e08a3206713efdf420c06b2051cf5771ce9 Mon Sep 17 00:00:00 2001 From: Marie Chatfield Date: Thu, 23 Jul 2026 12:38:52 -0700 Subject: [PATCH 06/10] docs(ViewHistoryFlow): switch the step diagram to TD The flow has one spoke and a real exit, not a fan-out of spokes off a resting screen -- shape-wise it reads as a guided drill-in-and-back, not a hub. Also names the return edge explicitly (breadcrumb/navigate) instead of collapsing both directions under one label. Co-Authored-By: Claude Sonnet 5 --- src/components/Contractor/Payments/ViewHistoryFlow/GUIDE.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/Contractor/Payments/ViewHistoryFlow/GUIDE.md b/src/components/Contractor/Payments/ViewHistoryFlow/GUIDE.md index f68e4fc656..d3492f290e 100644 --- a/src/components/Contractor/Payments/ViewHistoryFlow/GUIDE.md +++ b/src/components/Contractor/Payments/ViewHistoryFlow/GUIDE.md @@ -7,9 +7,10 @@ `ViewHistoryFlow` centers on `PaymentHistory` as its hub: it shows a payment group's details and can either drill into an individual contractor's statement (`contractor/payments/view/details` → `PaymentStatement`) or cancel the group outright (`contractor/payments/cancel`), which exits the flow. ```mermaid -flowchart LR +flowchart TD start@{ shape: sm-circ } --> PaymentHistory - PaymentHistory <-->|"contractor/payments/view/details"| PaymentStatement + PaymentHistory -->|"contractor/payments/view/details"| PaymentStatement + PaymentStatement -->|"breadcrumb/navigate"| PaymentHistory PaymentHistory -->|"contractor/payments/cancel"| done@{ shape: fr-circ, label: " " } ``` From 8300818d0d29ff361575ec5d5c6435c67afe63ca Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 23 Jul 2026 19:44:20 +0000 Subject: [PATCH 07/10] chore: update derived files --- .reports/embedded-react-sdk.api.md | 20 ++++++++++++++++++++ docs/guides/endpoint-inventory.json | 19 +++++++++++++++---- docs/guides/endpoint-reference.md | 4 +++- 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/.reports/embedded-react-sdk.api.md b/.reports/embedded-react-sdk.api.md index 5b12156154..63a2dc90be 100644 --- a/.reports/embedded-react-sdk.api.md +++ b/.reports/embedded-react-sdk.api.md @@ -1773,6 +1773,10 @@ declare namespace ContractorManagement { ContractorTab, PaymentFlow, PaymentFlowProps, + CreatePaymentFlow, + CreatePaymentFlowProps, + ViewHistoryFlow, + ViewHistoryFlowProps, PaymentsList, PaymentsListProps, CreatePayment, @@ -2072,6 +2076,14 @@ type CreatableTimeOffPolicyType = Extract; // @public function CreatePayment(props: CreatePaymentProps): JSX; +// @alpha +function CreatePaymentFlow(props: CreatePaymentFlowProps): JSX; + +// @alpha +interface CreatePaymentFlowProps extends BaseComponentInterface { + companyId: string; +} + // @public interface CreatePaymentProps extends BaseComponentInterface<'Contractor.Payments.CreatePayment'> { companyId: string; @@ -6205,6 +6217,14 @@ export type UseWorkAddressFormResult = HookLoadingResult | UseWorkAddressFormRea // @public export type ValidationMessages = Record & Partial>; +// @alpha +function ViewHistoryFlow(props: ViewHistoryFlowProps): JSX; + +// @alpha +interface ViewHistoryFlowProps extends BaseComponentInterface { + paymentId: string; +} + // @public function ViewHolidayEmployees(props: ViewHolidayEmployeesProps): JSX; diff --git a/docs/guides/endpoint-inventory.json b/docs/guides/endpoint-inventory.json index 5fb23dd180..702d638e40 100644 --- a/docs/guides/endpoint-inventory.json +++ b/docs/guides/endpoint-inventory.json @@ -2300,16 +2300,27 @@ "EmployeeOnboarding.OnboardingFlow" ] }, - "ContractorManagement.PaymentFlow": { + "ContractorManagement.CreatePaymentFlow": { "blocks": [ "ContractorManagement.CreatePayment", - "ContractorManagement.PaymentHistory", - "ContractorManagement.PaymentStatement", - "ContractorManagement.PaymentSummary", + "ContractorManagement.PaymentSummary" + ] + }, + "ContractorManagement.PaymentFlow": { + "blocks": [ + "ContractorManagement.CreatePaymentFlow", "ContractorManagement.PaymentsList", + "ContractorManagement.ViewHistoryFlow", "InformationRequests.InformationRequestsFlow" ] }, + "ContractorManagement.ViewHistoryFlow": { + "blocks": [ + "ContractorManagement.CreatePayment", + "ContractorManagement.PaymentHistory", + "ContractorManagement.PaymentStatement" + ] + }, "ContractorOnboarding.OnboardingFlow": { "blocks": [ "ContractorOnboarding.Address", diff --git a/docs/guides/endpoint-reference.md b/docs/guides/endpoint-reference.md index 6e2571cb59..c313d93947 100644 --- a/docs/guides/endpoint-reference.md +++ b/docs/guides/endpoint-reference.md @@ -341,7 +341,9 @@ import inventory from '@gusto/embedded-react-sdk/endpoint-inventory.json' | Flow | Blocks included | | --- | --- | -| **ContractorManagement.PaymentFlow** | ContractorManagement.CreatePayment, ContractorManagement.PaymentHistory, ContractorManagement.PaymentStatement, ContractorManagement.PaymentSummary, ContractorManagement.PaymentsList, InformationRequests.InformationRequestsFlow | +| **ContractorManagement.CreatePaymentFlow** | ContractorManagement.CreatePayment, ContractorManagement.PaymentSummary | +| **ContractorManagement.PaymentFlow** | ContractorManagement.CreatePaymentFlow, ContractorManagement.PaymentsList, ContractorManagement.ViewHistoryFlow, InformationRequests.InformationRequestsFlow | +| **ContractorManagement.ViewHistoryFlow** | ContractorManagement.CreatePayment, ContractorManagement.PaymentHistory, ContractorManagement.PaymentStatement | | **ContractorOnboarding.OnboardingFlow** | ContractorOnboarding.Address, ContractorOnboarding.ContractorList, ContractorOnboarding.ContractorProfile, ContractorOnboarding.ContractorSubmit, ContractorOnboarding.NewHireReport, ContractorOnboarding.PaymentMethod | | **ContractorOnboarding.SelfOnboardingFlow** | ContractorOnboarding.Address, ContractorOnboarding.ContractorProfile, ContractorOnboarding.DocumentSigner, ContractorOnboarding.Landing, ContractorOnboarding.OnboardingSummary, ContractorOnboarding.PaymentMethod | From 9cee5ead5011be4815d669dbcbd28653526e7387 Mon Sep 17 00:00:00 2001 From: Marie Chatfield Date: Thu, 23 Jul 2026 13:02:12 -0700 Subject: [PATCH 08/10] refactor(PaymentFlow): rename ViewHistoryFlow spoke to ViewPaymentFlow Aligns the view spoke's name with its sibling CreatePaymentFlow -- both are Payment-flow spokes of ContractorManagement.PaymentFlow, and the old ViewHistoryFlow name was the only one that didn't follow that pattern. Pure rename: no behavior change. --- .../PaymentFlow/PaymentFlowComponents.tsx | 6 +-- .../PaymentFlow/paymentStateMachine.ts | 8 ++-- .../Payments/ViewHistoryFlow/index.ts | 2 - .../GUIDE.md | 4 +- .../ViewPaymentFlow.tsx} | 46 +++++++++---------- .../ViewPaymentFlowComponents.tsx} | 10 ++-- .../Payments/ViewPaymentFlow/index.ts | 2 + .../viewPaymentMachine.test.ts} | 14 +++--- .../viewPaymentMachine.ts} | 16 +++---- .../exports/contractorManagement.ts | 2 +- 10 files changed, 55 insertions(+), 55 deletions(-) delete mode 100644 src/components/Contractor/Payments/ViewHistoryFlow/index.ts rename src/components/Contractor/Payments/{ViewHistoryFlow => ViewPaymentFlow}/GUIDE.md (90%) rename src/components/Contractor/Payments/{ViewHistoryFlow/ViewHistoryFlow.tsx => ViewPaymentFlow/ViewPaymentFlow.tsx} (69%) rename src/components/Contractor/Payments/{ViewHistoryFlow/ViewHistoryFlowComponents.tsx => ViewPaymentFlow/ViewPaymentFlowComponents.tsx} (78%) create mode 100644 src/components/Contractor/Payments/ViewPaymentFlow/index.ts rename src/components/Contractor/Payments/{ViewHistoryFlow/viewHistoryMachine.test.ts => ViewPaymentFlow/viewPaymentMachine.test.ts} (86%) rename src/components/Contractor/Payments/{ViewHistoryFlow/viewHistoryMachine.ts => ViewPaymentFlow/viewPaymentMachine.ts} (84%) diff --git a/src/components/Contractor/Payments/PaymentFlow/PaymentFlowComponents.tsx b/src/components/Contractor/Payments/PaymentFlow/PaymentFlowComponents.tsx index 858be30bc5..ae4bf1ca9f 100644 --- a/src/components/Contractor/Payments/PaymentFlow/PaymentFlowComponents.tsx +++ b/src/components/Contractor/Payments/PaymentFlow/PaymentFlowComponents.tsx @@ -1,6 +1,6 @@ import { PaymentsListInternal } from '../PaymentsList/PaymentsList' import { CreatePaymentInternalFlow } from '../CreatePaymentFlow/CreatePaymentFlow' -import { ViewHistoryInternalFlow } from '../ViewHistoryFlow/ViewHistoryFlow' +import { ViewPaymentInternalFlow } from '../ViewPaymentFlow/ViewPaymentFlow' import type { InternalAlert } from '../types' import { InformationRequestsFlow } from '@/components/InformationRequests' import { useFlow, type FlowContextInterface } from '@/components/Flow/useFlow' @@ -54,12 +54,12 @@ export function CreatePaymentFlowContextual() { } /** @internal */ -export function ViewHistoryFlowContextual() { +export function ViewPaymentFlowContextual() { const { currentPaymentId, onEvent } = useFlow() const prefixBreadcrumbs = useLandingPrefixBreadcrumbs() return ( - { return { ...ctx, - component: ViewHistoryFlowContextual, + component: ViewPaymentFlowContextual, currentPaymentId: ev.payload.paymentId, alerts: undefined, } diff --git a/src/components/Contractor/Payments/ViewHistoryFlow/index.ts b/src/components/Contractor/Payments/ViewHistoryFlow/index.ts deleted file mode 100644 index 9701b077d3..0000000000 --- a/src/components/Contractor/Payments/ViewHistoryFlow/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { ViewHistoryFlow } from './ViewHistoryFlow' -export type { ViewHistoryFlowProps } from './ViewHistoryFlowComponents' diff --git a/src/components/Contractor/Payments/ViewHistoryFlow/GUIDE.md b/src/components/Contractor/Payments/ViewPaymentFlow/GUIDE.md similarity index 90% rename from src/components/Contractor/Payments/ViewHistoryFlow/GUIDE.md rename to src/components/Contractor/Payments/ViewPaymentFlow/GUIDE.md index d3492f290e..ae02b2f8a8 100644 --- a/src/components/Contractor/Payments/ViewHistoryFlow/GUIDE.md +++ b/src/components/Contractor/Payments/ViewPaymentFlow/GUIDE.md @@ -1,10 +1,10 @@ -# ViewHistoryFlow +# ViewPaymentFlow ## Step flow -`ViewHistoryFlow` centers on `PaymentHistory` as its hub: it shows a payment group's details and can either drill into an individual contractor's statement (`contractor/payments/view/details` → `PaymentStatement`) or cancel the group outright (`contractor/payments/cancel`), which exits the flow. +`ViewPaymentFlow` centers on `PaymentHistory` as its hub: it shows a payment group's details and can either drill into an individual contractor's statement (`contractor/payments/view/details` → `PaymentStatement`) or cancel the group outright (`contractor/payments/cancel`), which exits the flow. ```mermaid flowchart TD diff --git a/src/components/Contractor/Payments/ViewHistoryFlow/ViewHistoryFlow.tsx b/src/components/Contractor/Payments/ViewPaymentFlow/ViewPaymentFlow.tsx similarity index 69% rename from src/components/Contractor/Payments/ViewHistoryFlow/ViewHistoryFlow.tsx rename to src/components/Contractor/Payments/ViewPaymentFlow/ViewPaymentFlow.tsx index 535c2fa002..44d16def87 100644 --- a/src/components/Contractor/Payments/ViewHistoryFlow/ViewHistoryFlow.tsx +++ b/src/components/Contractor/Payments/ViewPaymentFlow/ViewPaymentFlow.tsx @@ -1,11 +1,11 @@ import { createMachine } from 'robot3' import { useState } from 'react' -import { viewHistoryBreadcrumbsNodes, viewHistoryMachine } from './viewHistoryMachine' +import { viewPaymentBreadcrumbsNodes, viewPaymentMachine } from './viewPaymentMachine' import { PaymentHistoryContextual, - type ViewHistoryFlowContextInterface, - type ViewHistoryFlowProps, -} from './ViewHistoryFlowComponents' + type ViewPaymentFlowContextInterface, + type ViewPaymentFlowProps, +} from './ViewPaymentFlowComponents' import { Flow } from '@/components/Flow/Flow' import type { FlowBreadcrumb } from '@/components/Common/FlowBreadcrumbs/FlowBreadcrumbsTypes' import { buildBreadcrumbs, updateBreadcrumbs } from '@/helpers/breadcrumbHelpers' @@ -13,12 +13,12 @@ import { buildBreadcrumbs, updateBreadcrumbs } from '@/helpers/breadcrumbHelpers const EMPTY_BREADCRUMBS: FlowBreadcrumb[] = [] /** - * Props for the flow-internal {@link ViewHistoryInternalFlow}, which layers a parent flow's - * prefix breadcrumbs on top of the public {@link ViewHistoryFlowProps}. + * Props for the flow-internal {@link ViewPaymentInternalFlow}, which layers a parent flow's + * prefix breadcrumbs on top of the public {@link ViewPaymentFlowProps}. * * @internal */ -export interface ViewHistoryInternalFlowProps extends ViewHistoryFlowProps { +export interface ViewPaymentInternalFlowProps extends ViewPaymentFlowProps { /** * Breadcrumbs prepended to the flow's own breadcrumb trail. Set by a parent flow (e.g. * `PaymentFlow`) so the breadcrumb history remains coherent across the handoff. @@ -31,9 +31,9 @@ export interface ViewHistoryInternalFlowProps extends ViewHistoryFlowProps { * contractor's payment statement. * * @remarks - * This is the inner flow that powers the view-history spoke of `ContractorManagement.PaymentFlow`. + * This is the inner flow that powers the view-payment spoke of `ContractorManagement.PaymentFlow`. * Render it directly when you have built your own payments landing page and want to hand the user - * off to the standard history-viewing experience without re-implementing it. The flow ships with + * off to the standard payment-viewing experience without re-implementing it. The flow ships with * breadcrumb navigation and lets the user cancel an individual payment from the history screen. * * @events @@ -47,8 +47,8 @@ export interface ViewHistoryInternalFlowProps extends ViewHistoryFlowProps { * - {@link PaymentHistory} * - {@link PaymentStatement} * - * @param props - See {@link ViewHistoryFlowProps}. - * @returns The composed view-history flow. + * @param props - See {@link ViewPaymentFlowProps}. + * @returns The composed view-payment flow. * @alpha * * @example @@ -57,7 +57,7 @@ export interface ViewHistoryInternalFlowProps extends ViewHistoryFlowProps { * * function MyApp() { * return ( - * {}} * /> @@ -65,27 +65,27 @@ export interface ViewHistoryInternalFlowProps extends ViewHistoryFlowProps { * } * ``` */ -export function ViewHistoryFlow(props: ViewHistoryFlowProps) { - return +export function ViewPaymentFlow(props: ViewPaymentFlowProps) { + return } /** - * Flow-internal entry point for {@link ViewHistoryFlow} that additionally accepts - * flow-injected `prefixBreadcrumbs`. Partners use {@link ViewHistoryFlow}; `PaymentFlow` renders + * Flow-internal entry point for {@link ViewPaymentFlow} that additionally accepts + * flow-injected `prefixBreadcrumbs`. Partners use {@link ViewPaymentFlow}; `PaymentFlow` renders * this directly to prepend its own breadcrumb trail. * * @internal */ -export function ViewHistoryInternalFlow({ +export function ViewPaymentInternalFlow({ paymentId, onEvent, prefixBreadcrumbs = EMPTY_BREADCRUMBS, -}: ViewHistoryInternalFlowProps) { +}: ViewPaymentInternalFlowProps) { // Built once via a lazy useState initializer, not useMemo -- see CreatePaymentFlow.tsx for why: // a useMemo keyed on `prefixBreadcrumbs` doesn't guarantee the machine's identity survives a // re-render triggered by a bubbled `onEvent` call. - const [viewHistoryFlow] = useState(() => { - const baseBreadcrumbs = buildBreadcrumbs(viewHistoryBreadcrumbsNodes) + const [viewPaymentFlow] = useState(() => { + const baseBreadcrumbs = buildBreadcrumbs(viewPaymentBreadcrumbsNodes) const breadcrumbs = Object.fromEntries( Object.entries(baseBreadcrumbs).map(([stateKey, trail]) => [ stateKey, @@ -102,8 +102,8 @@ export function ViewHistoryInternalFlow({ return createMachine( 'history', - viewHistoryMachine, - (initialContext: ViewHistoryFlowContextInterface) => ({ + viewPaymentMachine, + (initialContext: ViewPaymentFlowContextInterface) => ({ ...initialContext, ...initialBreadcrumbContext, component: PaymentHistoryContextual, @@ -112,5 +112,5 @@ export function ViewHistoryInternalFlow({ ) }) - return + return } diff --git a/src/components/Contractor/Payments/ViewHistoryFlow/ViewHistoryFlowComponents.tsx b/src/components/Contractor/Payments/ViewPaymentFlow/ViewPaymentFlowComponents.tsx similarity index 78% rename from src/components/Contractor/Payments/ViewHistoryFlow/ViewHistoryFlowComponents.tsx rename to src/components/Contractor/Payments/ViewPaymentFlow/ViewPaymentFlowComponents.tsx index fe52cade14..b37ee05bd3 100644 --- a/src/components/Contractor/Payments/ViewHistoryFlow/ViewHistoryFlowComponents.tsx +++ b/src/components/Contractor/Payments/ViewPaymentFlow/ViewPaymentFlowComponents.tsx @@ -5,31 +5,31 @@ import type { BaseComponentInterface } from '@/components/Base' import { ensureRequired } from '@/helpers/ensureRequired' /** - * Props for {@link ViewHistoryFlow}. + * Props for {@link ViewPaymentFlow}. * * @alpha */ -export interface ViewHistoryFlowProps extends BaseComponentInterface { +export interface ViewPaymentFlowProps extends BaseComponentInterface { /** Identifier of the payment group to inspect. */ paymentId: string } /** @internal */ -export interface ViewHistoryFlowContextInterface extends FlowContextInterface { +export interface ViewPaymentFlowContextInterface extends FlowContextInterface { currentPaymentId?: string currentContractorUuid?: string } /** @internal */ export function PaymentHistoryContextual() { - const { currentPaymentId, onEvent } = useFlow() + const { currentPaymentId, onEvent } = useFlow() return } /** @internal */ export function PaymentStatementContextual() { const { currentPaymentId, currentContractorUuid, onEvent } = - useFlow() + useFlow() return ( ({ + viewPaymentMachine, + (initialContext: ViewPaymentFlowContextInterface): ViewPaymentFlowContextInterface => ({ ...initialContext, component: () => null, currentPaymentId: 'payment-123', header: { type: 'breadcrumbs', - breadcrumbs: buildBreadcrumbs(viewHistoryBreadcrumbsNodes), + breadcrumbs: buildBreadcrumbs(viewPaymentBreadcrumbsNodes), currentBreadcrumbId: 'history', }, }), @@ -40,7 +40,7 @@ function currentBreadcrumbId(service: ReturnType) { : undefined } -describe('viewHistoryMachine', () => { +describe('viewPaymentMachine', () => { describe('history state', () => { it('transitions to statement on CONTRACTOR_PAYMENT_VIEW_DETAILS with contractor and payment ids', () => { const service = createService() @@ -73,7 +73,7 @@ describe('viewHistoryMachine', () => { send(service, componentEvents.BREADCRUMB_NAVIGATE, { key: 'history', - onNavigate: ensureRequired(viewHistoryBreadcrumbsNodes.history).item.onNavigate, + onNavigate: ensureRequired(viewPaymentBreadcrumbsNodes.history).item.onNavigate, }) expect(service.machine.current).toBe('history') diff --git a/src/components/Contractor/Payments/ViewHistoryFlow/viewHistoryMachine.ts b/src/components/Contractor/Payments/ViewPaymentFlow/viewPaymentMachine.ts similarity index 84% rename from src/components/Contractor/Payments/ViewHistoryFlow/viewHistoryMachine.ts rename to src/components/Contractor/Payments/ViewPaymentFlow/viewPaymentMachine.ts index 45a74bfe96..3d42cb0294 100644 --- a/src/components/Contractor/Payments/ViewHistoryFlow/viewHistoryMachine.ts +++ b/src/components/Contractor/Payments/ViewPaymentFlow/viewPaymentMachine.ts @@ -4,8 +4,8 @@ import { getContractorDisplayName } from '../CreatePayment/helpers' import { PaymentHistoryContextual, PaymentStatementContextual, - type ViewHistoryFlowContextInterface, -} from './ViewHistoryFlowComponents' + type ViewPaymentFlowContextInterface, +} from './ViewPaymentFlowComponents' import { componentEvents } from '@/shared/constants' import type { MachineEventType, MachineTransition } from '@/types/Helpers' import { updateBreadcrumbs } from '@/helpers/breadcrumbHelpers' @@ -20,17 +20,17 @@ type EventPayloads = { } const breadcrumbNavigateTransition = - createBreadcrumbNavigateTransition() + createBreadcrumbNavigateTransition() /** @internal */ -export const viewHistoryBreadcrumbsNodes: BreadcrumbNodes = { +export const viewPaymentBreadcrumbsNodes: BreadcrumbNodes = { history: { parent: null, item: { id: 'history', label: 'breadcrumbLabel', namespace: 'Contractor.Payments.PaymentHistory', - onNavigate: ((ctx: ViewHistoryFlowContextInterface) => ({ + onNavigate: ((ctx: ViewPaymentFlowContextInterface) => ({ ...updateBreadcrumbs('history', ctx), component: PaymentHistoryContextual, })) as (context: unknown) => unknown, @@ -47,19 +47,19 @@ export const viewHistoryBreadcrumbsNodes: BreadcrumbNodes = { } /** @internal */ -export const viewHistoryMachine = { +export const viewPaymentMachine = { history: state( transition( componentEvents.CONTRACTOR_PAYMENT_VIEW_DETAILS, 'statement', reduce( ( - ctx: ViewHistoryFlowContextInterface, + ctx: ViewPaymentFlowContextInterface, ev: MachineEventType< EventPayloads, typeof componentEvents.CONTRACTOR_PAYMENT_VIEW_DETAILS >, - ): ViewHistoryFlowContextInterface => { + ): ViewPaymentFlowContextInterface => { return { ...updateBreadcrumbs('statement', ctx, { contractorName: getContractorDisplayName(ev.payload.contractor), diff --git a/src/components/Contractor/exports/contractorManagement.ts b/src/components/Contractor/exports/contractorManagement.ts index bdca363e8a..ec14ff7c2f 100644 --- a/src/components/Contractor/exports/contractorManagement.ts +++ b/src/components/Contractor/exports/contractorManagement.ts @@ -5,7 +5,7 @@ export { } from '../ContractorList/management/ManagementContractorList' export { PaymentFlow, type PaymentFlowProps } from '../Payments/PaymentFlow' export { CreatePaymentFlow, type CreatePaymentFlowProps } from '../Payments/CreatePaymentFlow' -export { ViewHistoryFlow, type ViewHistoryFlowProps } from '../Payments/ViewHistoryFlow' +export { ViewPaymentFlow, type ViewPaymentFlowProps } from '../Payments/ViewPaymentFlow' export { PaymentsList, type PaymentsListProps } from '../Payments/PaymentsList/PaymentsList' export { CreatePayment, type CreatePaymentProps } from '../Payments/CreatePayment/CreatePayment' export { PaymentHistory, type PaymentHistoryProps } from '../Payments/PaymentHistory/PaymentHistory' From 04914834ed615947891f0813541943926a8e90fd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 23 Jul 2026 20:07:06 +0000 Subject: [PATCH 09/10] chore: update derived files --- .reports/embedded-react-sdk.api.md | 20 ++++++++++---------- docs/guides/endpoint-inventory.json | 4 ++-- docs/guides/endpoint-reference.md | 4 ++-- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.reports/embedded-react-sdk.api.md b/.reports/embedded-react-sdk.api.md index 63a2dc90be..abe1c2cfe3 100644 --- a/.reports/embedded-react-sdk.api.md +++ b/.reports/embedded-react-sdk.api.md @@ -1775,8 +1775,8 @@ declare namespace ContractorManagement { PaymentFlowProps, CreatePaymentFlow, CreatePaymentFlowProps, - ViewHistoryFlow, - ViewHistoryFlowProps, + ViewPaymentFlow, + ViewPaymentFlowProps, PaymentsList, PaymentsListProps, CreatePayment, @@ -6217,14 +6217,6 @@ export type UseWorkAddressFormResult = HookLoadingResult | UseWorkAddressFormRea // @public export type ValidationMessages = Record & Partial>; -// @alpha -function ViewHistoryFlow(props: ViewHistoryFlowProps): JSX; - -// @alpha -interface ViewHistoryFlowProps extends BaseComponentInterface { - paymentId: string; -} - // @public function ViewHolidayEmployees(props: ViewHolidayEmployeesProps): JSX; @@ -6250,6 +6242,14 @@ interface ViewHolidayScheduleProps extends BaseComponentInterface<'Company.TimeO companyId: string; } +// @alpha +function ViewPaymentFlow(props: ViewPaymentFlowProps): JSX; + +// @alpha +interface ViewPaymentFlowProps extends BaseComponentInterface { + paymentId: string; +} + // @public export const WageType: { readonly Fixed: "Fixed"; diff --git a/docs/guides/endpoint-inventory.json b/docs/guides/endpoint-inventory.json index 702d638e40..d6be8cd5e7 100644 --- a/docs/guides/endpoint-inventory.json +++ b/docs/guides/endpoint-inventory.json @@ -2310,11 +2310,11 @@ "blocks": [ "ContractorManagement.CreatePaymentFlow", "ContractorManagement.PaymentsList", - "ContractorManagement.ViewHistoryFlow", + "ContractorManagement.ViewPaymentFlow", "InformationRequests.InformationRequestsFlow" ] }, - "ContractorManagement.ViewHistoryFlow": { + "ContractorManagement.ViewPaymentFlow": { "blocks": [ "ContractorManagement.CreatePayment", "ContractorManagement.PaymentHistory", diff --git a/docs/guides/endpoint-reference.md b/docs/guides/endpoint-reference.md index c313d93947..4d7fd37c9a 100644 --- a/docs/guides/endpoint-reference.md +++ b/docs/guides/endpoint-reference.md @@ -342,8 +342,8 @@ import inventory from '@gusto/embedded-react-sdk/endpoint-inventory.json' | Flow | Blocks included | | --- | --- | | **ContractorManagement.CreatePaymentFlow** | ContractorManagement.CreatePayment, ContractorManagement.PaymentSummary | -| **ContractorManagement.PaymentFlow** | ContractorManagement.CreatePaymentFlow, ContractorManagement.PaymentsList, ContractorManagement.ViewHistoryFlow, InformationRequests.InformationRequestsFlow | -| **ContractorManagement.ViewHistoryFlow** | ContractorManagement.CreatePayment, ContractorManagement.PaymentHistory, ContractorManagement.PaymentStatement | +| **ContractorManagement.PaymentFlow** | ContractorManagement.CreatePaymentFlow, ContractorManagement.PaymentsList, ContractorManagement.ViewPaymentFlow, InformationRequests.InformationRequestsFlow | +| **ContractorManagement.ViewPaymentFlow** | ContractorManagement.CreatePayment, ContractorManagement.PaymentHistory, ContractorManagement.PaymentStatement | | **ContractorOnboarding.OnboardingFlow** | ContractorOnboarding.Address, ContractorOnboarding.ContractorList, ContractorOnboarding.ContractorProfile, ContractorOnboarding.ContractorSubmit, ContractorOnboarding.NewHireReport, ContractorOnboarding.PaymentMethod | | **ContractorOnboarding.SelfOnboardingFlow** | ContractorOnboarding.Address, ContractorOnboarding.ContractorProfile, ContractorOnboarding.DocumentSigner, ContractorOnboarding.Landing, ContractorOnboarding.OnboardingSummary, ContractorOnboarding.PaymentMethod | From aaf6a3452c3f1d3ea2de0b2c5fcf5d7f4d3ec88c Mon Sep 17 00:00:00 2001 From: Marie Chatfield Date: Fri, 24 Jul 2026 14:15:08 -0700 Subject: [PATCH 10/10] chore: update derived files --- docs/guides/endpoint-inventory.json | 1 - docs/guides/endpoint-reference.md | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/guides/endpoint-inventory.json b/docs/guides/endpoint-inventory.json index 601418839c..e715e13aa1 100644 --- a/docs/guides/endpoint-inventory.json +++ b/docs/guides/endpoint-inventory.json @@ -2334,7 +2334,6 @@ }, "ContractorManagement.ViewPaymentFlow": { "blocks": [ - "ContractorManagement.CreatePayment", "ContractorManagement.PaymentHistory", "ContractorManagement.PaymentStatement" ] diff --git a/docs/guides/endpoint-reference.md b/docs/guides/endpoint-reference.md index 9deb8a1207..bf9ee3aa47 100644 --- a/docs/guides/endpoint-reference.md +++ b/docs/guides/endpoint-reference.md @@ -345,7 +345,7 @@ import inventory from '@gusto/embedded-react-sdk/endpoint-inventory.json' | --- | --- | | **ContractorManagement.CreatePaymentFlow** | ContractorManagement.CreatePayment, ContractorManagement.PaymentSummary | | **ContractorManagement.PaymentFlow** | ContractorManagement.CreatePaymentFlow, ContractorManagement.PaymentsList, ContractorManagement.ViewPaymentFlow, InformationRequests.InformationRequestsFlow | -| **ContractorManagement.ViewPaymentFlow** | ContractorManagement.CreatePayment, ContractorManagement.PaymentHistory, ContractorManagement.PaymentStatement | +| **ContractorManagement.ViewPaymentFlow** | ContractorManagement.PaymentHistory, ContractorManagement.PaymentStatement | | **ContractorOnboarding.OnboardingFlow** | ContractorOnboarding.Address, ContractorOnboarding.ContractorList, ContractorOnboarding.ContractorProfile, ContractorOnboarding.ContractorSubmit, ContractorOnboarding.NewHireReport, ContractorOnboarding.PaymentMethod | | **ContractorOnboarding.SelfOnboardingFlow** | ContractorOnboarding.Address, ContractorOnboarding.ContractorProfile, ContractorOnboarding.DocumentSigner, ContractorOnboarding.Landing, ContractorOnboarding.OnboardingSummary, ContractorOnboarding.PaymentMethod |