From 33e497bc6e1d797824b337611a2f820403b2be65 Mon Sep 17 00:00:00 2001 From: Ronald A Richardson Date: Mon, 20 Apr 2026 22:35:59 -0400 Subject: [PATCH 1/3] feat: add dedicated Invite User button and dialog to IAM Users UI ## Summary Adds a clean, focused 'Invite User' dialog to the IAM Users index page alongside the existing 'New User' button. This is the frontend companion to the core-api fix that restores cross-organisation invite support. ## Changes ### addon/components/modals/invite-user.hbs (new) Minimal invite dialog with three fields: - Email (required) - the only field strictly needed for an invite - Name (optional) - required by the backend only for brand-new users - Role (optional) - pre-assigns a role on acceptance ### addon/components/modals/invite-user.js (new) Glimmer component backing class. The modal is fully data-driven via the modalsManager options hash; no tracked state or actions are needed in the component itself. ### app/components/modals/invite-user.js (new) Re-export shim following the existing pattern in app/components/modals/. ### addon/controllers/users/index.js - Added 'Invite User' button (paper-plane icon) to actionButtons, placed between the refresh button and the existing 'New User' button. - Added inviteUser() @action that opens the new modal and POSTs to users/invite-user. Handles both response shapes: - { invited: true } -> existing user cross-org invite success message - { invited: false } -> new pending user creation success message ### translations/en-us.yaml - iam.users.index.invite-user: button label - iam.users.invite.*: full set of dialog strings (title, description, field placeholders, help text, success messages, validation warning) --- addon/components/modals/invite-user.hbs | 25 ++++++++++ addon/components/modals/invite-user.js | 10 ++++ addon/controllers/users/index.js | 66 +++++++++++++++++++++++++ app/components/modals/invite-user.js | 1 + translations/en-us.yaml | 15 ++++++ 5 files changed, 117 insertions(+) create mode 100644 addon/components/modals/invite-user.hbs create mode 100644 addon/components/modals/invite-user.js create mode 100644 app/components/modals/invite-user.js diff --git a/addon/components/modals/invite-user.hbs b/addon/components/modals/invite-user.hbs new file mode 100644 index 0000000..8cc932e --- /dev/null +++ b/addon/components/modals/invite-user.hbs @@ -0,0 +1,25 @@ + + + diff --git a/addon/components/modals/invite-user.js b/addon/components/modals/invite-user.js new file mode 100644 index 0000000..5ec0bf3 --- /dev/null +++ b/addon/components/modals/invite-user.js @@ -0,0 +1,10 @@ +import Component from '@glimmer/component'; + +/** + * Invite User modal component. + * + * This is a data-driven modal — all state lives in the options hash passed by + * the modalsManager. The component itself needs no tracked properties or + * actions; the confirm callback in UsersIndexController handles the submission. + */ +export default class ModalsInviteUserComponent extends Component {} diff --git a/addon/controllers/users/index.js b/addon/controllers/users/index.js index ed7525a..405621f 100644 --- a/addon/controllers/users/index.js +++ b/addon/controllers/users/index.js @@ -26,6 +26,13 @@ export default class UsersIndexController extends Controller { onClick: () => this.hostRouter.refresh(), helpText: this.intl.t('common.refresh'), }, + { + text: this.intl.t('iam.users.index.invite-user'), + type: 'default', + icon: 'paper-plane', + permission: 'iam create user', + onClick: this.inviteUser, + }, { text: this.intl.t('common.new'), type: 'primary', @@ -288,6 +295,65 @@ export default class UsersIndexController extends Controller { }); } + /** + * Opens the Invite User dialog. + * + * Sends only an email (and optional name / role) to POST users/invite-user. + * The backend handles both cases transparently: + * - Email already in the system → cross-organisation invite issued. + * - Brand-new email → pending user created and invite email sent. + * + * The response includes `invited: true` when an existing user was invited, + * allowing the frontend to display the appropriate success message. + * + * @void + */ + @action inviteUser() { + this.modalsManager.show('modals/invite-user', { + title: this.intl.t('iam.users.invite.title'), + acceptButtonText: this.intl.t('iam.users.invite.send-invitation'), + acceptButtonIcon: 'paper-plane', + email: '', + name: '', + role: null, + confirm: async (modal) => { + modal.startLoading(); + + const email = modal.getOption('email'); + const name = modal.getOption('name'); + const role = modal.getOption('role'); + + if (!email) { + this.notifications.warning(this.intl.t('iam.users.invite.email-required')); + return modal.stopLoading(); + } + + try { + const response = await this.fetch.post('users/invite-user', { + user: { + email, + name, + role_uuid: role ? role.id : undefined, + }, + }); + + const wasExistingUser = response && response.invited === true; + this.notifications.success( + wasExistingUser + ? this.intl.t('iam.users.invite.invitation-sent-existing') + : this.intl.t('iam.users.invite.invitation-sent-new') + ); + + modal.done(); + return this.hostRouter.refresh(); + } catch (error) { + this.notifications.serverError(error); + modal.stopLoading(); + } + }, + }); + } + /** * Toggles modal to create a new API key * diff --git a/app/components/modals/invite-user.js b/app/components/modals/invite-user.js new file mode 100644 index 0000000..4d1c6a9 --- /dev/null +++ b/app/components/modals/invite-user.js @@ -0,0 +1 @@ +export { default } from '@fleetbase/iam-engine/components/modals/invite-user'; diff --git a/translations/en-us.yaml b/translations/en-us.yaml index 96eed54..e1e37ed 100644 --- a/translations/en-us.yaml +++ b/translations/en-us.yaml @@ -156,6 +156,21 @@ iam: resend-invitation-to-join-organization: Resend invitation to join organization confirming-fleetbase-will-re-send-invitation-for-user-to-join-your-organization: By confirming Fleetbase will re-send the invitation for this user to join your organization. invitation-resent: Invitation resent. + invite-user: Invite User + invite: + title: Invite User + description: >- + Enter the email address of the person you want to invite. If they already + have a Fleetbase account they will receive an invitation to join your + organisation. If they are new, a pending account will be created and they + will be asked to set a password on acceptance. + email-placeholder: colleague@example.com + name-placeholder: Full name (required for new users) + name-help: Required only if the person does not yet have a Fleetbase account. + send-invitation: Send Invitation + email-required: Please enter an email address. + invitation-sent-existing: Invitation sent. The user will receive an email to join your organisation. + invitation-sent-new: Invitation sent. A pending account has been created and the user will be asked to set a password. application: access-management: Access Management home: From 07dd3db8c233d78ffd4d7caf07cab6cc5282e6ce Mon Sep 17 00:00:00 2001 From: "Ronald A. Richardson" Date: Tue, 21 Apr 2026 15:04:15 +0800 Subject: [PATCH 2/3] tweaked invite user modal, ran prettier, upgraded dependencies --- addon/components/modals/invite-user.hbs | 8 +- addon/controllers/users/index.js | 11 +- package.json | 4 +- pnpm-lock.yaml | 134 ++++++++++++++++++++++-- translations/en-us.yaml | 1 + 5 files changed, 135 insertions(+), 23 deletions(-) diff --git a/addon/components/modals/invite-user.hbs b/addon/components/modals/invite-user.hbs index 8cc932e..95dfd14 100644 --- a/addon/components/modals/invite-user.hbs +++ b/addon/components/modals/invite-user.hbs @@ -1,9 +1,9 @@