Add flow native render support for invite flows#2276
Add flow native render support for invite flows#2276ThaminduDilshan merged 1 commit intoasgardeo:mainfrom
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughBackend flows add PROMPT nodes to surface invite/email and registration-complete UI; frontend removes local invite/email-sent state, adds COPYABLE_TEXT support and FlowComponent.source, adjusts action rendering and i18next config, and updates tests and i18n keys. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Frontend as Frontend (Flow Renderer)
participant Backend as Backend Flow
participant Email as Email Service
User->>Frontend: submit invite/registration
Frontend->>Backend: invoke TASK (send invite/email / provisioning)
Backend->>Email: perform send_registration_email / invite delivery
Email-->>Backend: success
Backend-->>Frontend: PROMPT (registration_email_sent / invite_link_status)
Frontend-->>User: render PROMPT (localized message / copyable link)
User->>Frontend: interact (copy / continue)
Frontend->>Backend: continue -> invite_verify / provisioning
Backend-->>Frontend: PROMPT (registration_complete)
Frontend-->>User: render completion PROMPT then end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~55 minutes Possibly related issues
Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
frontend/apps/thunder-console/src/features/users/pages/UserInvitePage.tsx (1)
147-150: Duplicatedresolvewrapper function.The
resolvewrapper is defined identically in bothInviteUserStepContent(lines 147-148) andInviteUserFlowBridge(lines 669-670). Consider extracting this to a shared utility or custom hook to reduce duplication.♻️ Suggested extraction to reduce duplication
// At file level or in a shared hooks file function useResolve() { const {resolveFlowTemplateLiterals: rawResolve} = useAsgardeo(); return useCallback((text?: string) => (text ? rawResolve(text) : undefined), [rawResolve]); }Then use
const resolve = useResolve();in both components.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/apps/thunder-console/src/features/users/pages/UserInvitePage.tsx` around lines 147 - 150, The file defines the same resolve wrapper twice (in InviteUserStepContent and InviteUserFlowBridge) using useAsgardeo(). Extract this duplication into a shared hook or utility named e.g. useResolve that calls useAsgardeo() to get resolveFlowTemplateLiterals and returns the useCallback((text?: string) => text ? rawResolve(text) : undefined, [rawResolve]); then replace both local wrappers in InviteUserStepContent and InviteUserFlowBridge with const resolve = useResolve(); to remove duplication and keep behavior identical.frontend/apps/thunder-gate/src/components/AcceptInvite/__tests__/AcceptInviteBox.test.tsx (1)
252-261: This test no longer exercises a completion-specific path.
AcceptInviteBoxdoes not branch onisCompleteanymore, so this still passes withisComplete: false. Rename it to reflect plain component rendering, or make the mock change behavior whenisCompleteis set so it actually guards the completion flow.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/apps/thunder-gate/src/components/AcceptInvite/__tests__/AcceptInviteBox.test.tsx` around lines 252 - 261, The test currently named and intended to exercise the completion path doesn't actually depend on isComplete because AcceptInviteBox no longer branches on that flag; update the test so it either (A) is renamed to reflect it only verifies general render behavior (e.g., "renders components") and keeps using createMockAcceptInviteRenderProps/mockAcceptInviteRenderProps as-is, or (B) actually exercises the completion flow by making createMockAcceptInviteRenderProps produce completion-only output when isComplete: true (or adding a completion-specific prop/component) and then assert for UI unique to completion; reference AcceptInviteBox, createMockAcceptInviteRenderProps, and mockAcceptInviteRenderProps when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@frontend/packages/thunder-design/src/components/flow/adapters/__tests__/TextAdapter.test.tsx`:
- Around line 54-60: The test 'uses center alignment when design mode is enabled
and no align prop' currently only verifies presence; update the test for
TextAdapter to assert the element's alignment by checking the rendered element
(obtained via screen.getByText('Hello World')) has the expected center
alignment—either by asserting its computed style (e.g.,
getComputedStyle(el).textAlign === 'center') or that it contains the appropriate
CSS class applied by TextAdapter when designContext.isDesignEnabled is true;
update the expectation after rendering with renderWithProviders(<TextAdapter
component={baseComponent} resolve={(s) => s} />, { designContext:
{isDesignEnabled: true} }) to verify alignment.
In
`@frontend/packages/thunder-design/src/components/flow/adapters/CopyableTextAdapter.tsx`:
- Around line 62-75: The handleCopy callback sets a timeout that calls
setCopied(false) without cleanup, risking state updates after unmount; fix by
storing the timeout ID (e.g., in a ref like copyTimeoutRef) when calling
setTimeout inside handleCopy, clear any existing timeout before setting a new
one, and add a useEffect cleanup that clears copyTimeoutRef.current on unmount
so setCopied is never called after the component has been unmounted.
---
Nitpick comments:
In `@frontend/apps/thunder-console/src/features/users/pages/UserInvitePage.tsx`:
- Around line 147-150: The file defines the same resolve wrapper twice (in
InviteUserStepContent and InviteUserFlowBridge) using useAsgardeo(). Extract
this duplication into a shared hook or utility named e.g. useResolve that calls
useAsgardeo() to get resolveFlowTemplateLiterals and returns the
useCallback((text?: string) => text ? rawResolve(text) : undefined,
[rawResolve]); then replace both local wrappers in InviteUserStepContent and
InviteUserFlowBridge with const resolve = useResolve(); to remove duplication
and keep behavior identical.
In
`@frontend/apps/thunder-gate/src/components/AcceptInvite/__tests__/AcceptInviteBox.test.tsx`:
- Around line 252-261: The test currently named and intended to exercise the
completion path doesn't actually depend on isComplete because AcceptInviteBox no
longer branches on that flag; update the test so it either (A) is renamed to
reflect it only verifies general render behavior (e.g., "renders components")
and keeps using createMockAcceptInviteRenderProps/mockAcceptInviteRenderProps
as-is, or (B) actually exercises the completion flow by making
createMockAcceptInviteRenderProps produce completion-only output when
isComplete: true (or adding a completion-specific prop/component) and then
assert for UI unique to completion; reference AcceptInviteBox,
createMockAcceptInviteRenderProps, and mockAcceptInviteRenderProps when making
the change.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 826aeea2-5a2f-4f37-905f-b78bdbdb0c2d
⛔ Files ignored due to path filters (1)
frontend/pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (19)
backend/cmd/server/bootstrap/flows/registration/registration_flow_basic_invite.jsonbackend/cmd/server/bootstrap/flows/user_onboarding/user_onboarding_flow.jsonbackend/cmd/server/bootstrap/i18n/en-US.jsonfrontend/apps/thunder-console/src/features/users/pages/UserInvitePage.tsxfrontend/apps/thunder-console/src/features/users/pages/__tests__/UserInvitePage.test.tsxfrontend/apps/thunder-console/src/hocs/withI18n.tsxfrontend/apps/thunder-gate/src/components/AcceptInvite/AcceptInviteBox.tsxfrontend/apps/thunder-gate/src/components/AcceptInvite/__tests__/AcceptInviteBox.test.tsxfrontend/apps/thunder-gate/src/components/SignUp/SignUpBox.tsxfrontend/apps/thunder-gate/src/components/SignUp/__tests__/SignUpBox.test.tsxfrontend/packages/thunder-design/src/components/flow/FlowComponentRenderer.tsxfrontend/packages/thunder-design/src/components/flow/__tests__/FlowComponentRenderer.test.tsxfrontend/packages/thunder-design/src/components/flow/adapters/CopyableTextAdapter.tsxfrontend/packages/thunder-design/src/components/flow/adapters/TextAdapter.tsxfrontend/packages/thunder-design/src/components/flow/adapters/__tests__/CopyableTextAdapter.test.tsxfrontend/packages/thunder-design/src/components/flow/adapters/__tests__/TextAdapter.test.tsxfrontend/packages/thunder-design/src/index.tsfrontend/packages/thunder-design/src/models/flow.tsfrontend/pnpm-workspace.yaml
frontend/packages/thunder-design/src/components/flow/adapters/__tests__/TextAdapter.test.tsx
Show resolved
Hide resolved
frontend/packages/thunder-design/src/components/flow/adapters/CopyableTextAdapter.tsx
Show resolved
Hide resolved
aba7582 to
cda3335
Compare
There was a problem hiding this comment.
🧹 Nitpick comments (2)
frontend/packages/thunder-design/src/components/flow/FlowComponentRenderer.tsx (1)
175-178: Keep the supported-types docblock in sync with the new renderer branch.
COPYABLE_TEXTis now supported, but the top-level “Supported types” comment still omits it.📝 Suggested doc update
* - `BLOCK` (form or trigger) → {`@link` BlockAdapter} + * - `COPYABLE_TEXT` → {`@link` CopyableTextAdapter} * - `ACTION / TRIGGER` (standalone) → {`@link` StandaloneTriggerAdapter}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/packages/thunder-design/src/components/flow/FlowComponentRenderer.tsx` around lines 175 - 178, The “Supported types” docblock at the top of FlowComponentRenderer.tsx is out of date and doesn't list the newly supported COPYABLE_TEXT renderer; update that top-level comment to include COPYABLE_TEXT in the list of supported component types so it matches the runtime branch where FlowComponentRenderer checks comp.type === 'COPYABLE_TEXT' and returns CopyableTextAdapter (referencing the CopyableTextAdapter symbol and the conditional in FlowComponentRenderer). Ensure the docblock wording/format matches the existing list style.frontend/apps/thunder-console/src/features/users/pages/__tests__/UserInvitePage.test.tsx (1)
420-423: Avoid order-dependent selection for the footer Close button.Using
closeButtons[closeButtons.length - 1](Line 422) is brittle if header/footer button order changes.✅ Less brittle selector approach
- // Footer close button is the last close button - const closeButtons = screen.getAllByRole('button', {name: /close/i}); - await userEvent.click(closeButtons[closeButtons.length - 1]); + const footerCloseButton = screen + .getAllByRole('button', {name: /close/i}) + .find((button) => button.textContent?.trim().toLowerCase() === 'close'); + expect(footerCloseButton).toBeDefined(); + await userEvent.click(footerCloseButton!);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/apps/thunder-console/src/features/users/pages/__tests__/UserInvitePage.test.tsx` around lines 420 - 423, The test is using an order-dependent array lookup (closeButtons[closeButtons.length - 1]) to click the footer Close button; replace this with a targeted query scoped to the footer container: locate the footer element (e.g., via screen.getByRole('contentinfo') or a footer test id), use within(footer).getByRole('button', { name: /close/i }) to get the footer Close button, then call userEvent.click on that result instead of using closeButtons and array indexing; update the test where closeButtons, screen.getAllByRole, and userEvent.click are used.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In
`@frontend/apps/thunder-console/src/features/users/pages/__tests__/UserInvitePage.test.tsx`:
- Around line 420-423: The test is using an order-dependent array lookup
(closeButtons[closeButtons.length - 1]) to click the footer Close button;
replace this with a targeted query scoped to the footer container: locate the
footer element (e.g., via screen.getByRole('contentinfo') or a footer test id),
use within(footer).getByRole('button', { name: /close/i }) to get the footer
Close button, then call userEvent.click on that result instead of using
closeButtons and array indexing; update the test where closeButtons,
screen.getAllByRole, and userEvent.click are used.
In
`@frontend/packages/thunder-design/src/components/flow/FlowComponentRenderer.tsx`:
- Around line 175-178: The “Supported types” docblock at the top of
FlowComponentRenderer.tsx is out of date and doesn't list the newly supported
COPYABLE_TEXT renderer; update that top-level comment to include COPYABLE_TEXT
in the list of supported component types so it matches the runtime branch where
FlowComponentRenderer checks comp.type === 'COPYABLE_TEXT' and returns
CopyableTextAdapter (referencing the CopyableTextAdapter symbol and the
conditional in FlowComponentRenderer). Ensure the docblock wording/format
matches the existing list style.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 681ea253-e47c-4d10-ac98-e955d2f68d77
⛔ Files ignored due to path filters (1)
frontend/pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (19)
backend/cmd/server/bootstrap/flows/registration/registration_flow_basic_invite.jsonbackend/cmd/server/bootstrap/flows/user_onboarding/user_onboarding_flow.jsonbackend/cmd/server/bootstrap/i18n/en-US.jsonfrontend/apps/thunder-console/src/features/users/pages/UserInvitePage.tsxfrontend/apps/thunder-console/src/features/users/pages/__tests__/UserInvitePage.test.tsxfrontend/apps/thunder-console/src/hocs/withI18n.tsxfrontend/apps/thunder-gate/src/components/AcceptInvite/AcceptInviteBox.tsxfrontend/apps/thunder-gate/src/components/AcceptInvite/__tests__/AcceptInviteBox.test.tsxfrontend/apps/thunder-gate/src/components/SignUp/SignUpBox.tsxfrontend/apps/thunder-gate/src/components/SignUp/__tests__/SignUpBox.test.tsxfrontend/packages/thunder-design/src/components/flow/FlowComponentRenderer.tsxfrontend/packages/thunder-design/src/components/flow/__tests__/FlowComponentRenderer.test.tsxfrontend/packages/thunder-design/src/components/flow/adapters/CopyableTextAdapter.tsxfrontend/packages/thunder-design/src/components/flow/adapters/TextAdapter.tsxfrontend/packages/thunder-design/src/components/flow/adapters/__tests__/CopyableTextAdapter.test.tsxfrontend/packages/thunder-design/src/components/flow/adapters/__tests__/TextAdapter.test.tsxfrontend/packages/thunder-design/src/index.tsfrontend/packages/thunder-design/src/models/flow.tsfrontend/pnpm-workspace.yaml
✅ Files skipped from review due to trivial changes (7)
- frontend/packages/thunder-design/src/models/flow.ts
- frontend/pnpm-workspace.yaml
- frontend/packages/thunder-design/src/components/flow/adapters/TextAdapter.tsx
- frontend/apps/thunder-console/src/hocs/withI18n.tsx
- frontend/packages/thunder-design/src/index.ts
- frontend/apps/thunder-gate/src/components/SignUp/SignUpBox.tsx
- frontend/packages/thunder-design/src/components/flow/tests/FlowComponentRenderer.test.tsx
🚧 Files skipped from review as they are similar to previous changes (10)
- backend/cmd/server/bootstrap/flows/registration/registration_flow_basic_invite.json
- frontend/packages/thunder-design/src/components/flow/adapters/tests/TextAdapter.test.tsx
- backend/cmd/server/bootstrap/flows/user_onboarding/user_onboarding_flow.json
- frontend/apps/thunder-gate/src/components/SignUp/tests/SignUpBox.test.tsx
- frontend/packages/thunder-design/src/components/flow/adapters/tests/CopyableTextAdapter.test.tsx
- frontend/apps/thunder-gate/src/components/AcceptInvite/AcceptInviteBox.tsx
- frontend/packages/thunder-design/src/components/flow/adapters/CopyableTextAdapter.tsx
- backend/cmd/server/bootstrap/i18n/en-US.json
- frontend/apps/thunder-gate/src/components/AcceptInvite/tests/AcceptInviteBox.test.tsx
- frontend/apps/thunder-console/src/features/users/pages/UserInvitePage.tsx
e33ba9d to
c8c248b
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #2276 +/- ##
==========================================
- Coverage 89.50% 89.49% -0.01%
==========================================
Files 913 913
Lines 60372 60361 -11
==========================================
- Hits 54033 54022 -11
+ Misses 4707 4706 -1
- Partials 1632 1633 +1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
8612bea to
c5b7c81
Compare
| lng: I18nDefaultConstants.FALLBACK_LANGUAGE, | ||
| fallbackLng: I18nDefaultConstants.FALLBACK_LANGUAGE, | ||
| defaultNS: 'common', | ||
| keySeparator: false, |
There was a problem hiding this comment.
Why do we need this change
There was a problem hiding this comment.
Without this i18n keys won't be resolved for the admin invite components. In other console flows, i18n keys are resolved from the resource bundle. But since this is a flow/execution, i18n has to be resolved with flow/meta.
The flow definition's i18n keys (delivered via bootstrap/meta) use flat dot-notation (e.g. onboarding.forms.invite_mode.title). Without keySeparator: false, i18next treats the dots as path separators and fails to resolve them
There was a problem hiding this comment.
The console's i18n resource bundle uses flat dot-notation keys within each namespace (e.g., 'actions.close': 'Close' inside the common namespace). By default, i18next uses . as a key separator, so it tries to resolve actions.close as a nested path actions → close — which fails because the keys are flat strings.
Before this PR, every t() call had a hardcoded fallback (e.g., t('common:actions.close', 'Close')), so the failed resolution was silently masked — translations were never actually resolving from the bundle, always falling through to the second argument.
This PR introduced t() calls without fallbacks (e.g., t(resolve(heading.label)) for flow-resolved labels), which made the broken resolution visible. keySeparator: false tells i18next to treat the full dotted string as a literal key, fixing resolution for both the console's own resource bundle and the flow/meta translations.
There was a problem hiding this comment.
@coderabbitai can you check whether this is correct?
There was a problem hiding this comment.
@ThaminduDilshan — yes, the explanation is correct. Here's the verification:
Default keySeparator behaviour: The i18next keySeparator option defaults to '.' as the character used to separate keys. If working with a flat JSON, it's recommended to set this to false.
So without keySeparator: false, a key like actions.close (stored as a flat top-level string in the JSON resource bundle) would be interpreted as a nested path — i.e., i18next would try to walk actions → close in the translation object, find nothing, and fall back to whatever the second t() argument was.
This confirms all three points in your explanation:
- ✅ Flat keys break under default config — i18next's dot-as-separator default causes flat dotted keys like
onboarding.forms.invite_mode.titleoractions.closeto be misinterpreted as nested paths, resulting in lookup failure. - ✅ Previous
t()fallbacks silently masked it — hardcoded second arguments (e.g.,t('common:actions.close', 'Close')) meant failures went unnoticed; the UI still showed English strings, just from the fallback, not the bundle. - ✅
keySeparator: falseis the correct fix — it instructs i18next to treat the entire dotted string as a literal flat key, fixing resolution for both the console's own bundle and the flow/meta translation keys delivered from bootstrap.
The change is well-motivated and aligns with the official i18next recommendation for flat JSON translation files.
✏️ Learnings added
Learnt from: ThaminduDilshan
Repo: asgardeo/thunder PR: 2276
File: frontend/apps/thunder-console/src/hocs/withI18n.tsx:33-33
Timestamp: 2026-04-09T13:59:30.990Z
Learning: In `frontend/apps/thunder-console/src/hocs/withI18n.tsx` (asgardeo/thunder), `keySeparator: false` was added to the i18next init config because the console's i18n resource bundle uses flat dot-notation keys (e.g., `'actions.close': 'Close'` inside the `common` namespace). By default, i18next uses `.` as a key separator and tries to resolve `actions.close` as a nested path `actions → close`, which fails for flat keys. Before PR `#2276`, every `t()` call had a hardcoded fallback (e.g., `t('common:actions.close', 'Close')`), which silently masked the resolution failure. PR `#2276` introduced `t()` calls without fallbacks for flow-resolved labels, making the broken resolution visible. `keySeparator: false` fixes this for both the console's own resource bundle and the flow/meta translations from bootstrap. Do not flag this setting as unnecessary in future reviews.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
🧠 Learnings used
Learnt from: brionmario
Repo: asgardeo/thunder PR: 1838
File: frontend/apps/thunder-console/src/features/flows/components/resources/elements/adapters/TypographyAdapter.tsx:120-121
Timestamp: 2026-03-16T17:45:17.697Z
Learning: In `frontend/apps/thunder-console/src/features/flows/components/resources/elements/adapters/TypographyAdapter.tsx`, the `align` property read from `typographyElement?.align` is intentionally NOT given a `?? 'left'` fallback. Legacy typography resources without an `align` property are expected to use MUI's default alignment. Do not flag the absence of a nullish fallback here as a bug or inconsistency in future reviews.
Learnt from: ThaminduDilshan
Repo: asgardeo/thunder PR: 2276
File: backend/cmd/server/bootstrap/flows/user_onboarding/user_onboarding_flow.json:260-261
Timestamp: 2026-04-09T13:54:02.325Z
Learning: In `backend/cmd/server/bootstrap/flows/` JSON flow definitions (asgardeo/thunder), the top-level `message` field on display-only PROMPT nodes (e.g., `invite_link_status`, `registration_complete`) is intentionally hardcoded in English. It is NOT a UI-rendered field — it is used for non-verbose/internal flow execution status signaling. Do not flag hardcoded English strings in these `message` fields as localization issues in future reviews.
Learnt from: brionmario
Repo: asgardeo/thunder PR: 1838
File: frontend/apps/thunder-console/src/features/organization-units/components/OrganizationUnitsTreeView.tsx:510-552
Timestamp: 2026-03-16T18:14:09.535Z
Learning: In `asgardeo/thunder`, the `handleRootLoadMore` callback in `frontend/apps/thunder-console/src/features/organization-units/components/OrganizationUnitsTreeView.tsx` was only reformatted (not functionally changed) in PR `#1838`. The potential re-entry race condition flagged in that block is a pre-existing issue and should not be raised as a new finding against formatting-only diffs of that function.
Learnt from: brionmario
Repo: asgardeo/thunder PR: 1838
File: frontend/apps/thunder-console/src/components/GatePreview/mocks/buildPreviewMock.ts:81-87
Timestamp: 2026-03-16T18:14:21.361Z
Learning: In `frontend/apps/thunder-console/src/components/GatePreview/mocks/buildPreviewMock.ts`, the `variant` field for the heading component uses the string literal `'HEADING_1'` (not a short token like `'n'`). This is intentional — the GatePreview mock works with the `asgardeo/react` SDK's EmbeddedFlowComponent format which uses full variant names. Do not flag `'HEADING_1'` as a mismatch with any token mapping in `elements.ts` in future reviews.
Learnt from: brionmario
Repo: asgardeo/thunder PR: 1966
File: frontend/packages/thunder-eslint-plugin/src/index.ts:84-103
Timestamp: 2026-03-26T14:55:00.968Z
Learning: In `asgardeo/thunder`, the `frontend/packages/thunder-eslint-plugin` package is used internally only. Backward compatibility for exported config aliases (e.g., `configs.base`) does not need to be preserved when renaming or restructuring them. Do not flag removal of previously exported aliases as breaking changes for this package.
Learnt from: brionmario
Repo: asgardeo/thunder PR: 1942
File: frontend/apps/thunder-console/src/features/applications/components/edit-application/token-settings/ScopeSelector.tsx:246-265
Timestamp: 2026-03-25T10:42:28.903Z
Learning: In `frontend/apps/thunder-console/src/features/applications/components/edit-application/token-settings/ScopeSelector.tsx`, the custom scope TextField intentionally omits the `label` prop because a visible uppercase "CUSTOM" section heading (rendered as a `Typography` caption above the input) already provides the visual and contextual label for the field. Do not flag the missing `label` prop on this TextField in future reviews.
Learnt from: Dilusha-Madushan
Repo: asgardeo/thunder PR: 1970
File: backend/cmd/server/bootstrap/flows/registration/registration_flow_basic_invite.json:206-216
Timestamp: 2026-03-27T04:22:52.079Z
Learning: In `frontend/apps/thunder-gate/src/components/SignUp/SignUpBox.tsx` (PR `#1970`), `emailSent` state is `boolean | null`: `null` = initial/unknown, `true` = email delivered (show "Check your inbox" alert), `false` = email skipped due to missing SMTP config (show "Email service unavailable" warning). The Thunder flow engine accumulates `AdditionalData` across all steps, so `emailSent: "false"` set by `EmailExecutor` in the `send_registration_email` node is still present in the response from subsequent nodes (e.g., `invite_verify`). The `onFlowChange` callback captures both `'true'` and `'false'` values, allowing the frontend to surface the correct UI state even when the flow has already progressed past the email step. Do not flag the `send_registration_email → invite_verify` transition as a dead-end for the no-SMTP case; the recovery is handled at the frontend layer via the accumulated `emailSent` flag.
Learnt from: ThaminduDilshan
Repo: asgardeo/thunder PR: 2224
File: api/flow-execution.yaml:88-108
Timestamp: 2026-04-07T03:48:11.069Z
Learning: In `api/flow-execution.yaml` (PR `#2224`, asgardeo/thunder), the `data.message` field in the `displayOnlyFlow` response example is intentionally placed at the top level of the `Data` object and reflects the actual backend implementation for display-only PROMPT nodes. The `components/schemas/Data` schema is outdated and does not yet include this `message` property. Updating the schema to match the implementation is deferred to a follow-up PR. Do not flag `data.message` as inconsistent with the schema in this PR.
Learnt from: Dilusha-Madushan
Repo: asgardeo/thunder PR: 1970
File: frontend/apps/thunder-gate/src/components/SignUp/SignUpBox.tsx:87-94
Timestamp: 2026-03-26T21:26:45.918Z
Learning: In `frontend/apps/thunder-gate/src/components/SignUp/SignUpBox.tsx`, the `if (!error)` branch inside `renderFlowContent` is intentional. It shows a generic "Oops" fallback only when the flow unexpectedly returns no components, no `emailSent` flag, and no error. When an error IS present, `renderFlowContent` returns `null` because the error is already rendered by the parent block (`{error && <Alert>...}`) above the `renderFlowContent` call. Do not flag this as an inverted condition in future reviews.
Learnt from: DonOmalVindula
Repo: asgardeo/thunder PR: 2246
File: backend/cmd/server/bootstrap/flows/authentication/auth_flow_disambiguation.json:81-167
Timestamp: 2026-04-08T00:52:54.689Z
Learning: In `backend/cmd/server/bootstrap/flows/authentication/auth_flow_disambiguation.json`, pre-password OU disambiguation (routing to `prompt_disambiguate` before `prompt_password`) is intentional and architecturally required: passwords are bound to per-OU user records, so the correct account must be resolved before credential verification. This matches the enterprise-SSO pattern (Okta, Azure AD). The `IdentifyingExecutor` only triggers disambiguation for genuinely multi-OU users, so disclosure is scoped. The SELECT component (showing OU options as a dropdown) is a flow-designer choice; operators can switch to TEXT_INPUT to avoid dropdown-based enumeration — backend behaviour is identical either way. Do not flag this pre-password disambiguation as a security issue in future reviews.
frontend/apps/thunder-gate/src/components/AcceptInvite/AcceptInviteBox.tsx
Outdated
Show resolved
Hide resolved
61abcda to
b8527a0
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@backend/cmd/server/bootstrap/flows/user_onboarding/user_onboarding_flow.json`:
- Around line 260-261: The top-level "message" fields in the onboarding flow
JSON are hard-coded English strings and must be replaced with references to the
same i18n keys used by the visible TEXT components so clients reading
data.message see localized text; update the "message" values (e.g., the entry
whose "next" is "invite_verify" and the other occurrence around lines 488-489)
to use the corresponding i18n key name (same key used by the component) or a
localization token instead of the literal "The invite link is ready to share",
ensuring the Data payload sources the string from the same i18n resource as the
UI.
In
`@frontend/apps/thunder-console/src/features/users/pages/__tests__/UserInvitePage.test.tsx`:
- Around line 805-836: The test currently only asserts the untouched "Get Link"
button; update it to also verify the clicked STACK action entered loading state:
after clicking the "Send Email" button (the submitAction with id 'act-email'
used in mockInviteUserRenderProps.components and mockHandleSubmit), assert that
the "Send Email" button is disabled and shows the loading spinner (e.g., no
visible label or spinner indicator) while mockInviteUserRenderProps.isLoading is
true, before calling resolveSubmit(); ensure you use the same queries
(screen.getByRole / userEvent.click) used elsewhere so the assertion targets the
actual clicked action in UserInvitePage.
In
`@frontend/packages/thunder-design/src/components/flow/adapters/CopyableTextAdapter.tsx`:
- Around line 118-129: The Button in CopyableTextAdapter.tsx currently uses a
fixed aria-label ("Copy") which prevents assistive tech from reflecting the
success state; update the aria-label on the Button (the element using copied,
startIcon and handleCopy) to be conditional based on the copied boolean (e.g.,
use the localized "Copied!" when copied is true and "Copy" when false) or remove
the explicit aria-label entirely so the visible button text
(t('common:actions.copy') / t('common:actions.copied')) becomes the accessible
name; ensure the change uses the same t(...) translations already in use.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 9090cea9-3f47-48f2-94e9-5abf8af0bde1
⛔ Files ignored due to path filters (1)
frontend/pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (19)
backend/cmd/server/bootstrap/flows/registration/registration_flow_basic_invite.jsonbackend/cmd/server/bootstrap/flows/user_onboarding/user_onboarding_flow.jsonbackend/cmd/server/bootstrap/i18n/en-US.jsonfrontend/apps/thunder-console/src/features/users/pages/UserInvitePage.tsxfrontend/apps/thunder-console/src/features/users/pages/__tests__/UserInvitePage.test.tsxfrontend/apps/thunder-console/src/hocs/withI18n.tsxfrontend/apps/thunder-gate/src/components/AcceptInvite/AcceptInviteBox.tsxfrontend/apps/thunder-gate/src/components/AcceptInvite/__tests__/AcceptInviteBox.test.tsxfrontend/apps/thunder-gate/src/components/SignUp/SignUpBox.tsxfrontend/apps/thunder-gate/src/components/SignUp/__tests__/SignUpBox.test.tsxfrontend/packages/thunder-design/src/components/flow/FlowComponentRenderer.tsxfrontend/packages/thunder-design/src/components/flow/__tests__/FlowComponentRenderer.test.tsxfrontend/packages/thunder-design/src/components/flow/adapters/CopyableTextAdapter.tsxfrontend/packages/thunder-design/src/components/flow/adapters/TextAdapter.tsxfrontend/packages/thunder-design/src/components/flow/adapters/__tests__/CopyableTextAdapter.test.tsxfrontend/packages/thunder-design/src/components/flow/adapters/__tests__/TextAdapter.test.tsxfrontend/packages/thunder-design/src/index.tsfrontend/packages/thunder-design/src/models/flow.tsfrontend/pnpm-workspace.yaml
✅ Files skipped from review due to trivial changes (7)
- frontend/pnpm-workspace.yaml
- frontend/packages/thunder-design/src/models/flow.ts
- frontend/packages/thunder-design/src/components/flow/adapters/TextAdapter.tsx
- frontend/packages/thunder-design/src/components/flow/adapters/tests/TextAdapter.test.tsx
- frontend/packages/thunder-design/src/index.ts
- frontend/packages/thunder-design/src/components/flow/tests/FlowComponentRenderer.test.tsx
- frontend/apps/thunder-console/src/features/users/pages/UserInvitePage.tsx
🚧 Files skipped from review as they are similar to previous changes (7)
- frontend/apps/thunder-console/src/hocs/withI18n.tsx
- backend/cmd/server/bootstrap/flows/registration/registration_flow_basic_invite.json
- frontend/apps/thunder-gate/src/components/SignUp/SignUpBox.tsx
- frontend/apps/thunder-gate/src/components/SignUp/tests/SignUpBox.test.tsx
- frontend/packages/thunder-design/src/components/flow/adapters/tests/CopyableTextAdapter.test.tsx
- frontend/apps/thunder-gate/src/components/AcceptInvite/tests/AcceptInviteBox.test.tsx
- backend/cmd/server/bootstrap/i18n/en-US.json
backend/cmd/server/bootstrap/flows/user_onboarding/user_onboarding_flow.json
Show resolved
Hide resolved
frontend/apps/thunder-console/src/features/users/pages/__tests__/UserInvitePage.test.tsx
Show resolved
Hide resolved
frontend/packages/thunder-design/src/components/flow/adapters/CopyableTextAdapter.tsx
Show resolved
Hide resolved
b8527a0 to
f931612
Compare
f931612 to
1cea396
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
frontend/apps/thunder-gate/src/components/AcceptInvite/AcceptInviteBox.tsx (1)
112-140: Add a fallback for empty-component states to avoid a blank card.If the flow returns no components and no explicit error, the UI renders nothing. A small fallback here would improve resilience for misconfigured or partial flows.
♻️ Proposed change
// Loading if (isLoading && !components?.length) { return ( <Box sx={{display: 'flex', justifyContent: 'center', p: 3}}> <CircularProgress /> </Box> ); } + + // Prevent blank UI if flow returns no renderable components. + if (!components?.length && !(flowError ?? error)) { + return ( + <Alert severity="warning"> + <AlertTitle>{t('invite:errors.failed.title', 'Error')}</AlertTitle> + {t('invite:errors.failed.description', 'An error occurred.')} + </Alert> + ); + } return (Based on learnings,
frontend/apps/thunder-gate/src/components/SignUp/SignUpBox.tsxintentionally includes a non-empty fallback for “no components, no error” states to prevent blank screens.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/apps/thunder-gate/src/components/AcceptInvite/AcceptInviteBox.tsx` around lines 112 - 140, The AcceptInviteBox currently renders nothing when there are no components and no error; update the AcceptInviteBox JSX to include a non-empty fallback for the "no components, no error" case (similar to SignUpBox.tsx) by adding an else branch after the components?.length check that renders a user-facing placeholder (for example an Alert or Typography with a translated message via t('invite:empty'...) and optionally a retry or contact action). Ensure the fallback is shown when components is empty/null and both flowError and error are falsy, referencing the existing symbols: AcceptInviteBox, components, flowError, error, and reuse the translation pattern used elsewhere.frontend/apps/thunder-console/src/features/users/pages/UserInvitePage.tsx (1)
147-148: Duplicatedresolvecallback pattern.The same
resolvecallback wrappingrawResolveis defined identically in bothInviteUserStepContent(lines 147-148) andInviteUserFlowBridge(lines 669-670). Consider extracting this to a shared utility or custom hook to reduce duplication.Also applies to: 669-670
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/apps/thunder-console/src/features/users/pages/UserInvitePage.tsx` around lines 147 - 148, The duplicate resolve callback (const {resolveFlowTemplateLiterals: rawResolve} = useAsgardeo(); const resolve = useCallback((text?: string) => (text ? rawResolve(text) : undefined), [rawResolve]);) is defined in both InviteUserStepContent and InviteUserFlowBridge; extract this into a small shared helper (e.g., a utility function or custom hook like useAsgardeoResolve or resolveFlowTemplate) that accepts rawResolve (or calls useAsgardeo internally) and returns the null-safe resolve function, then replace the inline definitions in InviteUserStepContent and InviteUserFlowBridge with that shared hook/util to remove duplication.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@frontend/apps/thunder-console/src/features/users/pages/UserInvitePage.tsx`:
- Around line 104-110: The hasActionsOrInputs function's inputTypes Set is
missing 'PASSWORD_INPUT', so update the inputTypes constant inside
hasActionsOrInputs to include 'PASSWORD_INPUT' (e.g., add 'PASSWORD_INPUT' to
the Set declaration) so prompt_credential and similar steps are treated as
interactive; keep the recursive check on c.components and the existing type
checks in place.
In
`@frontend/apps/thunder-gate/src/components/SignUp/__tests__/SignUpBox.test.tsx`:
- Around line 181-193: The test currently uses optional chaining on
capturedOnFlowChange which lets the test pass even if the flow-change callback
was never captured; update the test to explicitly assert the callback exists
before invoking it—e.g., add an assertion like
expect(capturedOnFlowChange).toBeDefined() (or fail the test if undefined) and
then call capturedOnFlowChange({data:{additionalData:{}}}) instead of using
optional chaining; reference the capturedOnFlowChange variable in
SignUpBox.test.tsx to locate and harden the flow-change invocation.
---
Nitpick comments:
In `@frontend/apps/thunder-console/src/features/users/pages/UserInvitePage.tsx`:
- Around line 147-148: The duplicate resolve callback (const
{resolveFlowTemplateLiterals: rawResolve} = useAsgardeo(); const resolve =
useCallback((text?: string) => (text ? rawResolve(text) : undefined),
[rawResolve]);) is defined in both InviteUserStepContent and
InviteUserFlowBridge; extract this into a small shared helper (e.g., a utility
function or custom hook like useAsgardeoResolve or resolveFlowTemplate) that
accepts rawResolve (or calls useAsgardeo internally) and returns the null-safe
resolve function, then replace the inline definitions in InviteUserStepContent
and InviteUserFlowBridge with that shared hook/util to remove duplication.
In `@frontend/apps/thunder-gate/src/components/AcceptInvite/AcceptInviteBox.tsx`:
- Around line 112-140: The AcceptInviteBox currently renders nothing when there
are no components and no error; update the AcceptInviteBox JSX to include a
non-empty fallback for the "no components, no error" case (similar to
SignUpBox.tsx) by adding an else branch after the components?.length check that
renders a user-facing placeholder (for example an Alert or Typography with a
translated message via t('invite:empty'...) and optionally a retry or contact
action). Ensure the fallback is shown when components is empty/null and both
flowError and error are falsy, referencing the existing symbols:
AcceptInviteBox, components, flowError, error, and reuse the translation pattern
used elsewhere.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: aeb78068-7e37-40cf-ba00-60ae6f0bd4dc
⛔ Files ignored due to path filters (1)
frontend/pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (19)
backend/cmd/server/bootstrap/flows/registration/registration_flow_basic_invite.jsonbackend/cmd/server/bootstrap/flows/user_onboarding/user_onboarding_flow.jsonbackend/cmd/server/bootstrap/i18n/en-US.jsonfrontend/apps/thunder-console/src/features/users/pages/UserInvitePage.tsxfrontend/apps/thunder-console/src/features/users/pages/__tests__/UserInvitePage.test.tsxfrontend/apps/thunder-console/src/hocs/withI18n.tsxfrontend/apps/thunder-gate/src/components/AcceptInvite/AcceptInviteBox.tsxfrontend/apps/thunder-gate/src/components/AcceptInvite/__tests__/AcceptInviteBox.test.tsxfrontend/apps/thunder-gate/src/components/SignUp/SignUpBox.tsxfrontend/apps/thunder-gate/src/components/SignUp/__tests__/SignUpBox.test.tsxfrontend/packages/thunder-design/src/components/flow/FlowComponentRenderer.tsxfrontend/packages/thunder-design/src/components/flow/__tests__/FlowComponentRenderer.test.tsxfrontend/packages/thunder-design/src/components/flow/adapters/CopyableTextAdapter.tsxfrontend/packages/thunder-design/src/components/flow/adapters/TextAdapter.tsxfrontend/packages/thunder-design/src/components/flow/adapters/__tests__/CopyableTextAdapter.test.tsxfrontend/packages/thunder-design/src/components/flow/adapters/__tests__/TextAdapter.test.tsxfrontend/packages/thunder-design/src/index.tsfrontend/packages/thunder-design/src/models/flow.tsfrontend/pnpm-workspace.yaml
✅ Files skipped from review due to trivial changes (9)
- frontend/pnpm-workspace.yaml
- frontend/packages/thunder-design/src/models/flow.ts
- frontend/apps/thunder-console/src/hocs/withI18n.tsx
- frontend/packages/thunder-design/src/index.ts
- frontend/packages/thunder-design/src/components/flow/adapters/TextAdapter.tsx
- frontend/apps/thunder-gate/src/components/SignUp/SignUpBox.tsx
- frontend/packages/thunder-design/src/components/flow/adapters/tests/TextAdapter.test.tsx
- frontend/packages/thunder-design/src/components/flow/tests/FlowComponentRenderer.test.tsx
- frontend/apps/thunder-console/src/features/users/pages/tests/UserInvitePage.test.tsx
🚧 Files skipped from review as they are similar to previous changes (5)
- backend/cmd/server/bootstrap/flows/registration/registration_flow_basic_invite.json
- frontend/packages/thunder-design/src/components/flow/FlowComponentRenderer.tsx
- frontend/packages/thunder-design/src/components/flow/adapters/tests/CopyableTextAdapter.test.tsx
- backend/cmd/server/bootstrap/i18n/en-US.json
- frontend/apps/thunder-gate/src/components/AcceptInvite/tests/AcceptInviteBox.test.tsx
frontend/apps/thunder-console/src/features/users/pages/UserInvitePage.tsx
Show resolved
Hide resolved
frontend/apps/thunder-gate/src/components/SignUp/__tests__/SignUpBox.test.tsx
Show resolved
Hide resolved
1cea396 to
e30c503
Compare
Purpose
This pull request enhances the user registration and onboarding flows by introducing clearer, flow based prompts for admin invite and self invite registration flows. It adds new UI steps for sending invites via email or link, provides user feedback after each action, and updates translations to support these changes. With this, customizations done to admin user invite and gate registration pages will be replaced with flow definition based views.
🔧 Summary of Breaking Changes
This PR introduces a minor breaking change to the default user invite flow when it involved with email invite delivery. Default admin user invite flow now only supports sending user invite links manually (via link copy and share). Invite delivery mode now explicitly has to be defined in the flow definition enabling a greater flexibility to the admin invite flows.
💥 Impact
Default admin user invite flow now only supports sending user invite links manually (via link copy and share). This changes the previous behaviour where it delivers the invite link via email when a email provider is configured. If not fallbacks to the manual invite mode.
🔄 Migration Guide
Update your admin invite flow definition to have email delivery related nodes. You can refer or re-use the following sample flow definitions.
User Onboarding flow with Manual Invite (Default):
Click to Expand
{ "name": "User Onboarding Flow", "handle": "default-user-onboarding", "flowType": "USER_ONBOARDING", "nodes": [ { "id": "start", "type": "START", "onSuccess": "permission_validator" }, { "id": "permission_validator", "type": "TASK_EXECUTION", "properties": { "requiredScopes": ["system"] }, "executor": { "name": "PermissionValidator" }, "onSuccess": "user_type_resolver" }, { "id": "user_type_resolver", "type": "TASK_EXECUTION", "executor": { "name": "UserTypeResolver" }, "onSuccess": "ou_selection", "onIncomplete": "prompt_usertype" }, { "id": "prompt_usertype", "type": "PROMPT", "meta": { "components": [ { "align": "center", "type": "TEXT", "id": "heading_usertype", "label": "{{ t(onboarding:forms.user_type.title) }}", "variant": "HEADING_1" }, { "type": "TEXT", "id": "subtitle_usertype", "label": "{{ t(onboarding:forms.user_type.subtitle) }}" }, { "type": "BLOCK", "id": "block_usertype", "components": [ { "type": "SELECT", "id": "usertype_input", "ref": "userType", "label": "{{ t(onboarding:forms.user_type.fields.user_type.label) }}", "placeholder": "{{ t(onboarding:forms.user_type.fields.user_type.placeholder) }}", "required": true, "options": [] }, { "type": "ACTION", "id": "action_usertype", "label": "{{ t(onboarding:forms.user_type.actions.continue.label) }}", "variant": "PRIMARY", "eventType": "SUBMIT" } ] } ] }, "prompts": [ { "inputs": [ { "ref": "usertype_input", "identifier": "userType", "type": "SELECT", "required": true } ], "action": { "ref": "action_usertype", "nextNode": "user_type_resolver" } } ] }, { "id": "ou_selection", "type": "TASK_EXECUTION", "properties": { "resolveFrom": "prompt" }, "executor": { "name": "OUResolverExecutor" }, "onSuccess": "prompt_email", "onIncomplete": "prompt_ou_selection" }, { "id": "prompt_ou_selection", "type": "PROMPT", "meta": { "components": [ { "align": "center", "type": "TEXT", "id": "heading_ou_selection", "label": "{{ t(onboarding:forms.ou_selection.title) }}", "variant": "HEADING_1" }, { "type": "TEXT", "id": "subtitle_ou_selection", "label": "{{ t(onboarding:forms.ou_selection.subtitle) }}" }, { "type": "BLOCK", "id": "block_ou_selection", "components": [ { "type": "OU_SELECT", "id": "ou_selection_input", "ref": "ouId", "label": "{{ t(onboarding:forms.ou_selection.fields.ou.label) }}", "required": true }, { "type": "ACTION", "id": "action_ou_selection", "label": "{{ t(onboarding:forms.ou_selection.actions.continue.label) }}", "variant": "PRIMARY", "eventType": "SUBMIT" } ] } ] }, "prompts": [ { "inputs": [ { "ref": "ou_selection_input", "identifier": "ouId", "type": "OU_SELECT", "required": true } ], "action": { "ref": "action_ou_selection", "nextNode": "ou_selection" } } ] }, { "id": "prompt_email", "type": "PROMPT", "meta": { "components": [ { "align": "center", "type": "TEXT", "id": "text_header_email", "label": "{{ t(onboarding:forms.email.title) }}", "variant": "HEADING_1" }, { "type": "BLOCK", "id": "block_email", "components": [ { "id": "input_prompt_email", "ref": "email", "type": "EMAIL_INPUT", "label": "{{ t(onboarding:forms.email.fields.email.label) }}", "required": true, "placeholder": "{{ t(onboarding:forms.email.fields.email.placeholder) }}" }, { "type": "ACTION", "id": "action_submit_email", "label": "{{ t(onboarding:forms.email.actions.next.label) }}", "variant": "PRIMARY", "eventType": "SUBMIT" } ] } ] }, "prompts": [ { "inputs": [ { "ref": "input_prompt_email", "identifier": "email", "type": "EMAIL_INPUT", "required": true } ], "action": { "ref": "action_submit_email", "nextNode": "check_email_uniqueness" } } ] }, { "id": "check_email_uniqueness", "type": "TASK_EXECUTION", "executor": { "name": "AttributeUniquenessValidator" }, "onSuccess": "invite_generate", "onIncomplete": "prompt_email" }, { "id": "invite_generate", "type": "TASK_EXECUTION", "executor": { "name": "InviteExecutor", "mode": "generate" }, "onSuccess": "invite_link_status" }, { "id": "invite_link_status", "type": "PROMPT", "meta": { "components": [ { "align": "center", "type": "TEXT", "id": "link_status_icon", "label": "✅", "variant": "HEADING_1" }, { "align": "center", "type": "TEXT", "id": "link_status_heading", "label": "{{ t(onboarding:forms.invite_link_status.title) }}", "variant": "HEADING_1" }, { "align": "center", "type": "TEXT", "id": "link_status_message", "label": "{{ t(onboarding:forms.invite_link_status.message) }}" }, { "type": "COPYABLE_TEXT", "id": "invite_link_copyable", "label": "{{ t(onboarding:forms.invite_link_status.link_label) }}", "source": "inviteLink" } ] }, "message": "The invite link is ready to share", "next": "invite_verify" }, { "id": "invite_verify", "type": "TASK_EXECUTION", "inputs": [ { "ref": "input_003", "identifier": "inviteToken", "type": "HIDDEN", "required": true } ], "executor": { "name": "InviteExecutor", "mode": "verify" }, "onSuccess": "prompt_user_details" }, { "id": "prompt_user_details", "type": "PROMPT", "meta": { "components": [ { "align": "center", "type": "TEXT", "id": "text_header_details", "label": "{{ t(onboarding:forms.user_details.title) }}", "variant": "HEADING_1" }, { "type": "BLOCK", "id": "block_user_details", "components": [ { "id": "input_prompt_username", "ref": "username", "type": "TEXT_INPUT", "label": "{{ t(onboarding:forms.user_details.fields.username.label) }}", "required": true, "placeholder": "{{ t(onboarding:forms.user_details.fields.username.placeholder) }}" }, { "id": "input_prompt_given_name", "ref": "given_name", "type": "TEXT_INPUT", "label": "{{ t(onboarding:forms.user_details.fields.first_name.label) }}", "required": false, "placeholder": "{{ t(onboarding:forms.user_details.fields.first_name.placeholder) }}" }, { "id": "input_prompt_family_name", "ref": "family_name", "type": "TEXT_INPUT", "label": "{{ t(onboarding:forms.user_details.fields.last_name.label) }}", "required": false, "placeholder": "{{ t(onboarding:forms.user_details.fields.last_name.placeholder) }}" }, { "type": "ACTION", "id": "action_submit_details", "label": "{{ t(onboarding:forms.user_details.actions.next.label) }}", "variant": "PRIMARY", "eventType": "SUBMIT" } ] } ] }, "prompts": [ { "inputs": [ { "ref": "input_prompt_username", "identifier": "username", "type": "TEXT_INPUT", "required": true }, { "ref": "input_prompt_given_name", "identifier": "given_name", "type": "TEXT_INPUT", "required": false }, { "ref": "input_prompt_family_name", "identifier": "family_name", "type": "TEXT_INPUT", "required": false } ], "action": { "ref": "action_submit_details", "nextNode": "check_user_details_uniqueness" } } ] }, { "id": "check_user_details_uniqueness", "type": "TASK_EXECUTION", "executor": { "name": "AttributeUniquenessValidator" }, "onSuccess": "prompt_credential", "onIncomplete": "prompt_user_details" }, { "id": "prompt_credential", "type": "PROMPT", "meta": { "components": [ { "align": "center", "type": "TEXT", "id": "text_header_pwd", "label": "{{ t(onboarding:forms.credential.title) }}", "variant": "HEADING_1" }, { "type": "BLOCK", "id": "block_pwd", "components": [ { "id": "input_prompt_password", "ref": "password", "type": "PASSWORD_INPUT", "label": "{{ t(onboarding:forms.credential.fields.password.label) }}", "required": true, "placeholder": "{{ t(onboarding:forms.credential.fields.password.placeholder) }}" }, { "type": "ACTION", "id": "action_submit_pwd", "label": "{{ t(onboarding:forms.credential.actions.submit.label) }}", "variant": "PRIMARY", "eventType": "SUBMIT" } ] } ] }, "prompts": [ { "inputs": [ { "ref": "input_prompt_password", "identifier": "password", "type": "PASSWORD_INPUT", "required": true } ], "action": { "ref": "action_submit_pwd", "nextNode": "provisioning" } } ] }, { "id": "provisioning", "type": "TASK_EXECUTION", "inputs": [ { "ref": "input_email", "identifier": "email", "type": "EMAIL_INPUT", "required": true }, { "ref": "input_username", "identifier": "username", "type": "TEXT_INPUT", "required": true }, { "ref": "input_password", "identifier": "password", "type": "PASSWORD_INPUT", "required": true }, { "ref": "input_given_name", "identifier": "given_name", "type": "TEXT_INPUT", "required": false }, { "ref": "input_family_name", "identifier": "family_name", "type": "TEXT_INPUT", "required": false } ], "executor": { "name": "ProvisioningExecutor" }, "onSuccess": "registration_complete" }, { "id": "registration_complete", "type": "PROMPT", "meta": { "components": [ { "align": "center", "type": "TEXT", "id": "registration_complete_icon", "label": "✅", "variant": "HEADING_1" }, { "align": "center", "type": "TEXT", "id": "registration_complete_heading", "label": "{{ t(invite:complete.title) }}", "variant": "HEADING_1" }, { "align": "center", "type": "TEXT", "id": "registration_complete_message", "label": "{{ t(invite:complete.description) }}" } ] }, "message": "Registration complete", "next": "end" }, { "id": "end", "type": "END" } ] }Screen.Recording.2026-04-09.at.3.31.39.PM.mov
User Onboarding flow with Email Invite:
Click to Expand
{ "name": "User Onboarding Flow", "handle": "default-user-onboarding", "flowType": "USER_ONBOARDING", "nodes": [ { "id": "start", "type": "START", "onSuccess": "permission_validator" }, { "id": "permission_validator", "type": "TASK_EXECUTION", "properties": { "requiredScopes": ["system"] }, "executor": { "name": "PermissionValidator" }, "onSuccess": "user_type_resolver" }, { "id": "user_type_resolver", "type": "TASK_EXECUTION", "executor": { "name": "UserTypeResolver" }, "onSuccess": "ou_selection", "onIncomplete": "prompt_usertype" }, { "id": "prompt_usertype", "type": "PROMPT", "meta": { "components": [ { "align": "center", "type": "TEXT", "id": "heading_usertype", "label": "{{ t(onboarding:forms.user_type.title) }}", "variant": "HEADING_1" }, { "type": "TEXT", "id": "subtitle_usertype", "label": "{{ t(onboarding:forms.user_type.subtitle) }}" }, { "type": "BLOCK", "id": "block_usertype", "components": [ { "type": "SELECT", "id": "usertype_input", "ref": "userType", "label": "{{ t(onboarding:forms.user_type.fields.user_type.label) }}", "placeholder": "{{ t(onboarding:forms.user_type.fields.user_type.placeholder) }}", "required": true, "options": [] }, { "type": "ACTION", "id": "action_usertype", "label": "{{ t(onboarding:forms.user_type.actions.continue.label) }}", "variant": "PRIMARY", "eventType": "SUBMIT" } ] } ] }, "prompts": [ { "inputs": [ { "ref": "usertype_input", "identifier": "userType", "type": "SELECT", "required": true } ], "action": { "ref": "action_usertype", "nextNode": "user_type_resolver" } } ] }, { "id": "ou_selection", "type": "TASK_EXECUTION", "properties": { "resolveFrom": "prompt" }, "executor": { "name": "OUResolverExecutor" }, "onSuccess": "prompt_email", "onIncomplete": "prompt_ou_selection" }, { "id": "prompt_ou_selection", "type": "PROMPT", "meta": { "components": [ { "align": "center", "type": "TEXT", "id": "heading_ou_selection", "label": "{{ t(onboarding:forms.ou_selection.title) }}", "variant": "HEADING_1" }, { "type": "TEXT", "id": "subtitle_ou_selection", "label": "{{ t(onboarding:forms.ou_selection.subtitle) }}" }, { "type": "BLOCK", "id": "block_ou_selection", "components": [ { "type": "OU_SELECT", "id": "ou_selection_input", "ref": "ouId", "label": "{{ t(onboarding:forms.ou_selection.fields.ou.label) }}", "required": true }, { "type": "ACTION", "id": "action_ou_selection", "label": "{{ t(onboarding:forms.ou_selection.actions.continue.label) }}", "variant": "PRIMARY", "eventType": "SUBMIT" } ] } ] }, "prompts": [ { "inputs": [ { "ref": "ou_selection_input", "identifier": "ouId", "type": "OU_SELECT", "required": true } ], "action": { "ref": "action_ou_selection", "nextNode": "ou_selection" } } ] }, { "id": "prompt_email", "type": "PROMPT", "meta": { "components": [ { "align": "center", "type": "TEXT", "id": "text_header_email", "label": "{{ t(onboarding:forms.email.title) }}", "variant": "HEADING_1" }, { "type": "BLOCK", "id": "block_email", "components": [ { "id": "input_prompt_email", "ref": "email", "type": "EMAIL_INPUT", "label": "{{ t(onboarding:forms.email.fields.email.label) }}", "required": true, "placeholder": "{{ t(onboarding:forms.email.fields.email.placeholder) }}" }, { "type": "ACTION", "id": "action_submit_email", "label": "{{ t(onboarding:forms.email.actions.next.label) }}", "variant": "PRIMARY", "eventType": "SUBMIT" } ] } ] }, "prompts": [ { "inputs": [ { "ref": "input_prompt_email", "identifier": "email", "type": "EMAIL_INPUT", "required": true } ], "action": { "ref": "action_submit_email", "nextNode": "check_email_uniqueness" } } ] }, { "id": "check_email_uniqueness", "type": "TASK_EXECUTION", "executor": { "name": "AttributeUniquenessValidator" }, "onSuccess": "invite_generate_email", "onIncomplete": "prompt_email" }, { "id": "invite_generate_email", "type": "TASK_EXECUTION", "executor": { "name": "InviteExecutor", "mode": "generate" }, "onSuccess": "send_invite_email" }, { "id": "send_invite_email", "type": "TASK_EXECUTION", "properties": { "emailTemplate": "USER_INVITE" }, "executor": { "name": "EmailExecutor", "mode": "send" }, "onSuccess": "invite_email_sent" }, { "id": "invite_email_sent", "type": "PROMPT", "meta": { "components": [ { "align": "center", "type": "TEXT", "id": "email_sent_icon", "label": "✅", "variant": "HEADING_1" }, { "align": "center", "type": "TEXT", "id": "email_sent_heading", "label": "{{ t(onboarding:forms.invite_email_sent.title) }}", "variant": "HEADING_1" }, { "align": "center", "type": "TEXT", "id": "email_sent_message", "label": "{{ t(onboarding:forms.invite_email_sent.message) }}" } ] }, "message": "An invitation email has been sent to the user", "next": "invite_verify" }, { "id": "invite_verify", "type": "TASK_EXECUTION", "inputs": [ { "ref": "input_003", "identifier": "inviteToken", "type": "HIDDEN", "required": true } ], "executor": { "name": "InviteExecutor", "mode": "verify" }, "onSuccess": "prompt_user_details" }, { "id": "prompt_user_details", "type": "PROMPT", "meta": { "components": [ { "align": "center", "type": "TEXT", "id": "text_header_details", "label": "{{ t(onboarding:forms.user_details.title) }}", "variant": "HEADING_1" }, { "type": "BLOCK", "id": "block_user_details", "components": [ { "id": "input_prompt_username", "ref": "username", "type": "TEXT_INPUT", "label": "{{ t(onboarding:forms.user_details.fields.username.label) }}", "required": true, "placeholder": "{{ t(onboarding:forms.user_details.fields.username.placeholder) }}" }, { "id": "input_prompt_given_name", "ref": "given_name", "type": "TEXT_INPUT", "label": "{{ t(onboarding:forms.user_details.fields.first_name.label) }}", "required": false, "placeholder": "{{ t(onboarding:forms.user_details.fields.first_name.placeholder) }}" }, { "id": "input_prompt_family_name", "ref": "family_name", "type": "TEXT_INPUT", "label": "{{ t(onboarding:forms.user_details.fields.last_name.label) }}", "required": false, "placeholder": "{{ t(onboarding:forms.user_details.fields.last_name.placeholder) }}" }, { "type": "ACTION", "id": "action_submit_details", "label": "{{ t(onboarding:forms.user_details.actions.next.label) }}", "variant": "PRIMARY", "eventType": "SUBMIT" } ] } ] }, "prompts": [ { "inputs": [ { "ref": "input_prompt_username", "identifier": "username", "type": "TEXT_INPUT", "required": true }, { "ref": "input_prompt_given_name", "identifier": "given_name", "type": "TEXT_INPUT", "required": false }, { "ref": "input_prompt_family_name", "identifier": "family_name", "type": "TEXT_INPUT", "required": false } ], "action": { "ref": "action_submit_details", "nextNode": "check_user_details_uniqueness" } } ] }, { "id": "check_user_details_uniqueness", "type": "TASK_EXECUTION", "executor": { "name": "AttributeUniquenessValidator" }, "onSuccess": "prompt_credential", "onIncomplete": "prompt_user_details" }, { "id": "prompt_credential", "type": "PROMPT", "meta": { "components": [ { "align": "center", "type": "TEXT", "id": "text_header_pwd", "label": "{{ t(onboarding:forms.credential.title) }}", "variant": "HEADING_1" }, { "type": "BLOCK", "id": "block_pwd", "components": [ { "id": "input_prompt_password", "ref": "password", "type": "PASSWORD_INPUT", "label": "{{ t(onboarding:forms.credential.fields.password.label) }}", "required": true, "placeholder": "{{ t(onboarding:forms.credential.fields.password.placeholder) }}" }, { "type": "ACTION", "id": "action_submit_pwd", "label": "{{ t(onboarding:forms.credential.actions.submit.label) }}", "variant": "PRIMARY", "eventType": "SUBMIT" } ] } ] }, "prompts": [ { "inputs": [ { "ref": "input_prompt_password", "identifier": "password", "type": "PASSWORD_INPUT", "required": true } ], "action": { "ref": "action_submit_pwd", "nextNode": "provisioning" } } ] }, { "id": "provisioning", "type": "TASK_EXECUTION", "inputs": [ { "ref": "input_email", "identifier": "email", "type": "EMAIL_INPUT", "required": true }, { "ref": "input_username", "identifier": "username", "type": "TEXT_INPUT", "required": true }, { "ref": "input_password", "identifier": "password", "type": "PASSWORD_INPUT", "required": true }, { "ref": "input_given_name", "identifier": "given_name", "type": "TEXT_INPUT", "required": false }, { "ref": "input_family_name", "identifier": "family_name", "type": "TEXT_INPUT", "required": false } ], "executor": { "name": "ProvisioningExecutor" }, "onSuccess": "registration_complete" }, { "id": "registration_complete", "type": "PROMPT", "meta": { "components": [ { "align": "center", "type": "TEXT", "id": "registration_complete_icon", "label": "✅", "variant": "HEADING_1" }, { "align": "center", "type": "TEXT", "id": "registration_complete_heading", "label": "{{ t(invite:complete.title) }}", "variant": "HEADING_1" }, { "align": "center", "type": "TEXT", "id": "registration_complete_message", "label": "{{ t(invite:complete.description) }}" } ] }, "message": "Registration complete", "next": "end" }, { "id": "end", "type": "END" } ] }Screen.Recording.2026-04-09.at.8.29.03.PM.mov
User Onboarding flow with Manual or Email Invite:
Click to Expand
{ "name": "User Onboarding Flow", "handle": "default-user-onboarding", "flowType": "USER_ONBOARDING", "nodes": [ { "id": "start", "type": "START", "onSuccess": "permission_validator" }, { "id": "permission_validator", "type": "TASK_EXECUTION", "properties": { "requiredScopes": ["system"] }, "executor": { "name": "PermissionValidator" }, "onSuccess": "user_type_resolver" }, { "id": "user_type_resolver", "type": "TASK_EXECUTION", "executor": { "name": "UserTypeResolver" }, "onSuccess": "ou_selection", "onIncomplete": "prompt_usertype" }, { "id": "prompt_usertype", "type": "PROMPT", "meta": { "components": [ { "align": "center", "type": "TEXT", "id": "heading_usertype", "label": "{{ t(onboarding:forms.user_type.title) }}", "variant": "HEADING_1" }, { "type": "TEXT", "id": "subtitle_usertype", "label": "{{ t(onboarding:forms.user_type.subtitle) }}" }, { "type": "BLOCK", "id": "block_usertype", "components": [ { "type": "SELECT", "id": "usertype_input", "ref": "userType", "label": "{{ t(onboarding:forms.user_type.fields.user_type.label) }}", "placeholder": "{{ t(onboarding:forms.user_type.fields.user_type.placeholder) }}", "required": true, "options": [] }, { "type": "ACTION", "id": "action_usertype", "label": "{{ t(onboarding:forms.user_type.actions.continue.label) }}", "variant": "PRIMARY", "eventType": "SUBMIT" } ] } ] }, "prompts": [ { "inputs": [ { "ref": "usertype_input", "identifier": "userType", "type": "SELECT", "required": true } ], "action": { "ref": "action_usertype", "nextNode": "user_type_resolver" } } ] }, { "id": "ou_selection", "type": "TASK_EXECUTION", "properties": { "resolveFrom": "prompt" }, "executor": { "name": "OUResolverExecutor" }, "onSuccess": "prompt_email", "onIncomplete": "prompt_ou_selection" }, { "id": "prompt_ou_selection", "type": "PROMPT", "meta": { "components": [ { "align": "center", "type": "TEXT", "id": "heading_ou_selection", "label": "{{ t(onboarding:forms.ou_selection.title) }}", "variant": "HEADING_1" }, { "type": "TEXT", "id": "subtitle_ou_selection", "label": "{{ t(onboarding:forms.ou_selection.subtitle) }}" }, { "type": "BLOCK", "id": "block_ou_selection", "components": [ { "type": "OU_SELECT", "id": "ou_selection_input", "ref": "ouId", "label": "{{ t(onboarding:forms.ou_selection.fields.ou.label) }}", "required": true }, { "type": "ACTION", "id": "action_ou_selection", "label": "{{ t(onboarding:forms.ou_selection.actions.continue.label) }}", "variant": "PRIMARY", "eventType": "SUBMIT" } ] } ] }, "prompts": [ { "inputs": [ { "ref": "ou_selection_input", "identifier": "ouId", "type": "OU_SELECT", "required": true } ], "action": { "ref": "action_ou_selection", "nextNode": "ou_selection" } } ] }, { "id": "prompt_email", "type": "PROMPT", "meta": { "components": [ { "align": "center", "type": "TEXT", "id": "text_header_email", "label": "{{ t(onboarding:forms.email.title) }}", "variant": "HEADING_1" }, { "type": "BLOCK", "id": "block_email", "components": [ { "id": "input_prompt_email", "ref": "email", "type": "EMAIL_INPUT", "label": "{{ t(onboarding:forms.email.fields.email.label) }}", "required": true, "placeholder": "{{ t(onboarding:forms.email.fields.email.placeholder) }}" }, { "type": "ACTION", "id": "action_submit_email", "label": "{{ t(onboarding:forms.email.actions.next.label) }}", "variant": "PRIMARY", "eventType": "SUBMIT" } ] } ] }, "prompts": [ { "inputs": [ { "ref": "input_prompt_email", "identifier": "email", "type": "EMAIL_INPUT", "required": true } ], "action": { "ref": "action_submit_email", "nextNode": "check_email_uniqueness" } } ] }, { "id": "check_email_uniqueness", "type": "TASK_EXECUTION", "executor": { "name": "AttributeUniquenessValidator" }, "onSuccess": "prompt_invite_mode", "onIncomplete": "prompt_email" }, { "id": "prompt_invite_mode", "type": "PROMPT", "meta": { "components": [ { "align": "center", "type": "TEXT", "id": "heading_invite_mode", "label": "{{ t(onboarding:forms.invite_mode.title) }}", "variant": "HEADING_1" }, { "align": "center", "type": "TEXT", "id": "subtitle_invite_mode", "label": "{{ t(onboarding:forms.invite_mode.subtitle) }}" }, { "type": "BLOCK", "id": "block_invite_mode", "components": [ { "type": "STACK", "id": "stack_invite_mode", "direction": "row", "gap": 2, "align": "center", "justify": "center", "components": [ { "type": "ACTION", "id": "action_send_email", "label": "{{ t(onboarding:forms.invite_mode.actions.email.label) }}", "variant": "SECONDARY", "eventType": "SUBMIT" }, { "type": "ACTION", "id": "action_share_link", "label": "{{ t(onboarding:forms.invite_mode.actions.link.label) }}", "variant": "SECONDARY", "eventType": "SUBMIT" } ] } ] } ] }, "prompts": [ { "action": { "ref": "action_send_email", "nextNode": "invite_generate_email", "type": "SUBMIT" } }, { "action": { "ref": "action_share_link", "nextNode": "invite_generate_link_share", "type": "SUBMIT" } } ] }, { "id": "invite_generate_email", "type": "TASK_EXECUTION", "executor": { "name": "InviteExecutor", "mode": "generate" }, "onSuccess": "send_invite_email" }, { "id": "send_invite_email", "type": "TASK_EXECUTION", "properties": { "emailTemplate": "USER_INVITE" }, "executor": { "name": "EmailExecutor", "mode": "send" }, "onSuccess": "invite_email_sent" }, { "id": "invite_email_sent", "type": "PROMPT", "meta": { "components": [ { "align": "center", "type": "TEXT", "id": "email_sent_icon", "label": "✅", "variant": "HEADING_1" }, { "align": "center", "type": "TEXT", "id": "email_sent_heading", "label": "{{ t(onboarding:forms.invite_email_sent.title) }}", "variant": "HEADING_1" }, { "align": "center", "type": "TEXT", "id": "email_sent_message", "label": "{{ t(onboarding:forms.invite_email_sent.message) }}" } ] }, "message": "An invitation email has been sent to the user", "next": "invite_verify" }, { "id": "invite_generate_link_share", "type": "TASK_EXECUTION", "executor": { "name": "InviteExecutor", "mode": "generate" }, "onSuccess": "invite_link_status" }, { "id": "invite_link_status", "type": "PROMPT", "meta": { "components": [ { "align": "center", "type": "TEXT", "id": "link_status_icon", "label": "✅", "variant": "HEADING_1" }, { "align": "center", "type": "TEXT", "id": "link_status_heading", "label": "{{ t(onboarding:forms.invite_link_status.title) }}", "variant": "HEADING_1" }, { "align": "center", "type": "TEXT", "id": "link_status_message", "label": "{{ t(onboarding:forms.invite_link_status.message) }}" }, { "type": "COPYABLE_TEXT", "id": "invite_link_copyable", "label": "{{ t(onboarding:forms.invite_link_status.link_label) }}", "source": "inviteLink" } ] }, "message": "The invite link is ready to share", "next": "invite_verify" }, { "id": "invite_verify", "type": "TASK_EXECUTION", "inputs": [ { "ref": "input_003", "identifier": "inviteToken", "type": "HIDDEN", "required": true } ], "executor": { "name": "InviteExecutor", "mode": "verify" }, "onSuccess": "prompt_user_details" }, { "id": "prompt_user_details", "type": "PROMPT", "meta": { "components": [ { "align": "center", "type": "TEXT", "id": "text_header_details", "label": "{{ t(onboarding:forms.user_details.title) }}", "variant": "HEADING_1" }, { "type": "BLOCK", "id": "block_user_details", "components": [ { "id": "input_prompt_username", "ref": "username", "type": "TEXT_INPUT", "label": "{{ t(onboarding:forms.user_details.fields.username.label) }}", "required": true, "placeholder": "{{ t(onboarding:forms.user_details.fields.username.placeholder) }}" }, { "id": "input_prompt_given_name", "ref": "given_name", "type": "TEXT_INPUT", "label": "{{ t(onboarding:forms.user_details.fields.first_name.label) }}", "required": false, "placeholder": "{{ t(onboarding:forms.user_details.fields.first_name.placeholder) }}" }, { "id": "input_prompt_family_name", "ref": "family_name", "type": "TEXT_INPUT", "label": "{{ t(onboarding:forms.user_details.fields.last_name.label) }}", "required": false, "placeholder": "{{ t(onboarding:forms.user_details.fields.last_name.placeholder) }}" }, { "type": "ACTION", "id": "action_submit_details", "label": "{{ t(onboarding:forms.user_details.actions.next.label) }}", "variant": "PRIMARY", "eventType": "SUBMIT" } ] } ] }, "prompts": [ { "inputs": [ { "ref": "input_prompt_username", "identifier": "username", "type": "TEXT_INPUT", "required": true }, { "ref": "input_prompt_given_name", "identifier": "given_name", "type": "TEXT_INPUT", "required": false }, { "ref": "input_prompt_family_name", "identifier": "family_name", "type": "TEXT_INPUT", "required": false } ], "action": { "ref": "action_submit_details", "nextNode": "check_user_details_uniqueness" } } ] }, { "id": "check_user_details_uniqueness", "type": "TASK_EXECUTION", "executor": { "name": "AttributeUniquenessValidator" }, "onSuccess": "prompt_credential", "onIncomplete": "prompt_user_details" }, { "id": "prompt_credential", "type": "PROMPT", "meta": { "components": [ { "align": "center", "type": "TEXT", "id": "text_header_pwd", "label": "{{ t(onboarding:forms.credential.title) }}", "variant": "HEADING_1" }, { "type": "BLOCK", "id": "block_pwd", "components": [ { "id": "input_prompt_password", "ref": "password", "type": "PASSWORD_INPUT", "label": "{{ t(onboarding:forms.credential.fields.password.label) }}", "required": true, "placeholder": "{{ t(onboarding:forms.credential.fields.password.placeholder) }}" }, { "type": "ACTION", "id": "action_submit_pwd", "label": "{{ t(onboarding:forms.credential.actions.submit.label) }}", "variant": "PRIMARY", "eventType": "SUBMIT" } ] } ] }, "prompts": [ { "inputs": [ { "ref": "input_prompt_password", "identifier": "password", "type": "PASSWORD_INPUT", "required": true } ], "action": { "ref": "action_submit_pwd", "nextNode": "provisioning" } } ] }, { "id": "provisioning", "type": "TASK_EXECUTION", "inputs": [ { "ref": "input_email", "identifier": "email", "type": "EMAIL_INPUT", "required": true }, { "ref": "input_username", "identifier": "username", "type": "TEXT_INPUT", "required": true }, { "ref": "input_password", "identifier": "password", "type": "PASSWORD_INPUT", "required": true }, { "ref": "input_given_name", "identifier": "given_name", "type": "TEXT_INPUT", "required": false }, { "ref": "input_family_name", "identifier": "family_name", "type": "TEXT_INPUT", "required": false } ], "executor": { "name": "ProvisioningExecutor" }, "onSuccess": "registration_complete" }, { "id": "registration_complete", "type": "PROMPT", "meta": { "components": [ { "align": "center", "type": "TEXT", "id": "registration_complete_icon", "label": "✅", "variant": "HEADING_1" }, { "align": "center", "type": "TEXT", "id": "registration_complete_heading", "label": "{{ t(invite:complete.title) }}", "variant": "HEADING_1" }, { "align": "center", "type": "TEXT", "id": "registration_complete_message", "label": "{{ t(invite:complete.description) }}" } ] }, "message": "Registration complete", "next": "end" }, { "id": "end", "type": "END" } ] }Screen.Recording.2026-04-09.at.12.56.52.PM.mov
Approach
Registration and Onboarding Flow Improvements:
registration_flow_basic_invite.jsonanduser_onboarding_flow.jsonto provide users with clear feedback after sending an invite email, sharing an invite link, and completing registration.Internationalization Updates:
en-US.jsonto cover new invite and registration status messages, as well as labels for the new invite modes and actions.Frontend Component and Logic Enhancements:
UserInvitePage.tsxto support new prompt types and actions, improved template literal resolution usinguseAsgardeo, and added a utility to check for actionable/input components in prompt trees.Minor Cleanups:
Related Issues
axiosdependency from Thunder #1466Related PRs
Checklist
breaking changelabel added.Security checks
Summary by CodeRabbit
New Features
Refactor
Tests