feat(console): wire onboarding to real /orgs + eERC onboarding + treasury#63
Conversation
…sury Rewire the onboarding wizard off the demo-era endpoints onto the backend: create the org (POST /orgs), run eERC onboarding for the signed-in address (POST /onboarding/start + a credentialed status stream with a polling fallback), and provision the managed treasury (POST /orgs/:id/treasury). On finish the store refreshes and the now-active org lands in the workspace. RootGate routes a signed-in user with no active org into the wizard. Demo stubs updated to the new shapes. Epic #58 step C. Closes #62.
📝 WalkthroughWalkthroughThe console onboarding flow now creates an organization, starts eERC onboarding with streamed or polled progress, provisions a managed treasury, and routes authenticated sessions without an active organization through the wizard before entering the workspace. ChangesOrganization onboarding
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant OnboardingWizard
participant API
participant Backend
User->>OnboardingWizard: Submit organization details
OnboardingWizard->>API: Create organization
API->>Backend: POST /orgs
Backend-->>API: Organization response
OnboardingWizard->>API: Start eERC onboarding
API->>Backend: POST /onboarding/start
Backend-->>API: Status updates
API-->>OnboardingWizard: Stream or poll onboarding status
OnboardingWizard->>API: Provision treasury
API->>Backend: POST /orgs/{orgId}/treasury
Backend-->>API: Treasury registration response
OnboardingWizard-->>User: Show completion and enter workspace
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
| try { | ||
| source = new EventSource(apiHref("/onboarding/status/stream"), { withCredentials: true }); | ||
| source.addEventListener("status", (event) => { | ||
| try { |
There was a problem hiding this comment.
Default SSE Messages Are Ignored
When /onboarding/status/stream sends standard SSE messages without a custom event: status field, this listener never receives them and the healthy connection does not trigger polling fallback. The wizard then stays in the eERC onboarding step even though the backend is publishing progress.
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/console/src/lib/api.ts
Line: 321
Comment:
**Default SSE Messages Are Ignored**
When `/onboarding/status/stream` sends standard SSE messages without a custom `event: status` field, this listener never receives them and the healthy connection does not trigger polling fallback. The wizard then stays in the eERC onboarding step even though the backend is publishing progress.
How can I resolve this? If you propose a fix, please make it concise.|
Greptile (default SSE messages ignored): by design — the backend's |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/console/src/app/Onboarding.tsx`:
- Around line 171-175: Update the canNext logic in Onboarding to require both a
treasury address and successful registration and consent status before allowing
the treasury step to advance; use the relevant treasury response fields
(registered and consented) alongside treasury?.address, while preserving the
existing org and KYB checks.
In `@apps/console/src/app/RootGate.tsx`:
- Around line 29-35: Update the useEffect in RootGate to explicitly set
orgOnboarding to false whenever session.activeOrg exists, while preserving the
existing false reset when there is no session; ensure the flag is assigned on
both branches so a refreshed active organization cannot leave stale onboarding
state.
In `@apps/console/src/lib/api.test.ts`:
- Around line 23-24: Update the test fixture’s chain configuration to use the
shared Fuji metadata from `@benzo/config`: import the Fuji chain object and shared
chain ID, replace the hardcoded chainId with the shared value, and derive
chainEnv from the imported chain object instead of duplicating "testnet".
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: a58bfe04-1ca7-4043-86a3-d2ede73eeb36
📒 Files selected for processing (7)
apps/console/src/app/Onboarding.tsxapps/console/src/app/RootGate.tsxapps/console/src/demo/api.tsapps/console/src/demo/data.tsapps/console/src/lib/api.test.tsapps/console/src/lib/api.tspackages/types/src/api.ts
| const canNext = | ||
| step.key === "org" ? !!draft.name && !!draft.legalName : | ||
| step.key === "kyb" ? kyb?.status === "approved" : | ||
| step.key === "treasury" ? !!mvk : | ||
| step.key === "org" ? !!draft.name?.trim() : | ||
| step.key === "kyb" ? onboardingComplete : | ||
| step.key === "treasury" ? !!treasury?.address : | ||
| true; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Require registration and consent before finishing onboarding.
An address alone permits advancing even when the response says registered: false or consented: false; users can then enter the workspace despite the treasury UI reporting registration pending.
Proposed fix
- step.key === "treasury" ? !!treasury?.address :
+ step.key === "treasury" ? !!treasury?.address && treasury.registered && treasury.consented :📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const canNext = | |
| step.key === "org" ? !!draft.name && !!draft.legalName : | |
| step.key === "kyb" ? kyb?.status === "approved" : | |
| step.key === "treasury" ? !!mvk : | |
| step.key === "org" ? !!draft.name?.trim() : | |
| step.key === "kyb" ? onboardingComplete : | |
| step.key === "treasury" ? !!treasury?.address : | |
| true; | |
| const canNext = | |
| step.key === "org" ? !!draft.name?.trim() : | |
| step.key === "kyb" ? onboardingComplete : | |
| step.key === "treasury" ? !!treasury?.address && treasury.registered && treasury.consented : | |
| true; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/console/src/app/Onboarding.tsx` around lines 171 - 175, Update the
canNext logic in Onboarding to require both a treasury address and successful
registration and consent status before allowing the treasury step to advance;
use the relevant treasury response fields (registered and consented) alongside
treasury?.address, while preserving the existing org and KYB checks.
| useEffect(() => { | ||
| if (!session) { | ||
| setOrgOnboarding(false); | ||
| return; | ||
| } | ||
| if (!session.activeOrg) setOrgOnboarding(true); | ||
| }, [session]); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Reset orgOnboarding when an active organization appears.
This effect only changes the flag to true. If a refresh later supplies session.activeOrg, the stale flag still routes the user to onboarding because Line 45 checks orgOnboarding first.
Proposed fix
useEffect(() => {
- if (!session) {
- setOrgOnboarding(false);
- return;
- }
- if (!session.activeOrg) setOrgOnboarding(true);
+ setOrgOnboarding(Boolean(session && !session.activeOrg));
}, [session]);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| useEffect(() => { | |
| if (!session) { | |
| setOrgOnboarding(false); | |
| return; | |
| } | |
| if (!session.activeOrg) setOrgOnboarding(true); | |
| }, [session]); | |
| useEffect(() => { | |
| setOrgOnboarding(Boolean(session && !session.activeOrg)); | |
| }, [session]); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/console/src/app/RootGate.tsx` around lines 29 - 35, Update the useEffect
in RootGate to explicitly set orgOnboarding to false whenever session.activeOrg
exists, while preserving the existing false reset when there is no session;
ensure the flag is assigned on both branches so a refreshed active organization
cannot leave stale onboarding state.
| chainEnv: "testnet", | ||
| chainId: 43113, |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== relevant files =="
git ls-files | rg '^(apps/console/src/lib/api\.test\.ts|packages/.*/config|.*config.*)$' || true
echo
echo "== search for `@benzo/config` usage =="
rg -n "`@benzo/config`|chainEnv|chainId|testnet|43113" apps/console/src packages -g '!**/node_modules/**' || true
echo
echo "== map api.test.ts =="
if [ -f apps/console/src/lib/api.test.ts ]; then
wc -l apps/console/src/lib/api.test.ts
cat -n apps/console/src/lib/api.test.ts
fi
echo
echo "== map candidate config files =="
for f in $(git ls-files | rg '^.*config.*\.(ts|tsx|js|mjs|cjs|json)$' | head -n 40); do
echo "--- $f"
wc -l "$f"
doneRepository: Miny-Labs/benzo-console
Length of output: 25688
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== file list =="
git ls-files | rg '^(apps/console/src/lib/api\.test\.ts|.*`@benzo/config`.*|.*config.*)$' || true
echo
echo "== api.test.ts =="
sed -n '1,200p' apps/console/src/lib/api.test.ts
echo
echo "== `@benzo/config` references =="
rg -n "`@benzo/config`|chainEnv|chainId|testnet|43113" apps/console/src packages -g '!**/node_modules/**' || trueRepository: Miny-Labs/benzo-console
Length of output: 15752
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== packages/config/src/index.ts (relevant ranges) =="
sed -n '1,120p' packages/config/src/index.ts
echo
sed -n '120,240p' packages/config/src/index.ts
echo
sed -n '240,320p' packages/config/src/index.ts
echo
echo "== packages/config/test/index.test.ts =="
sed -n '1,120p' packages/config/test/index.test.ts
echo
echo "== apps/console/src/lib/api.ts =="
sed -n '1,220p' apps/console/src/lib/api.tsRepository: Miny-Labs/benzo-console
Length of output: 17830
Import the shared Fuji chain metadata here. chainEnv/chainId still duplicate the test chain setup; pull the chain ID from @benzo/config and derive the env label from the shared chain object so this fixture stays in sync.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/console/src/lib/api.test.ts` around lines 23 - 24, Update the test
fixture’s chain configuration to use the shared Fuji metadata from
`@benzo/config`: import the Fuji chain object and shared chain ID, replace the
hardcoded chainId with the shared value, and derive chainEnv from the imported
chain object instead of duplicating "testnet".
Source: Coding guidelines
Epic: #58 · Step C (Onboarding) · Closes #62
Rewires the console's onboarding wizard off its demo-era endpoints onto the real eERC backend, building on the cookie-SIWE foundation (#60). A signed-in user with no org can now: create an org → run eERC onboarding for their address → provision the managed treasury → land in the workspace with that org active.
What changed
lib/api.ts— real client fns matching the backend:createOrg({name,slug})→POST /orgs;startOnboarding(mockKyc?)→POST /onboarding/start;onboardingStatus()→GET /onboarding/status;subscribeOnboardingStatus()— a credentialedEventSourceon/onboarding/status/streamwith a 2s polling fallback and clean teardown, closing oncomplete/failed;provisionTreasury(orgId)→POST /orgs/:id/treasury {consent:true}. Removed the dead demo-era onboarding/treasury-provision fns + paths.app/Onboarding.tsxWizard — driven entirely by the real calls: Business step →createOrg(slug derived, persistsACTIVE_ORG_KEY = org.id); a live eERC progress view streamed fromonboarding.steps(KYC → allowlist → gas → registration); Treasury step →provisionTreasury(shows the treasury address + registration tx); finish → storerefresh()+ land in workspace.app/RootGate.tsx— a signed-in user with noactiveOrgis routed into the wizard; on finish it refreshes and (org now active) renders the Shell.packages/types—OnboardingStatus(+steps) and org/treasury-provision response types matching the backend.VITE_DEMO_MODE=1still walks the wizard.Out of scope (later epic steps)
Treasury screen (D), payroll (E), hiding no-backend screens (B).
Verification (all green)
typecheck·test(72) ·build·VITE_DEMO_MODE=1 build·biome lint .Summary by CodeRabbit
New Features
Bug Fixes
Greptile Summary
This PR wires console onboarding to the real org, eERC, and treasury APIs. The main changes are:
/orgsand stores the active org id./onboarding/startand renders live step progress./orgs/:id/treasury.Confidence Score: 4/5
The onboarding stream handling needs a fix before merging.
apps/console/src/lib/api.ts
Important Files Changed
Sequence Diagram
%%{init: {'theme': 'neutral'}}%% sequenceDiagram participant User participant Console participant API participant Stream User->>Console: Complete business step Console->>API: POST /orgs API-->>Console: Created org Console->>API: POST /onboarding/start API-->>Console: Initial onboarding status Console->>Stream: GET /onboarding/status/stream Stream-->>Console: Onboarding progress events Console->>API: POST /orgs/:id/treasury API-->>Console: Treasury provisioned Console->>API: Refresh session and orgs Console-->>User: Workspace shell%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%% sequenceDiagram participant User participant Console participant API participant Stream User->>Console: Complete business step Console->>API: POST /orgs API-->>Console: Created org Console->>API: POST /onboarding/start API-->>Console: Initial onboarding status Console->>Stream: GET /onboarding/status/stream Stream-->>Console: Onboarding progress events Console->>API: POST /orgs/:id/treasury API-->>Console: Treasury provisioned Console->>API: Refresh session and orgs Console-->>User: Workspace shellPrompt To Fix All With AI
Reviews (1): Last reviewed commit: "feat(console): wire onboarding to real /..." | Re-trigger Greptile