feat(gp): example app demos for PayrollAdmin + PayrollEmployee onboarding (PBYR-4044)#1171
feat(gp): example app demos for PayrollAdmin + PayrollEmployee onboarding (PBYR-4044)#1171hamzaremote wants to merge 3 commits into
Conversation
|
Deploy preview for remote-flows ready!
Deployed with vercel-action |
|
Deploy preview for adp-cost-calculator ready!
Deployed with vercel-action |
Adds the Global Payroll demo pages and the server-side auth plumbing: - example/api/jwt_auth.js: server-side employee-assertion JWT minting - example/api/proxy.js: path-based token routing — /v1/employee/* → employee assertion (via x-rf-employment-id header), everything else unchanged - example/api/routes.js: wire the employee-token route - example/src/RemoteFlows.tsx: add authType 'none' for proxy-minted tokens - example/src/PayrollAdminOnboarding.tsx / PayrollEmployeeOnboarding.tsx: demos - example/src/App.tsx: register both demos The employee demo derives the bank substep from the flow bag's selfOnboardingSubsteps rather than calling a hook directly, so it needs no extra SDK export. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4ca62c2 to
3cff59d
Compare
📊 Coverage Report⚪ Coverage unchanged
Detailed BreakdownLines Coverage
Statements Coverage
Functions Coverage
Branches Coverage
✅ Coverage check passed |
- derive the last step from SDK-reachable steps (tax steps only navigable once employeeBag.isComplete) rather than the sidebar list, so Submit/completion fires on the real final step (e.g. bank_account on a pre-enrollment USA run). - federal_taxes "Skip" now finishes (setDone) when it's the last reachable step (USA without jurisdiction), instead of a no-op next(). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ 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 50f9594. Configure here.
…losure Step onSuccess handlers latched `done` from that render's `isLastStep`, so if a later step became reachable after the submit (e.g. enrollment flips isComplete and adds tax steps), the demo showed "Complete" instead of advancing. Now each submit records the step name and completion is derived per-render from `lastSubmittedStep === lastStepKey`, so a step that stops being last no longer ends the flow. Explicit "Finish" on an unavailable tax step still latches done. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
| const currentStep = adminBag.stepState.currentStep.name; | ||
| const allSteps = Object.entries(STEP_LABELS); | ||
|
|
||
| if (adminBag.isLoading && !adminBag.countryCode) { |
There was a problem hiding this comment.
isn't this a bit counterintuitive?
if countryCode is defined and isLoading is triggered by any step the isLoading, it won't show
| const renderStep = () => { | ||
| switch (currentStep) { | ||
| case 'select_country': { | ||
| const isFormReady = |
There was a problem hiding this comment.
shouldn't be isLoading and instead of not rendering the button put a disabled state?
| <> | ||
| <div className='steps-navigation'> | ||
| <ul> | ||
| {allSteps.map(([key, label], index) => ( |
There was a problem hiding this comment.
if for some reason in the future, the steps are dynamic like in the Onboarding & ContractorOnboarding, this won't work and we'll need to refactor it
Basically what we do is set the steps on the hook you read them through the adminBag and do the same iterations
| } | ||
|
|
||
| function GPAdminOnboardingInner() { | ||
| const { data: legalEntities, isLoading } = useGPLegalEntities(COMPANY_ID); |
There was a problem hiding this comment.
I would solve this in the hooks internally.
If there are no legalEntities, this property can be exposed in the adminBag and they can do whatever they want with it
And the legalEntityId pass to the hook can be set internally, instead of expecting people setting them themselves.
if there are no legal entities nothing should be set
Let me know if I am missing anything
| @@ -0,0 +1,564 @@ | |||
| import { useState } from 'react'; | |||
There was a problem hiding this comment.
Can you extract this file to a separated PR
I want to review it standalone to avoid having mixed comments from the other files?

What
The final piece of the Global Payroll onboarding work (see split plan, PR 5): the example-app demos for the admin and employee flows, plus the server-side auth plumbing they need. Both SDK flows (#1142, #1168) are already merged to
main.Contents
example/api/jwt_auth.js— server-side minting of an employee-assertion JWT-bearer token (getEmployeeToken), plus a production guard on the token endpoints.example/api/proxy.js— path-based token routing:/v1/employee/*→ employee assertion (employment resolved from thex-rf-employment-idheader, minted server-side so the FE never holds it); everything else unchanged.example/api/routes.js— wire the/api/fetch-employee-token/:employmentIdroute.example/src/RemoteFlows.tsx— addauthType: 'none'so the inner (employee) context skips the FE auth callback while the proxy mints tokens.example/src/PayrollAdminOnboarding.tsx/PayrollEmployeeOnboarding.tsx— the two demos.example/src/App.tsx— register both demos.Design notes (the open questions from the arch doc)
RemoteFlows-provider pattern (employee demo): the outer context uses a company-manager token to readcountry/jurisdictionfrom the employment (the employee assertion token can't fetch/v1/employments/:id); the inner context uses the employee-scoped token minted by the proxy.x-rf-employment-id; the proxy swaps it for a JWT-bearer employee token on/v1/employee/*.Notable
selfOnboardingSubsteps(not a direct hook call), so it needs no extra SDK export — consistent with dropping theuseGPOnboardingStepspublic export in feat(gp): PayrollAdminOnboarding flow — mutations, schemas, and step components (PBYR-4044) #1142.Verification
type-check(tsc -b) ✅ · oxfmt ✅ · oxlint ✅ (0 warnings on the demos)🤖 Generated with Claude Code
Note
Medium Risk
Introduces employment-scoped JWT minting and proxy token routing in the example server; mitigated by production guards on token endpoints and example-app scope, but auth plumbing changes warrant careful review.
Overview
Adds Global Payroll onboarding demos to the example app: GP Admin (create employment through invitation) and GP Employee (self-onboarding with optional USA tax steps), registered in the app navigation.
Server-side auth for the demos: the example proxy now mints employment-scoped JWT-bearer tokens for
/v1/employee/*when the client sendsx-rf-employment-id, strips that header before forwarding, and exposes a dev-only/api/fetch-employee-token/:employmentIdroute. Token helper endpoints (including company manager) are blocked in production viaNODE_ENVchecks.RemoteFlowsgainsauthType: 'none'so the UI relies on the proxy for tokens instead of FE auth callbacks.The employee demo uses nested
RemoteFlowsproviders: an outer context loads country/jurisdiction from the employment (company-manager path via proxy), and an inner context passesx-rf-employment-idfor employee API calls. Step UI handles bank substeps from the flow bag, tax availability messaging, and completion logic that tracks the last reachable step when enrollment unlocks tax steps.Reviewed by Cursor Bugbot for commit 0124a97. Bugbot is set up for automated code reviews on this repo. Configure here.