Skip to content

feat(gp): example app demos for PayrollAdmin + PayrollEmployee onboarding (PBYR-4044)#1171

Open
hamzaremote wants to merge 3 commits into
mainfrom
feat/gp-example-app
Open

feat(gp): example app demos for PayrollAdmin + PayrollEmployee onboarding (PBYR-4044)#1171
hamzaremote wants to merge 3 commits into
mainfrom
feat/gp-example-app

Conversation

@hamzaremote

@hamzaremote hamzaremote commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

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 the x-rf-employment-id header, minted server-side so the FE never holds it); everything else unchanged.
  • example/api/routes.js — wire the /api/fetch-employee-token/:employmentId route.
  • example/src/RemoteFlows.tsx — add authType: '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)

  • Dual-RemoteFlows-provider pattern (employee demo): the outer context uses a company-manager token to read country/jurisdiction from the employment (the employee assertion token can't fetch /v1/employments/:id); the inner context uses the employee-scoped token minted by the proxy.
  • Proxy auth contract: the FE sends x-rf-employment-id; the proxy swaps it for a JWT-bearer employee token on /v1/employee/*.

Notable

Verification

  • Library build ✅ · example 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 sends x-rf-employment-id, strips that header before forwarding, and exposes a dev-only /api/fetch-employee-token/:employmentId route. Token helper endpoints (including company manager) are blocked in production via NODE_ENV checks. RemoteFlows gains authType: 'none' so the UI relies on the proxy for tokens instead of FE auth callbacks.

The employee demo uses nested RemoteFlows providers: an outer context loads country/jurisdiction from the employment (company-manager path via proxy), and an inner context passes x-rf-employment-id for 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.

@github-actions

github-actions Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Deploy preview for remote-flows ready!

Project:remote-flows
Status: ✅  Deploy successful!
Preview URL:https://remote-flows-lc3drwggv-remotecom.vercel.app
Latest Commit:0124a97

Deployed with vercel-action

@github-actions

github-actions Bot commented Jul 17, 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-ty0cmnifb-remotecom.vercel.app
Latest Commit:0124a97

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>
@remotecom
remotecom force-pushed the feat/gp-example-app branch from 4ca62c2 to 3cff59d Compare July 17, 2026 14:05
Comment thread example/src/PayrollEmployeeOnboarding.tsx
Comment thread example/src/PayrollEmployeeOnboarding.tsx
@github-actions

Copy link
Copy Markdown
Contributor

📊 Coverage Report

⚪ Coverage unchanged

Metric Current Previous Change Status
Lines 83.37% 83.37% 0%
Statements 82.75% 82.75% 0%
Functions 81.32% 81.32% 0%
Branches 75.52% 75.52% 0%

Detailed Breakdown

Lines Coverage
  • Covered: 3971 / 4763
  • Coverage: 83.37%
  • Change: 0% (0 lines)
Statements Coverage
  • Covered: 4035 / 4876
  • Coverage: 82.75%
  • Change: 0% (0 statements)
Functions Coverage
  • Covered: 1071 / 1317
  • Coverage: 81.32%
  • Change: 0% (0 functions)
Branches Coverage
  • Covered: 2474 / 3276
  • Coverage: 75.52%
  • Change: 0% (0 branches)

✅ 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>

@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 1 potential issue.

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 50f9594. Configure here.

Comment thread example/src/PayrollEmployeeOnboarding.tsx
…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) {

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.

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 =

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.

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) => (

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.

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);

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.

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';

@gabrielseco gabrielseco Jul 20, 2026

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.

Can you extract this file to a separated PR

I want to review it standalone to avoid having mixed comments from the other files?

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