@@ -153,14 +134,14 @@ const ModalContent = ({ user, appInfo }: ModalData) => {
copyTooltip={m.misc_clipboard_copy()}
label={m.modal_add_user_enrollment_form_label_instance_url()}
data-testid="activation-url-field"
- text={enrollmentData.enrollment_url}
+ text={enrollmentResponse.enrollment_url}
/>
{appInfo.smtp_enabled && (
<>
diff --git a/web/src/shared/hooks/modalControls/types.ts b/web/src/shared/hooks/modalControls/types.ts
index 3c4f30ad10..bbd1f4f346 100644
--- a/web/src/shared/hooks/modalControls/types.ts
+++ b/web/src/shared/hooks/modalControls/types.ts
@@ -59,6 +59,7 @@ export interface OpenEnrollmentTokenModal {
appInfo: {
smtp_enabled: boolean;
};
+ enrollmentResponse: StartEnrollmentResponse;
}
export interface OpenCEWebhookModal {
From 70382c15d000a03e9bc07b4c748791bb4521623d Mon Sep 17 00:00:00 2001
From: jakub-tldr <78603704+jakub-tldr@users.noreply.github.com>
Date: Thu, 29 Jan 2026 14:53:55 +0100
Subject: [PATCH 09/13] fix submodule
---
web/src/shared/defguard-ui | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/web/src/shared/defguard-ui b/web/src/shared/defguard-ui
index b9fad0ee9e..dc3ada9c02 160000
--- a/web/src/shared/defguard-ui
+++ b/web/src/shared/defguard-ui
@@ -1 +1 @@
-Subproject commit b9fad0ee9ea351641db659fb22988c6d8d0d5089
+Subproject commit dc3ada9c0254082fbcba925c8f7b11ff914b5865
From 9bb31d82c69946582805996000be64bbe6f2e40f Mon Sep 17 00:00:00 2001
From: jakub-tldr <78603704+jakub-tldr@users.noreply.github.com>
Date: Thu, 29 Jan 2026 15:25:19 +0100
Subject: [PATCH 10/13] catch error, display snackbar
---
.../pages/UsersOverviewPage/UsersTable.tsx | 24 ++++++++++++-------
1 file changed, 15 insertions(+), 9 deletions(-)
diff --git a/web/src/pages/UsersOverviewPage/UsersTable.tsx b/web/src/pages/UsersOverviewPage/UsersTable.tsx
index 5a2a2061a8..f7c1cf9e41 100644
--- a/web/src/pages/UsersOverviewPage/UsersTable.tsx
+++ b/web/src/pages/UsersOverviewPage/UsersTable.tsx
@@ -35,6 +35,7 @@ import { TableCell } from '../../shared/defguard-ui/components/table/TableCell/T
import { TableFlexCell } from '../../shared/defguard-ui/components/table/TableFlexCell/TableFlexCell';
import { TableRowContainer } from '../../shared/defguard-ui/components/table/TableRowContainer/TableRowContainer';
import { TableTop } from '../../shared/defguard-ui/components/table/TableTop/TableTop';
+import { Snackbar } from '../../shared/defguard-ui/providers/snackbar/snackbar';
import { isPresent } from '../../shared/defguard-ui/utils/isPresent';
import { openModal } from '../../shared/hooks/modalControls/modalsSubjects';
import { ModalName } from '../../shared/hooks/modalControls/modalTypes';
@@ -244,18 +245,23 @@ export const UsersTable = ({ users }: Props) => {
{
text: m.users_row_menu_initiate_self_enrollment(),
icon: IconKind.AddUser,
- onClick: async () => {
- const enrollmentResponse = (
- await api.user.startEnrollment({
+ onClick: () => {
+ api.user
+ .startEnrollment({
send_enrollment_notification: false,
username: rowData.username,
})
- ).data;
- openModal(ModalName.EnrollmentToken, {
- user: rowData,
- appInfo,
- enrollmentResponse,
- });
+ .then((response) => {
+ openModal(ModalName.EnrollmentToken, {
+ user: rowData,
+ appInfo,
+ enrollmentResponse: response.data,
+ });
+ })
+ .catch((error) => {
+ Snackbar.error('Failed to initiate enrollment');
+ console.error(error);
+ });
},
},
],
From a534272227a245468b4a5e0f819f85a2b20bbda3 Mon Sep 17 00:00:00 2001
From: jakub-tldr <78603704+jakub-tldr@users.noreply.github.com>
Date: Thu, 29 Jan 2026 15:44:18 +0100
Subject: [PATCH 11/13] add snackbars, try/catch blocks
---
web/messages/en/common.json | 4 +-
.../pages/UsersOverviewPage/UsersTable.tsx | 251 +++++++++---------
.../modals/AddUserModal/AddUserModal.tsx | 20 +-
.../EnrollmentTokenModal.tsx | 6 +
4 files changed, 147 insertions(+), 134 deletions(-)
diff --git a/web/messages/en/common.json b/web/messages/en/common.json
index 95fc8c9d37..6c50778f35 100644
--- a/web/messages/en/common.json
+++ b/web/messages/en/common.json
@@ -50,5 +50,7 @@
"search_empty_common_title": "Nothing is found.",
"search_empty_common_subtitle": "Try adjusting your filters or search terms.",
"search_users": "Find users",
- "select_users_all": "All users"
+ "select_users_all": "All users",
+ "sucessfull_enrollment_email": "Enrollment email sent successfully",
+ "failed_to_start_enrollment": "Failed to start enrollment"
}
diff --git a/web/src/pages/UsersOverviewPage/UsersTable.tsx b/web/src/pages/UsersOverviewPage/UsersTable.tsx
index f7c1cf9e41..57c15c65d3 100644
--- a/web/src/pages/UsersOverviewPage/UsersTable.tsx
+++ b/web/src/pages/UsersOverviewPage/UsersTable.tsx
@@ -28,6 +28,7 @@ import { EmptyState } from '../../shared/defguard-ui/components/EmptyState/Empty
import { EmptyStateFlexible } from '../../shared/defguard-ui/components/EmptyStateFlexible/EmptyStateFlexible';
import { Icon, IconKind } from '../../shared/defguard-ui/components/Icon';
import { IconButtonMenu } from '../../shared/defguard-ui/components/IconButtonMenu/IconButtonMenu';
+import type { MenuItemsGroup } from '../../shared/defguard-ui/components/Menu/types';
import { Search } from '../../shared/defguard-ui/components/Search/Search';
import { tableEditColumnSize } from '../../shared/defguard-ui/components/table/consts';
import { TableBody } from '../../shared/defguard-ui/components/table/TableBody/TableBody';
@@ -239,139 +240,137 @@ export const UsersTable = ({ users }: Props) => {
cell: (info) => {
const rowData = info.row.original;
- const enrollmentSection = !rowData.enrolled
- ? {
- items: [
- {
- text: m.users_row_menu_initiate_self_enrollment(),
- icon: IconKind.AddUser,
- onClick: () => {
- api.user
- .startEnrollment({
- send_enrollment_notification: false,
- username: rowData.username,
- })
- .then((response) => {
- openModal(ModalName.EnrollmentToken, {
- user: rowData,
- appInfo,
- enrollmentResponse: response.data,
- });
- })
- .catch((error) => {
- Snackbar.error('Failed to initiate enrollment');
- console.error(error);
- });
- },
+ const menuItems: MenuItemsGroup[] = [
+ {
+ items: [
+ {
+ text: m.users_row_menu_edit(),
+ icon: 'edit',
+ onClick: () => {
+ openModal(ModalName.EditUserModal, {
+ user: rowData,
+ reservedEmails,
+ reservedUsernames,
+ });
},
- ],
- }
- : null;
-
- return (
-
- {
- openModal(ModalName.EditUserModal, {
- user: rowData,
- reservedEmails,
- reservedUsernames,
- });
- },
- },
- {
- text: m.users_row_menu_change_password(),
- icon: 'lock-open',
- testId: 'change-password',
- onClick: () => {
- openModal(ModalName.ChangePassword, {
- adminForm: true,
- user: rowData,
- });
- },
- },
- {
- text: m.users_row_menu_go_profile(),
- icon: 'profile',
- onClick: () => {
- navigate({
- to: '/user/$username',
- params: {
- username: rowData.username,
- },
- });
- },
- },
- {
- text: m.users_row_menu_edit_groups(),
- icon: 'add-group',
- testId: 'edit-groups',
- onClick: () => {
- useSelectionModal.setState({
- isOpen: true,
- options: groupsOptions,
- title: m.modal_edit_user_groups_title(),
- selected: new Set(rowData.groups),
- onSubmit: (selected) => {
- handleEditGroups(rowData, selected as string[]);
- },
- });
- },
- },
- ],
+ },
+ {
+ text: m.users_row_menu_change_password(),
+ icon: 'lock-open',
+ testId: 'change-password',
+ onClick: () => {
+ openModal(ModalName.ChangePassword, {
+ adminForm: true,
+ user: rowData,
+ });
},
- ...(enrollmentSection ? [enrollmentSection] : []),
- {
- items: [
- {
- text: m.users_row_menu_add_auth(),
- icon: 'key',
- onClick: () => {
- openModal(ModalName.AddAuthKey, {
- username: rowData.username,
- });
- },
+ },
+ {
+ text: m.users_row_menu_go_profile(),
+ icon: 'profile',
+ onClick: () => {
+ navigate({
+ to: '/user/$username',
+ params: {
+ username: rowData.username,
},
- ],
+ });
},
- {
- items: [
- {
- text: rowData.is_active
- ? m.users_row_menu_disable()
- : m.users_row_menu_enable(),
- icon: rowData.is_active ? 'disabled' : 'check-circle',
- testId: 'change-account-status',
- onClick: () => {
- changeAccountActiveState({
- active: !rowData.is_active,
- username: rowData.username,
- });
- },
+ },
+ {
+ text: m.users_row_menu_edit_groups(),
+ icon: 'add-group',
+ testId: 'edit-groups',
+ onClick: () => {
+ useSelectionModal.setState({
+ isOpen: true,
+ options: groupsOptions,
+ title: m.modal_edit_user_groups_title(),
+ selected: new Set(rowData.groups),
+ onSubmit: (selected) => {
+ handleEditGroups(rowData, selected as string[]);
},
- ],
+ });
},
- {
- items: [
- {
- text: m.users_row_menu_delete(),
- icon: 'delete',
- variant: 'danger',
- onClick: () => {
- deleteUser(rowData.username);
- },
- },
- ],
+ },
+ ],
+ },
+ {
+ items: [
+ {
+ text: m.users_row_menu_add_auth(),
+ icon: 'key',
+ onClick: () => {
+ openModal(ModalName.AddAuthKey, {
+ username: rowData.username,
+ });
},
- ]}
- />
+ },
+ ],
+ },
+ {
+ items: [
+ {
+ text: rowData.is_active
+ ? m.users_row_menu_disable()
+ : m.users_row_menu_enable(),
+ icon: rowData.is_active ? 'disabled' : 'check-circle',
+ testId: 'change-account-status',
+ onClick: () => {
+ changeAccountActiveState({
+ active: !rowData.is_active,
+ username: rowData.username,
+ });
+ },
+ },
+ ],
+ },
+ {
+ items: [
+ {
+ text: m.users_row_menu_delete(),
+ icon: 'delete',
+ variant: 'danger',
+ onClick: () => {
+ deleteUser(rowData.username);
+ },
+ },
+ ],
+ },
+ ];
+
+ if (!rowData.enrolled) {
+ menuItems.splice(1, 0, {
+ items: [
+ {
+ text: m.users_row_menu_initiate_self_enrollment(),
+ icon: IconKind.AddUser,
+ onClick: () => {
+ api.user
+ .startEnrollment({
+ send_enrollment_notification: false,
+ username: rowData.username,
+ })
+ .then((response) => {
+ openModal(ModalName.EnrollmentToken, {
+ user: rowData,
+ appInfo,
+ enrollmentResponse: response.data,
+ });
+ })
+ .catch((error) => {
+ Snackbar.error('Failed to initiate enrollment');
+ console.error(error);
+ });
+ },
+ },
+ ],
+ });
+ }
+
+ return (
+
+
);
},
diff --git a/web/src/pages/UsersOverviewPage/modals/AddUserModal/AddUserModal.tsx b/web/src/pages/UsersOverviewPage/modals/AddUserModal/AddUserModal.tsx
index 9e82005ed1..95b28196dc 100644
--- a/web/src/pages/UsersOverviewPage/modals/AddUserModal/AddUserModal.tsx
+++ b/web/src/pages/UsersOverviewPage/modals/AddUserModal/AddUserModal.tsx
@@ -358,13 +358,19 @@ const AddUserModalForm = () => {
data: { groups },
} = await api.group.getGroups();
if (enrollmentEnabled) {
- const enrollmentResponse = (
- await api.user.startEnrollment({
- send_enrollment_notification: false,
- username: created.username,
- })
- ).data;
- useAddUserModal.setState({ enrollResponse: enrollmentResponse, user: created });
+ try {
+ const enrollmentResponse = (
+ await api.user.startEnrollment({
+ send_enrollment_notification: false,
+ username: created.username,
+ })
+ ).data;
+ useAddUserModal.setState({ enrollResponse: enrollmentResponse, user: created });
+ } catch (error) {
+ console.error(m.failed_to_start_enrollment(), error);
+ useAddUserModal.setState({ isOpen: false });
+ return;
+ }
}
if (assignToGroups) {
useAddUserModal.setState({
diff --git a/web/src/pages/UsersOverviewPage/modals/EnrollmentTokenModal/EnrollmentTokenModal.tsx b/web/src/pages/UsersOverviewPage/modals/EnrollmentTokenModal/EnrollmentTokenModal.tsx
index 31a6f29349..0af6e8844e 100644
--- a/web/src/pages/UsersOverviewPage/modals/EnrollmentTokenModal/EnrollmentTokenModal.tsx
+++ b/web/src/pages/UsersOverviewPage/modals/EnrollmentTokenModal/EnrollmentTokenModal.tsx
@@ -9,6 +9,7 @@ import { CopyField } from '../../../../shared/defguard-ui/components/CopyField/C
import { Modal } from '../../../../shared/defguard-ui/components/Modal/Modal';
import { ModalControls } from '../../../../shared/defguard-ui/components/ModalControls/ModalControls';
import { SizedBox } from '../../../../shared/defguard-ui/components/SizedBox/SizedBox';
+import { Snackbar } from '../../../../shared/defguard-ui/providers/snackbar/snackbar';
import {
TextStyle,
ThemeSpacing,
@@ -66,8 +67,13 @@ const ModalContent = ({ user, appInfo, enrollmentResponse }: ModalData) => {
const { mutateAsync: sendEnrollmentEmail } = useMutation({
mutationFn: api.user.startEnrollment,
onSuccess: () => {
+ Snackbar.success(m.sucessfull_enrollment_email());
closeModal(modalName);
},
+ onError: (error) => {
+ Snackbar.error(m.failed_to_start_enrollment());
+ console.error(error);
+ },
});
const formSchema = useMemo(
From 735e9148e6be697deea1082620ece79b7df76097 Mon Sep 17 00:00:00 2001
From: jakub-tldr <78603704+jakub-tldr@users.noreply.github.com>
Date: Thu, 29 Jan 2026 15:50:50 +0100
Subject: [PATCH 12/13] change type name
---
web/src/pages/UsersOverviewPage/UsersTable.tsx | 2 +-
.../modals/EnrollmentTokenModal/EnrollmentTokenModal.tsx | 2 +-
web/src/shared/hooks/modalControls/modalTypes.ts | 5 +++--
3 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/web/src/pages/UsersOverviewPage/UsersTable.tsx b/web/src/pages/UsersOverviewPage/UsersTable.tsx
index 57c15c65d3..3521b40c29 100644
--- a/web/src/pages/UsersOverviewPage/UsersTable.tsx
+++ b/web/src/pages/UsersOverviewPage/UsersTable.tsx
@@ -352,7 +352,7 @@ export const UsersTable = ({ users }: Props) => {
username: rowData.username,
})
.then((response) => {
- openModal(ModalName.EnrollmentToken, {
+ openModal(ModalName.SelfEnrollmentToken, {
user: rowData,
appInfo,
enrollmentResponse: response.data,
diff --git a/web/src/pages/UsersOverviewPage/modals/EnrollmentTokenModal/EnrollmentTokenModal.tsx b/web/src/pages/UsersOverviewPage/modals/EnrollmentTokenModal/EnrollmentTokenModal.tsx
index 0af6e8844e..0373d11340 100644
--- a/web/src/pages/UsersOverviewPage/modals/EnrollmentTokenModal/EnrollmentTokenModal.tsx
+++ b/web/src/pages/UsersOverviewPage/modals/EnrollmentTokenModal/EnrollmentTokenModal.tsx
@@ -26,7 +26,7 @@ import {
import { ModalName } from '../../../../shared/hooks/modalControls/modalTypes';
import type { OpenEnrollmentTokenModal } from '../../../../shared/hooks/modalControls/types';
-const modalName = ModalName.EnrollmentToken;
+const modalName = ModalName.SelfEnrollmentToken;
type ModalData = OpenEnrollmentTokenModal;
diff --git a/web/src/shared/hooks/modalControls/modalTypes.ts b/web/src/shared/hooks/modalControls/modalTypes.ts
index a3af739954..acfb42f7a4 100644
--- a/web/src/shared/hooks/modalControls/modalTypes.ts
+++ b/web/src/shared/hooks/modalControls/modalTypes.ts
@@ -50,7 +50,8 @@ export const ModalName = {
AddLogStreaming: 'addLogStreaming',
EditLogStreaming: 'editLogStreaming',
DeleteLogStreaming: 'deleteLogStreaming',
- EnrollmentToken: 'enrollmentToken',
+ SelfEnrollmentToken: 'selfEnrollmentToken',
+
} as const;
export type ModalNameValue = (typeof ModalName)[keyof typeof ModalName];
@@ -102,7 +103,7 @@ const modalOpenArgsSchema = z.discriminatedUnion('name', [
data: z.custom(),
}),
z.object({
- name: z.literal(ModalName.EnrollmentToken),
+ name: z.literal(ModalName.SelfEnrollmentToken),
data: z.custom(),
}),
z.object({
From 57a60bbc74c6fe12d8c90e279ae5d5c3715ded2f Mon Sep 17 00:00:00 2001
From: jakub-tldr <78603704+jakub-tldr@users.noreply.github.com>
Date: Thu, 29 Jan 2026 15:53:10 +0100
Subject: [PATCH 13/13] linter
---
web/src/shared/hooks/modalControls/modalTypes.ts | 1 -
1 file changed, 1 deletion(-)
diff --git a/web/src/shared/hooks/modalControls/modalTypes.ts b/web/src/shared/hooks/modalControls/modalTypes.ts
index acfb42f7a4..8c1e6f155d 100644
--- a/web/src/shared/hooks/modalControls/modalTypes.ts
+++ b/web/src/shared/hooks/modalControls/modalTypes.ts
@@ -51,7 +51,6 @@ export const ModalName = {
EditLogStreaming: 'editLogStreaming',
DeleteLogStreaming: 'deleteLogStreaming',
SelfEnrollmentToken: 'selfEnrollmentToken',
-
} as const;
export type ModalNameValue = (typeof ModalName)[keyof typeof ModalName];