Skip to content

Add flow native render support for invite flows#2276

Merged
ThaminduDilshan merged 1 commit intoasgardeo:mainfrom
ThaminduDilshan:thamindu-prompts
Apr 9, 2026
Merged

Add flow native render support for invite flows#2276
ThaminduDilshan merged 1 commit intoasgardeo:mainfrom
ThaminduDilshan:thamindu-prompts

Conversation

@ThaminduDilshan
Copy link
Copy Markdown
Contributor

@ThaminduDilshan ThaminduDilshan commented Apr 9, 2026

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.


⚠️ Breaking Changes

🔧 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:

  • Added new prompt steps to both registration_flow_basic_invite.json and user_onboarding_flow.json to provide users with clear feedback after sending an invite email, sharing an invite link, and completing registration.

Internationalization Updates:

  • Expanded the English translations in en-US.json to cover new invite and registration status messages, as well as labels for the new invite modes and actions.

Frontend Component and Logic Enhancements:

  • Updated UserInvitePage.tsx to support new prompt types and actions, improved template literal resolution using useAsgardeo, and added a utility to check for actionable/input components in prompt trees.
  • Refactored form handling to resolve prompt labels and placeholders using the new template resolver, and removed unused props related to invite link copying.

Minor Cleanups:

  • Minor formatting and property list adjustments for consistency in flow definitions.

Related Issues

Related PRs

Checklist

  • Followed the contribution guidelines.
  • Manual test round performed and verified.
  • Documentation provided. (Add links if there are any)
    • Ran Vale and fixed all errors and warnings
  • Tests provided. (Add links if there are any)
    • Unit Tests
    • Integration Tests
  • Breaking changes. (Fill if applicable)
    • Breaking changes section filled.
    • breaking change label added.

Security checks

  • Followed secure coding standards in WSO2 Secure Coding Guidelines
  • Confirmed that this PR doesn't commit any keys, passwords, tokens, usernames, or other secrets.

Summary by CodeRabbit

  • New Features

    • Copyable-text component for sharing values with copy / “Copied!” feedback
    • Localized “Invitation Sent”, “Invite Link Ready”, and registration-complete prompts in onboarding/signup flows
  • Refactor

    • Streamlined invite/onboarding flows to show display-only prompts and unified submit/action handling
    • Simplified sign-up and accept-invite completion UI and layout behavior
  • Tests

    • Added/updated tests covering copyable text, stack actions, alignment, and prompt/display-only states

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 9, 2026

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Backend 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

Cohort / File(s) Summary
Backend Flow Definitions
backend/cmd/server/bootstrap/flows/registration/registration_flow_basic_invite.json, backend/cmd/server/bootstrap/flows/user_onboarding/user_onboarding_flow.json
Added PROMPT nodes (registration_email_sent, invite_link_status, registration_complete); rerouted task onSuccess transitions to show prompts before proceeding to verify/end.
Backend i18n
backend/cmd/server/bootstrap/i18n/en-US.json
Added onboarding/invite and signup email-sent translation keys used by new PROMPT nodes and copyable-text UI.
Frontend — Invite UI / Flow Bridge
frontend/apps/thunder-console/src/features/users/pages/UserInvitePage.tsx
Removed local invite/email-sent plumbing and copy handlers; switched template resolver hook; detect display-only prompts; added COPYABLE_TEXT handling; refactored BLOCK/STACK submit/action selection and per-action loading.
Frontend — Accept/SignUp Components
frontend/apps/thunder-gate/src/components/AcceptInvite/AcceptInviteBox.tsx, frontend/apps/thunder-gate/src/components/SignUp/SignUpBox.tsx
Removed meta-derived app link and emailSent state/UI; always render sign-in link; rely on flow PROMPTs for completion UI.
Frontend — Flow Renderer & Adapters
frontend/packages/thunder-design/src/components/flow/FlowComponentRenderer.tsx, frontend/packages/thunder-design/src/components/flow/adapters/CopyableTextAdapter.tsx, frontend/packages/thunder-design/src/components/flow/adapters/TextAdapter.tsx
Added COPYABLE_TEXT branch and new CopyableTextAdapter (clipboard API + textarea fallback, copied state); TextAdapter now respects component.align.
Frontend — Types & Exports
frontend/packages/thunder-design/src/models/flow.ts, frontend/packages/thunder-design/src/index.ts
Added optional source?: string to FlowComponent and re-exported CopyableTextAdapter.
Frontend — Tests
frontend/apps/thunder-console/.../__tests__/UserInvitePage.test.tsx, frontend/apps/thunder-gate/.../__tests__/AcceptInviteBox.test.tsx, frontend/apps/thunder-gate/.../__tests__/SignUpBox.test.tsx, frontend/packages/thunder-design/src/components/flow/__tests__/FlowComponentRenderer.test.tsx, frontend/packages/thunder-design/src/components/flow/adapters/__tests__/CopyableTextAdapter.test.tsx, frontend/packages/thunder-design/src/components/flow/adapters/__tests__/TextAdapter.test.tsx
Updated/added tests: removed invite-email/link state assertions; added coverage for COPYABLE_TEXT, copy behavior (clipboard + fallback), STACK/BLOCK action behavior, and text alignment precedence.
Build / Config / i18n
frontend/pnpm-workspace.yaml, frontend/apps/thunder-console/src/hocs/withI18n.tsx
Bumped @asgardeo/react pin to 0.21.3; set keySeparator: false in i18next init.

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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~55 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • jeradrutnam
  • brionmario
  • DonOmalVindula
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title directly summarizes the main change: adding flow native render support for invite flows, which is the core purpose of this PR.
Description check ✅ Passed The PR description is comprehensive and well-structured, covering purpose, breaking changes with impact and migration guide, approach, related issues/PRs, and security checks.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
frontend/apps/thunder-console/src/features/users/pages/UserInvitePage.tsx (1)

147-150: Duplicated resolve wrapper function.

The resolve wrapper is defined identically in both InviteUserStepContent (lines 147-148) and InviteUserFlowBridge (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.

AcceptInviteBox does not branch on isComplete anymore, so this still passes with isComplete: false. Rename it to reflect plain component rendering, or make the mock change behavior when isComplete is 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

📥 Commits

Reviewing files that changed from the base of the PR and between 0d7360b and d57aec4.

⛔ Files ignored due to path filters (1)
  • frontend/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (19)
  • backend/cmd/server/bootstrap/flows/registration/registration_flow_basic_invite.json
  • backend/cmd/server/bootstrap/flows/user_onboarding/user_onboarding_flow.json
  • backend/cmd/server/bootstrap/i18n/en-US.json
  • frontend/apps/thunder-console/src/features/users/pages/UserInvitePage.tsx
  • frontend/apps/thunder-console/src/features/users/pages/__tests__/UserInvitePage.test.tsx
  • frontend/apps/thunder-console/src/hocs/withI18n.tsx
  • frontend/apps/thunder-gate/src/components/AcceptInvite/AcceptInviteBox.tsx
  • frontend/apps/thunder-gate/src/components/AcceptInvite/__tests__/AcceptInviteBox.test.tsx
  • 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/FlowComponentRenderer.tsx
  • frontend/packages/thunder-design/src/components/flow/__tests__/FlowComponentRenderer.test.tsx
  • frontend/packages/thunder-design/src/components/flow/adapters/CopyableTextAdapter.tsx
  • frontend/packages/thunder-design/src/components/flow/adapters/TextAdapter.tsx
  • frontend/packages/thunder-design/src/components/flow/adapters/__tests__/CopyableTextAdapter.test.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/models/flow.ts
  • frontend/pnpm-workspace.yaml

@ThaminduDilshan ThaminduDilshan force-pushed the thamindu-prompts branch 2 times, most recently from aba7582 to cda3335 Compare April 9, 2026 10:19
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 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_TEXT is 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

📥 Commits

Reviewing files that changed from the base of the PR and between d57aec4 and cda3335.

⛔ Files ignored due to path filters (1)
  • frontend/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (19)
  • backend/cmd/server/bootstrap/flows/registration/registration_flow_basic_invite.json
  • backend/cmd/server/bootstrap/flows/user_onboarding/user_onboarding_flow.json
  • backend/cmd/server/bootstrap/i18n/en-US.json
  • frontend/apps/thunder-console/src/features/users/pages/UserInvitePage.tsx
  • frontend/apps/thunder-console/src/features/users/pages/__tests__/UserInvitePage.test.tsx
  • frontend/apps/thunder-console/src/hocs/withI18n.tsx
  • frontend/apps/thunder-gate/src/components/AcceptInvite/AcceptInviteBox.tsx
  • frontend/apps/thunder-gate/src/components/AcceptInvite/__tests__/AcceptInviteBox.test.tsx
  • 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/FlowComponentRenderer.tsx
  • frontend/packages/thunder-design/src/components/flow/__tests__/FlowComponentRenderer.test.tsx
  • frontend/packages/thunder-design/src/components/flow/adapters/CopyableTextAdapter.tsx
  • frontend/packages/thunder-design/src/components/flow/adapters/TextAdapter.tsx
  • frontend/packages/thunder-design/src/components/flow/adapters/__tests__/CopyableTextAdapter.test.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/models/flow.ts
  • frontend/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

@ThaminduDilshan ThaminduDilshan force-pushed the thamindu-prompts branch 2 times, most recently from e33ba9d to c8c248b Compare April 9, 2026 10:47
@codecov
Copy link
Copy Markdown

codecov bot commented Apr 9, 2026

Codecov Report

❌ Patch coverage is 97.56098% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 89.49%. Comparing base (226b5e2) to head (e30c503).
⚠️ Report is 4 commits behind head on main.

Files with missing lines Patch % Lines
...onsole/src/features/users/pages/UserInvitePage.tsx 97.43% 1 Missing ⚠️
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     
Flag Coverage Δ
backend-integration-postgres 53.23% <ø> (-0.04%) ⬇️
backend-integration-sqlite 53.28% <ø> (+0.05%) ⬆️
backend-unit 83.97% <ø> (-0.01%) ⬇️
frontend-apps-console-unit 90.13% <97.43%> (-0.02%) ⬇️
frontend-apps-gate-unit 97.35% <100.00%> (-0.14%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@ThaminduDilshan ThaminduDilshan force-pushed the thamindu-prompts branch 4 times, most recently from 8612bea to c5b7c81 Compare April 9, 2026 11:44
@ThaminduDilshan ThaminduDilshan disabled auto-merge April 9, 2026 13:05
thiva-k
thiva-k previously approved these changes Apr 9, 2026
lng: I18nDefaultConstants.FALLBACK_LANGUAGE,
fallbackLng: I18nDefaultConstants.FALLBACK_LANGUAGE,
defaultNS: 'common',
keySeparator: false,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why do we need this change

Copy link
Copy Markdown
Contributor Author

@ThaminduDilshan ThaminduDilshan Apr 9, 2026

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@coderabbitai can you check whether this is correct?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@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:

  1. Flat keys break under default config — i18next's dot-as-separator default causes flat dotted keys like onboarding.forms.invite_mode.title or actions.close to be misinterpreted as nested paths, resulting in lookup failure.
  2. 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.
  3. keySeparator: false is 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.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between cda3335 and b8527a0.

⛔ Files ignored due to path filters (1)
  • frontend/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (19)
  • backend/cmd/server/bootstrap/flows/registration/registration_flow_basic_invite.json
  • backend/cmd/server/bootstrap/flows/user_onboarding/user_onboarding_flow.json
  • backend/cmd/server/bootstrap/i18n/en-US.json
  • frontend/apps/thunder-console/src/features/users/pages/UserInvitePage.tsx
  • frontend/apps/thunder-console/src/features/users/pages/__tests__/UserInvitePage.test.tsx
  • frontend/apps/thunder-console/src/hocs/withI18n.tsx
  • frontend/apps/thunder-gate/src/components/AcceptInvite/AcceptInviteBox.tsx
  • frontend/apps/thunder-gate/src/components/AcceptInvite/__tests__/AcceptInviteBox.test.tsx
  • 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/FlowComponentRenderer.tsx
  • frontend/packages/thunder-design/src/components/flow/__tests__/FlowComponentRenderer.test.tsx
  • frontend/packages/thunder-design/src/components/flow/adapters/CopyableTextAdapter.tsx
  • frontend/packages/thunder-design/src/components/flow/adapters/TextAdapter.tsx
  • frontend/packages/thunder-design/src/components/flow/adapters/__tests__/CopyableTextAdapter.test.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/models/flow.ts
  • frontend/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

@ThaminduDilshan ThaminduDilshan added the breaking change The feature/ improvement will alter the existing behaviour label Apr 9, 2026
KaveeshaPiumini
KaveeshaPiumini previously approved these changes Apr 9, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

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.tsx intentionally 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: Duplicated resolve callback pattern.

The same resolve callback wrapping rawResolve is defined identically in both InviteUserStepContent (lines 147-148) and InviteUserFlowBridge (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

📥 Commits

Reviewing files that changed from the base of the PR and between b8527a0 and 1cea396.

⛔ Files ignored due to path filters (1)
  • frontend/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (19)
  • backend/cmd/server/bootstrap/flows/registration/registration_flow_basic_invite.json
  • backend/cmd/server/bootstrap/flows/user_onboarding/user_onboarding_flow.json
  • backend/cmd/server/bootstrap/i18n/en-US.json
  • frontend/apps/thunder-console/src/features/users/pages/UserInvitePage.tsx
  • frontend/apps/thunder-console/src/features/users/pages/__tests__/UserInvitePage.test.tsx
  • frontend/apps/thunder-console/src/hocs/withI18n.tsx
  • frontend/apps/thunder-gate/src/components/AcceptInvite/AcceptInviteBox.tsx
  • frontend/apps/thunder-gate/src/components/AcceptInvite/__tests__/AcceptInviteBox.test.tsx
  • 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/FlowComponentRenderer.tsx
  • frontend/packages/thunder-design/src/components/flow/__tests__/FlowComponentRenderer.test.tsx
  • frontend/packages/thunder-design/src/components/flow/adapters/CopyableTextAdapter.tsx
  • frontend/packages/thunder-design/src/components/flow/adapters/TextAdapter.tsx
  • frontend/packages/thunder-design/src/components/flow/adapters/__tests__/CopyableTextAdapter.test.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/models/flow.ts
  • frontend/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

@ThaminduDilshan ThaminduDilshan added this pull request to the merge queue Apr 9, 2026
Merged via the queue into asgardeo:main with commit 5b14a77 Apr 9, 2026
28 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking change The feature/ improvement will alter the existing behaviour dependencies-approved Type/Improvement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improve admin user invite flow

3 participants