Skip to content

feat(gp): PayrollAdminOnboarding flow — mutations, schemas, and step components (PBYR-4044)#1142

Open
hamzaremote wants to merge 9 commits into
mainfrom
feat/gp-admin-onboarding-flow
Open

feat(gp): PayrollAdminOnboarding flow — mutations, schemas, and step components (PBYR-4044)#1142
hamzaremote wants to merge 9 commits into
mainfrom
feat/gp-admin-onboarding-flow

Conversation

@hamzaremote

@hamzaremote hamzaremote commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

Summary

Completes the PayrollAdminOnboarding flow skeleton that was scaffolded in an earlier PR. The scaffold had step state and a render-prop container but no mutations, no schema loading, and no step components — this PR adds all of that.

Part of the GP payroll split plan (GP_PAYROLL_SPLIT_PLAN.md). Stacks on #1134.

What's added:

  • api.ts — React Query hooks for the four admin API calls: useGPFormSchema (schema per step), useGPCreateEmployment, useGPUpdateContractDetails, useGPUpdateAdministrativeDetails, useGPInviteEmployee
  • hooks.tsx — mutations wired into onSubmit, one schema query per step gated by enabled: currentStep === X (lazy, never fetches ahead), refetchSteps() called after each successful submit
  • components/SelectCountryStep, ContractDetailsStep, AdministrativeDetailsStep, InvitationStep, PayrollAdminForm, SubmitButton, BackButton, useStepSubmitHandler
  • src/flows/types.tsGPStepCallbacks<TSuccess> shared callback type (used here and by the employee flow in the next PR)
  • src/index.tsx — exports GPAdminStepCallbacks, useGPOnboardingSteps

Note on InvitationStep: this step doesn't go through useStepSubmitHandler because there's no form — it's a single button that fires the invite mutation directly. It owns its mutation instance and calls adminBag.refetchSteps() / adminBag.next() itself.

Test plan


Note

Medium Risk
Touches employment creation and payroll contract/admin updates plus employee invite; logic bugs could block onboarding or submit wrong data, though patterns match existing flow APIs.

Overview
Completes the PayrollAdminOnboarding flow beyond the earlier scaffold by wiring JSON-schema forms, API mutations, and render-prop step components end to end.

usePayrollAdminOnboarding now loads schemas per step (country picker until a country is chosen, then country-specific basic/contract/admin forms), validates via JSF, and submits: create employment on select_country, PUT contract/admin details on later steps, with refetchSteps() after each success. Country skip only when both employmentId and country are known upfront (fixes bypassing employment creation). employment_id is passed into schema fetches for role-aware fields.

New api.ts hooks cover form schemas, create/update employments, and invite. Step UI includes shared PayrollAdminForm, useStepSubmitHandler (callbacks + adminBag.onSubmit + advance), SubmitButton/BackButton, and InvitationStep (invite mutation only, no form).

Adds shared GPStepCallbacks and exports GPAdminStepCallbacks / useGPOnboardingSteps from the package entry.

Reviewed by Cursor Bugbot for commit 7177dc7. Bugbot is set up for automated code reviews on this repo. Configure here.

Comment thread src/flows/PayrollAdminOnboarding/hooks.tsx
Comment thread src/flows/PayrollAdminOnboarding/hooks.tsx
@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

📦 Bundle Size Report

Metric Current Previous Change Status
Total (gzip) 232.71 kB 226.6 kB +6.11 kB (+2.7%) 🔴
Total (raw) 625.88 kB 612.61 kB +13.27 kB (+2.2%) 🔴
CSS (gzip) 21.45 kB 21.45 kB 0 B (0%) 🟢
CSS (raw) 110.5 kB 110.5 kB 0 B (0%) 🟢

Size Limits

  • ✅ Total gzipped: 232.71 kB / 250 kB (93.1%)
  • ✅ Total raw: 625.88 kB / 650 kB (96.3%)
  • ✅ CSS gzipped: 21.45 kB / 25 kB (85.8%)

Largest Files (Top 5)

  1. chunk-Y3CJRZLA.js - 14 kB (0 B (0%))
  2. styles.css - 10.73 kB (0 B (0%))
  3. index.css - 10.73 kB (0 B (0%))
  4. chunk-EAUI6SOI.js - 6.43 kB (new)
  5. index.js - 6.37 kB (+25 B (+0.4%))
View All Files (359 total)
File Size (gzip) Change
chunk-Y3CJRZLA.js 14 kB 0 B (0%)
styles.css 10.73 kB 0 B (0%)
index.css 10.73 kB 0 B (0%)
chunk-EAUI6SOI.js 6.43 kB new
index.js 6.37 kB +25 B (+0.4%)
chunk-IE2PYWA5.js 6.14 kB new
chunk-G2LZFGZA.js 5.31 kB 0 B (0%)
chunk-S4SLNBZH.js 4.86 kB new
chunk-JTD6BUVG.js 4.77 kB new
chunk-ZWTZIWEM.js 3.89 kB new

✅ Bundle size check passed

@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

📊 Coverage Report

⚠️ Coverage decreased

Metric Current Previous Change Status
Lines 85.78% 88.46% -2.68% 🔴
Statements 85.23% 87.87% -2.64% 🔴
Functions 82.90% 84.84% -1.94% 🔴
Branches 77.85% 79.86% -2.01% 🔴

Detailed Breakdown

Lines Coverage
  • Covered: 3885 / 4529
  • Coverage: 85.78%
  • Change: -2.68% (0 lines)
Statements Coverage
  • Covered: 3947 / 4631
  • Coverage: 85.23%
  • Change: -2.64% (0 statements)
Functions Coverage
  • Covered: 1052 / 1269
  • Coverage: 82.90%
  • Change: -1.94% (0 functions)
Branches Coverage
  • Covered: 2442 / 3137
  • Coverage: 77.85%
  • Change: -2.01% (0 branches)

✅ Coverage check passed

@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Deploy preview for adp-cost-calculator ready!

Project:adp-cost-calculator
Status: ✅  Deploy successful!
Preview URL:https://adp-cost-calculator-8y402ri2q-remotecom.vercel.app
Latest Commit:7177dc7

Deployed with vercel-action

@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Deploy preview for remote-flows ready!

Project:remote-flows
Status: ✅  Deploy successful!
Preview URL:https://remote-flows-eqbmaoc57-remotecom.vercel.app
Latest Commit:7177dc7

Deployed with vercel-action

Comment thread src/flows/PayrollAdminOnboarding/components/SelectCountryStep.tsx

@jordividaller jordividaller left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

General concern: the flow re-implements mechanics that already exist in the lib

Overall this flow tends to reinvent logic that other flows already share, instead of reusing it. A few concrete points:

1. Error handling — reuse handleStepError.
There's a shared helper handleStepError already used by 9 components across ContractorOnboarding (7) and CreateCompany (2). It does the isMutationError check + normalizeFieldErrors + maps errors back onto the form fields in one call. This flow ignores it and re-inlines the same try/catch isMutationError block three times:

  • src/flows/PayrollAdminOnboarding/components/useStepSubmitHandler.ts
  • src/flows/PayrollAdminOnboarding/components/SelectCountryStep.tsx
  • src/flows/PayrollAdminOnboarding/components/InvitationStep.tsx

Please route these through handleStepError like the other flows.

2. Field errors aren't normalized — likely a functional regression, not just style.
The other flows pass NormalizedFieldError[] (via handleStepError / normalizeFieldErrors), which is what attaches server-side validation errors to the correct form fields. Here we pass the raw error.fieldErrors (FieldError[]) with no normalization, so field-level API errors probably won't render under their fields the way they do elsewhere. On top of that, the public GPStepCallbacks type freezes fieldErrors: FieldError[] while the rest of the public API exposes NormalizedFieldError[] — that's an inconsistent public contract. Suggest normalizing and aligning the type on NormalizedFieldError[].

3. SelectCountryStep hand-rolls the country field instead of going through the schema/field pipeline (see the separate comment). ContractorOnboarding even reuses selectCountryStepSchema + countriesOptions for this.

What's legitimately new (not a concern)

  • useGPFormSchema is fair: unlike the other flows that co-locate static schemas in json-schemas/, GP needs a remote per-country/per-form schema (getV1CountriesCountryCodeForm), so a new hook is justified. Minor style nit only: the three parallel useQuery calls gated by enabled + the manual currentSchema switch could collapse into a single hook parameterized by step.
  • PayrollAdminForm / useStepSubmitHandler are fine to exist — every flow has its own <XxxForm>, so that's the norm, not reinvention. The only issue is that useStepSubmitHandler doesn't call handleStepError (see point 1).

Net: the new remote-schema piece is good; the concern is only about the parts that already existed as shared helpers.

Comment thread src/flows/PayrollAdminOnboarding/components/SelectCountryStep.tsx
Comment thread src/flows/PayrollAdminOnboarding/hooks.tsx
Comment thread src/flows/PayrollAdminOnboarding/hooks.tsx
hamzaremote and others added 8 commits July 10, 2026 18:22
Add GPStepCallbacks<TSuccess> to src/flows/types.ts — the shared callback
prop type used by all GP step components. Replace the empty scaffold types
in PayrollAdminOnboarding/types.ts with real component signatures.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
GPAdminStepCallbacks is the public callback type consumers pass to each
admin step component. useGPOnboardingSteps is the hook consumers need to
read step state in an outer company-manager context.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two bugs flagged in review:

1. onSubmit guards returned undefined on failure (missing countryCode,
   missing employmentId, or no id in the create-employment response).
   useStepSubmitHandler treated undefined as success and called next(),
   advancing the wizard with no API write. Guards now throw so the catch
   block handles them correctly.

2. skipCountry only required initialCountryCode, so a pre-filled country
   with no initialEmploymentId would hide the select_country step (the
   only place createEmployment runs) and leave the flow with no
   employmentId for contract_details and beyond. skipCountry now requires
   both initialCountryCode and initialEmploymentId — meaning "skip" is
   only valid when resuming an existing employment.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…type

- Replace duplicated isMutationError try/catch blocks in
  useStepSubmitHandler, SelectCountryStep, and InvitationStep with
  handleStepError, matching the pattern used in ContractorOnboarding
  and CreateCompany
- Align GPStepCallbacks.onError.fieldErrors to NormalizedFieldError[]
  (was FieldError[]) so the public contract is consistent with other flows
- Remove hardcoded <select> HTML and Tailwind classes from
  SelectCountryStep; the component now renders PayrollAdminForm through
  the schema/field pipeline like ContractDetailsStep and
  AdministrativeDetailsStep — country selection is a consumer concern
  exposed via adminBag.setInternalCountryCode

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the three parallel useQuery calls (one per schema step) with a
single useGPFormSchema call parameterized by the current step. This
removes the manual currentSchema switch and the multi-schema isLoading
union, reducing the hook to a single query that naturally tracks the
active step.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Selecting a country via the raw <select> was the only mechanism that set
internalCountryCode; removing it left the basic-information schema
permanently disabled.

Replace the ad-hoc <select> with a schema-driven flow:
- Add a static gpSelectCountrySchema (country_code field, oneOf: [])
- Add useGPCountrySelectSchema that fetches countries and injects them
  as options into the static schema via createHeadlessForm
- On the select_country step, show the country-picker schema until a
  country is chosen (isSelectCountryPhase), then switch to the
  global_payroll_basic_information schema
- Wrap setFieldValues as handleFieldValues: when the country-picker is
  active and country_code changes, automatically call
  setInternalCountryCode so the schema transition happens on field change
  rather than form submit
- Key PayrollAdminForm on adminBag.countryCode in SelectCountryStep so
  the form fully remounts (resetting react-hook-form state) when the
  schema switches from country picker to basic information

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
react-hook-form's watch only fires on user changes, not on mount.
If a consumer prefills country_code via initialValues.basic_information
without passing the countryCode prop, handleFieldValues never runs on the
untouched field and internalCountryCode stays undefined — trapping the
flow in the country-picker phase.

Seed internalCountryCode from initialValues.basic_information.country_code
as a fallback so the basic-information form loads immediately when a
country is already present in the prefilled values.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@remotecom remotecom force-pushed the feat/gp-admin-onboarding-flow branch from 78adb54 to 8ece927 Compare July 10, 2026 16:23

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 3 potential issues.

There are 4 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 8ece927. Configure here.

Comment thread src/flows/PayrollAdminOnboarding/hooks.tsx
Comment thread src/flows/PayrollAdminOnboarding/components/SelectCountryStep.tsx Outdated
Comment thread src/flows/PayrollAdminOnboarding/components/InvitationStep.tsx Outdated
- skipCountry now derives from internalCountryCode (covers initialValues
  fallback), not just the explicit countryCode prop, avoiding a duplicate
  employment create on resume
- sync internalCountryCode on every country_code change, not only during
  the country-picker phase, so edits made from the basic-information form
  aren't submitted under a stale country code
- step forms restore stepState.values for the active step before falling
  back to initialValues, matching ContractorOnboarding/CreateCompany
- InvitationStep now surfaces a missing employmentId through onError
  instead of silently no-op'ing

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@remotecom remotecom requested a review from jordividaller July 10, 2026 16:35
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