Skip to content

feat(SDK-1294): add ContractorListFlow - #2694

Open
dmortal wants to merge 4 commits into
mainfrom
feat/SDK-1294-contractor-list-flow
Open

feat(SDK-1294): add ContractorListFlow#2694
dmortal wants to merge 4 commits into
mainfrom
feat/SDK-1294-contractor-list-flow

Conversation

@dmortal

@dmortal dmortal commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds ContractorManagement.ContractorListFlow, a hub-and-spoke flow that composes ContractorList with DashboardFlow and contractor onboarding, so a host app no longer has to hand-roll navigation between these components itself. Surfaced while building a Contractor Management (Steady State) test fest — ContractorList had no built-in way to reach DashboardFlow or onboarding.

Changes

  • New ContractorListFlow: routes "View details" → DashboardFlow, and "Add contractor" / an onboarding-tab row's "Continue"/"Review" → directly into the Profile step of contractor onboarding.
  • Extracted the shared Profile → Address → Payment Method → New Hire Report → Submit step sequence out of onboardingStateMachine.ts into an exported, parameterized createContractorOnboardingSteps factory, reused as-is by both the existing OnboardingFlow (behavior unchanged) and the new ContractorListFlow. This avoids mounting OnboardingFlow as a nested flow, which would otherwise double-render headers and route cancel/submit-done to the wrong list.
  • Adds the contractor/returnToList event and a backToListCta translation key.
  • contractor/dismiss and contractor/rehire are intentionally left unhandled (bubble through onEvent) — no dismissal-style flow exists yet.

Related

Testing

  • npm run test -- --run src/components/Contractor/ContractorListFlow/ContractorListFlow.test.tsx — 5/5 passed
  • npm run test -- --run src/components/Contractor/OnboardingFlow/onboardingStateMachine.test.ts — 4/4 passed (regression check on the shared, refactored state machine)
  • npm run test -- --run (full suite) — 3873/3874 passed, 1 pre-existing unrelated flake (confirmed passing in isolation, untouched by this PR)
  • npx tsc --noEmit, npx eslint, npm run docs:lint, npm run docs:build — all clean
  • Manually verified in the SDK dev app: /contractormanagement/ContractorListFlow → "View details" opens DashboardFlow with a "Back to contractors" header and returns cleanly; "Add contractor" and an onboarding-tab row's "Continue" both land directly on the Profile step (pre-filled for "Continue") with a single header — regression-tested against the double-header bug the nested-flow approach originally produced — and Cancel/Submit return to this same list

dmortal and others added 3 commits September 2, 2026 16:09
…shboardFlow and onboarding

Adds a hub-and-spoke flow composing ContractorManagement.ContractorList with
DashboardFlow ("View details") and the contractor-onboarding Profile step
("Add contractor" / an onboarding-tab row's "Continue"/"Review"), so a host
app no longer has to hand-roll navigation between these components itself.

The onboarding spoke reuses the exact Profile -> Address -> Payment Method ->
New Hire Report -> Submit states ContractorOnboarding.OnboardingFlow already
defines (extracted into an exported, parameterized
createContractorOnboardingSteps factory in onboardingStateMachine.ts) rather
than mounting OnboardingFlow as a nested component -- nesting would have
produced a second, independent header and made cancel/submit resolve against
OnboardingFlow's own internal list instead of this flow's list.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…to the contractor list

ContractorSubmit fires contractor/submit/done twice for the admin (non-self-
onboarding) path: once immediately after the mutation succeeds (bare
{ message }), and again if the admin clicks "Done" on the SubmitDone
confirmation screen once the onboarding-status query refetches as completed
({ onboardingStatus, message }). OnboardingFlow's own list shows a success
banner on return, so reacting to the first fire is correct there. But
ContractorListFlow's list has no such banner, so the same immediate return
left the admin swept back to the list with zero feedback and without ever
seeing the confirmation screen.

Adds an opt-in waitForExplicitSubmitDone option to the shared
createContractorOnboardingSteps factory: it guards the submit-done
transition to hold on the bare-message fire (letting SubmitDone render and
requiring its "Done" click) while still transitioning immediately on the
self-onboarding invite path, which has no confirmation screen of its own.
OnboardingFlow leaves the option unset, so its existing behavior is
unchanged; ContractorListFlow opts in.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The Active-tab row action fired contractor/dismiss but performed no
mutation and had no flow to hand the event off to — clicking it looked like
it worked but did nothing. Removing it from ManagementContractorList (and
thus ContractorListFlow, which reuses it) until a dismissal flow is built
under a separate ticket; "Rehire" is left as-is since that's tracked
separately.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@dmortal
dmortal marked this pull request as ready for review September 4, 2026 14:26
@dmortal
dmortal requested a review from a team as a code owner September 4, 2026 14:26
/** @internal */
export function DashboardFlowContextual() {
const { contractorId, onEvent } = useFlow<ContractorListFlowContextInterface>()
return <DashboardFlow contractorId={ensureRequired(contractorId)} onEvent={onEvent} />

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does the <DashboardFlow/> have the same double header risk?

contractorUpdateTransition,
),
dashboard: state<MachineTransition>(
transition(componentEvents.CONTRACTOR_RETURN_TO_LIST, 'list', returnToList),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If an admin takes an action inside DashboardFlow that effectively removes or changes the contractor (say, a future dismiss/rehire flow, or anything that makes the current contractor no longer viewable), should we auto-navigate back to list, or is clicking "Back to contractors" always the only way out regardless of what happened inside?

Comment thread src/shared/constants.ts
CONTRACTOR_DOCUMENTS_DONE: 'contractor/documents/done',
CONTRACTOR_VIEW: 'contractor/view',
CONTRACTOR_RETURN_TO_LIST: 'contractor/returnToList',
CONTRACTOR_DISMISS: 'contractor/dismiss',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since CONTRACTOR_DISMISS was removed in the other files, should it be removed here too?

status={contractorList.status}
onEdit={handleEdit}
onView={handleView}
onDismiss={handleDismiss}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this removal happen in this PR, or would it be safer/cleaner as its own PR? If a host app was already listening for contractor/dismiss and relying on the menu item being there, couldn't that be a breaking change?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants