From c07363a9eab594ce64061268e47732eeb93073b7 Mon Sep 17 00:00:00 2001 From: jakub-tldr <78603704+jakub-tldr@users.noreply.github.com> Date: Thu, 29 Jan 2026 12:40:27 +0100 Subject: [PATCH 01/13] Initiate self-enrollment from users list --- web/messages/en/modal.json | 4 +- web/messages/en/users.json | 1 + .../UsersOverviewPage/UsersOverviewPage.tsx | 2 + .../pages/UsersOverviewPage/UsersTable.tsx | 16 ++ .../EnrollmentTokenModal.tsx | 219 ++++++++++++++++++ web/src/shared/defguard-ui | 2 +- .../shared/hooks/modalControls/modalTypes.ts | 37 +-- web/src/shared/hooks/modalControls/types.ts | 4 + 8 files changed, 265 insertions(+), 20 deletions(-) create mode 100644 web/src/pages/UsersOverviewPage/modals/EnrollmentTokenModal/EnrollmentTokenModal.tsx diff --git a/web/messages/en/modal.json b/web/messages/en/modal.json index 58f0504d99..a8deebc459 100644 --- a/web/messages/en/modal.json +++ b/web/messages/en/modal.json @@ -62,7 +62,7 @@ "modal_add_user_device_manual_download_actions_download_all": "All locations", "modal_add_user_enrollment_details": "Enrollment details", "modal_add_user_enrollment_explain": "Provide the user with their token and URL in a convenient manner so they can complete the enrollment process independently.", - "modal_add_user_enrollment_form_label_url": "URL", + "modal_add_user_enrollment_form_label_instance_url": "Defguard instance URL", "modal_add_user_enrollment_form_label_token": "Activation token", "modal_add_user_enrollment_form_label_send": "Send enrollment details to user by email", "modal_edit_user_device_title": "Edit device", @@ -85,7 +85,7 @@ "modal_select_log_streaming_destination_title": "Select destination", "modal_add_log_streaming_destination_title": "Add destination", "modal_add_user_groups_title": "Assign user to group(s)", - "modal_add_user_enroll_title": "Start enrollment for user", + "modal_initiate_self_enrollment_title": "Initiate self-enrollment for user", "modal_add_user_choice_enroll_title": "Add user with self-enrollment option", "modal_add_user_choice_enroll_content": "User will be able to configure their account during the setup of their desktop client (e.g., set their own password, etc.).", "modal_add_user_choice_manual_title": "Add user manually", diff --git a/web/messages/en/users.json b/web/messages/en/users.json index 6844063276..75741282ff 100644 --- a/web/messages/en/users.json +++ b/web/messages/en/users.json @@ -24,5 +24,6 @@ "users_row_menu_edit": "Edit details", "users_row_menu_change_password": "Change password", "users_row_menu_edit_groups": "Edit groups", + "users_row_menu_initiate_self_enrollment": "Initiate self-enrollment", "modal_edit_user_groups_title": "Edit user groups" } diff --git a/web/src/pages/UsersOverviewPage/UsersOverviewPage.tsx b/web/src/pages/UsersOverviewPage/UsersOverviewPage.tsx index f7f3fed6d9..2d18bd0d59 100644 --- a/web/src/pages/UsersOverviewPage/UsersOverviewPage.tsx +++ b/web/src/pages/UsersOverviewPage/UsersOverviewPage.tsx @@ -11,6 +11,7 @@ import { AddUserModal } from './modals/AddUserModal/AddUserModal'; import { AssignUsersToGroupsModal } from './modals/AssignUsersToGroupsModal/AssignUsersToGroupsModal'; import { EditUserModal } from './modals/EditUserModal/EditUserModal'; import { UsersTable } from './UsersTable'; +import { EnrollmentTokenModal } from './modals/EnrollmentTokenModal/EnrollmentTokenModal'; export const UsersOverviewPage = () => { const { data: users } = useQuery(getUsersQueryOptions); @@ -23,6 +24,7 @@ export const UsersOverviewPage = () => { + diff --git a/web/src/pages/UsersOverviewPage/UsersTable.tsx b/web/src/pages/UsersOverviewPage/UsersTable.tsx index ea9b3f5099..285531b6d1 100644 --- a/web/src/pages/UsersOverviewPage/UsersTable.tsx +++ b/web/src/pages/UsersOverviewPage/UsersTable.tsx @@ -242,6 +242,7 @@ export const UsersTable = ({ users }: Props) => { menuItems={[ { items: [ + { text: m.users_row_menu_edit(), icon: 'edit', @@ -294,6 +295,21 @@ export const UsersTable = ({ users }: Props) => { }, ], }, + { + items: [ ...(!rowData.enrolled + ? [ + { + text: m.users_row_menu_initiate_self_enrollment(), + icon: 'add-user' as const, + onClick: () => { + openModal(ModalName.EnrollmentToken, { + user: rowData, + }); + }, + }, + ] + : []),] + }, { items: [ { diff --git a/web/src/pages/UsersOverviewPage/modals/EnrollmentTokenModal/EnrollmentTokenModal.tsx b/web/src/pages/UsersOverviewPage/modals/EnrollmentTokenModal/EnrollmentTokenModal.tsx new file mode 100644 index 0000000000..f5368d61db --- /dev/null +++ b/web/src/pages/UsersOverviewPage/modals/EnrollmentTokenModal/EnrollmentTokenModal.tsx @@ -0,0 +1,219 @@ +import { useStore } from '@tanstack/react-form'; +import { useMutation, useQuery } from '@tanstack/react-query'; +import { useEffect, useMemo, useState } from 'react'; +import z from 'zod'; +import { m } from '../../../../paraglide/messages'; +import api from '../../../../shared/api/api'; +import { AppText } from '../../../../shared/defguard-ui/components/AppText/AppText'; +import { Checkbox } from '../../../../shared/defguard-ui/components/Checkbox/Checkbox'; +import { CopyField } from '../../../../shared/defguard-ui/components/CopyField/CopyField'; +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 { + TextStyle, + ThemeSpacing, + ThemeVariable, +} from '../../../../shared/defguard-ui/types'; +import { isPresent } from '../../../../shared/defguard-ui/utils/isPresent'; +import { useAppForm } from '../../../../shared/form'; +import { formChangeLogic } from '../../../../shared/formLogic'; +import { + closeModal, + subscribeCloseModal, + subscribeOpenModal, +} from '../../../../shared/hooks/modalControls/modalsSubjects'; +import { ModalName } from '../../../../shared/hooks/modalControls/modalTypes'; +import type { OpenEnrollmentTokenModal } from '../../../../shared/hooks/modalControls/types'; +import { useApp } from '../../../../shared/hooks/useApp'; + +const modalName = ModalName.EnrollmentToken; + +type ModalData = OpenEnrollmentTokenModal; + +export const EnrollmentTokenModal = () => { + const [isOpen, setOpen] = useState(false); + const [modalData, setModalData] = useState(null); + + useEffect(() => { + const openSub = subscribeOpenModal(modalName, (data) => { + setModalData(data); + setOpen(true); + }); + const closeSub = subscribeCloseModal(modalName, () => setOpen(false)); + return () => { + openSub.unsubscribe(); + closeSub.unsubscribe(); + }; + }, []); + + return ( + setOpen(false)} + afterClose={() => { + setModalData(null); + }} + > + {isPresent(modalData) && } + + ); +}; + +const ModalContent = ({ user }: ModalData) => { + const [sendEmail, setSendEmail] = useState(false); + const appInfo = useApp((s) => s.appInfo); + + const { data: enrollmentData } = useQuery({ + queryFn: async () => { + const response = await api.user.startEnrollment({ + username: user.username, + send_enrollment_notification: false, + }); + return { + enrollment_url: response.data.enrollment_url, + enrollment_token: response.data.enrollment_token, + }; + }, + queryKey: ['enrollment-token', user.username], + refetchOnWindowFocus: false, + refetchOnMount: true, + }); + + const formSchema = useMemo( + () => + z + .object({ + email: z.string(), + }) + .superRefine((values, ctx) => { + if (sendEmail) { + const result = z + .email(m.form_error_email()) + .min(1, m.form_error_required()) + .safeParse(values.email); + if (!result.success) { + ctx.addIssue({ + code: 'custom', + path: ['email'], + message: result.error.issues[0].message, + }); + } + } + }), + [sendEmail], + ); + + const { mutateAsync: sendEnrollmentEmail } = useMutation({ + mutationFn: async (email: string) => { + return api.user.startEnrollment({ + username: user.username, + send_enrollment_notification: true, + email, + }); + }, + }); + + const form = useAppForm({ + defaultValues: { + email: user?.email ?? '', + }, + validationLogic: formChangeLogic, + validators: { + onSubmit: formSchema, + onChange: formSchema, + }, + onSubmit: async ({ value }) => { + await sendEnrollmentEmail(value.email); + closeModal(modalName); + }, + }); + + const isSubmitting = useStore(form.store, (s) => s.isSubmitting); + + useEffect(() => { + if (!form.state.isPristine) { + form.validateAllFields('change'); + } + }, [form.state.isPristine, form.validateAllFields]); + + if (!isPresent(enrollmentData)) { + return; + } + + return ( + <> +
+ + {m.modal_add_user_enrollment_details()} + + + + {m.modal_add_user_enrollment_explain()} + +
+ + + + + {appInfo.smtp_enabled && ( +
{ + e.stopPropagation(); + e.preventDefault(); + form.handleSubmit(); + }} + > + + + { + setSendEmail((s) => !s); + }} + /> + {sendEmail && ( + <> + + + {(field) => ( + + )} + + + )} + + + )} + { + if (sendEmail) { + form.handleSubmit(); + } else { + closeModal(modalName); + } + }, + }} + /> + + ); +}; diff --git a/web/src/shared/defguard-ui b/web/src/shared/defguard-ui index dc3ada9c02..b9fad0ee9e 160000 --- a/web/src/shared/defguard-ui +++ b/web/src/shared/defguard-ui @@ -1 +1 @@ -Subproject commit dc3ada9c0254082fbcba925c8f7b11ff914b5865 +Subproject commit b9fad0ee9ea351641db659fb22988c6d8d0d5089 diff --git a/web/src/shared/hooks/modalControls/modalTypes.ts b/web/src/shared/hooks/modalControls/modalTypes.ts index 7e5e0ddbe5..c3c0bef3c8 100644 --- a/web/src/shared/hooks/modalControls/modalTypes.ts +++ b/web/src/shared/hooks/modalControls/modalTypes.ts @@ -1,22 +1,23 @@ import z from 'zod'; import type { ActivityLogStream, AddDeviceResponse, User } from '../../api/types'; -import type { - OpenAddApiTokenModal, - OpenAddNetworkDeviceModal, - OpenAssignUsersToGroupsModal, - OpenAuthKeyRenameModal, - OpenCEGroupModal, - OpenCEOpenIdClientModal, - OpenCEWebhookModal, - OpenDisplayListModal, - OpenEditDeviceModal, - OpenEditNetworkDeviceModal, - OpenEditUserModal, - OpenLicenseModal, - OpenNetworkDeviceConfigModal, - OpenNetworkDeviceTokenModal, - OpenRenameApiTokenModal, - OpenUpgradeLicenseModal, +import { + type OpenAddApiTokenModal, + type OpenAddNetworkDeviceModal, + type OpenAssignUsersToGroupsModal, + type OpenAuthKeyRenameModal, + type OpenCEGroupModal, + type OpenCEOpenIdClientModal, + type OpenCEWebhookModal, + type OpenDisplayListModal, + type OpenEditDeviceModal, + type OpenEditNetworkDeviceModal, + type OpenEditUserModal, + type OpenLicenseModal, + type OpenEnrollmentTokenModal, + type OpenNetworkDeviceConfigModal, + type OpenNetworkDeviceTokenModal, + type OpenRenameApiTokenModal, + type OpenUpgradeLicenseModal, } from './types'; export const ModalName = { @@ -49,6 +50,7 @@ export const ModalName = { AddLogStreaming: 'addLogStreaming', EditLogStreaming: 'editLogStreaming', DeleteLogStreaming: 'deleteLogStreaming', + EnrollmentToken: 'enrollmentToken', } as const; export type ModalNameValue = (typeof ModalName)[keyof typeof ModalName]; @@ -99,6 +101,7 @@ const modalOpenArgsSchema = z.discriminatedUnion('name', [ name: z.literal(ModalName.EditUserModal), data: z.custom(), }), + z.object({name: z.literal(ModalName.EnrollmentToken), data: z.custom()}), z.object({ name: z.literal(ModalName.EditUserModal), data: z.custom(), diff --git a/web/src/shared/hooks/modalControls/types.ts b/web/src/shared/hooks/modalControls/types.ts index e01e845d85..350701ec8d 100644 --- a/web/src/shared/hooks/modalControls/types.ts +++ b/web/src/shared/hooks/modalControls/types.ts @@ -54,6 +54,10 @@ export interface OpenCEOpenIdClientModal { reservedNames: string[]; } +export interface OpenEnrollmentTokenModal { + user: User; +} + export interface OpenCEWebhookModal { webhook?: Webhook; } From 38a01457b577b5091cf4c772e00f3100b236b958 Mon Sep 17 00:00:00 2001 From: jakub-tldr <78603704+jakub-tldr@users.noreply.github.com> Date: Thu, 29 Jan 2026 12:42:56 +0100 Subject: [PATCH 02/13] lint --- .../UsersOverviewPage/UsersOverviewPage.tsx | 2 +- .../pages/UsersOverviewPage/UsersTable.tsx | 7 ++-- .../EnrollmentTokenModal.tsx | 5 +-- .../shared/hooks/modalControls/modalTypes.ts | 41 ++++++++++--------- 4 files changed, 28 insertions(+), 27 deletions(-) diff --git a/web/src/pages/UsersOverviewPage/UsersOverviewPage.tsx b/web/src/pages/UsersOverviewPage/UsersOverviewPage.tsx index 2d18bd0d59..6bfa2bd815 100644 --- a/web/src/pages/UsersOverviewPage/UsersOverviewPage.tsx +++ b/web/src/pages/UsersOverviewPage/UsersOverviewPage.tsx @@ -10,8 +10,8 @@ import { getUsersQueryOptions } from '../../shared/query'; import { AddUserModal } from './modals/AddUserModal/AddUserModal'; import { AssignUsersToGroupsModal } from './modals/AssignUsersToGroupsModal/AssignUsersToGroupsModal'; import { EditUserModal } from './modals/EditUserModal/EditUserModal'; -import { UsersTable } from './UsersTable'; import { EnrollmentTokenModal } from './modals/EnrollmentTokenModal/EnrollmentTokenModal'; +import { UsersTable } from './UsersTable'; export const UsersOverviewPage = () => { const { data: users } = useQuery(getUsersQueryOptions); diff --git a/web/src/pages/UsersOverviewPage/UsersTable.tsx b/web/src/pages/UsersOverviewPage/UsersTable.tsx index 285531b6d1..a2d0407fa2 100644 --- a/web/src/pages/UsersOverviewPage/UsersTable.tsx +++ b/web/src/pages/UsersOverviewPage/UsersTable.tsx @@ -242,7 +242,6 @@ export const UsersTable = ({ users }: Props) => { menuItems={[ { items: [ - { text: m.users_row_menu_edit(), icon: 'edit', @@ -296,7 +295,8 @@ export const UsersTable = ({ users }: Props) => { ], }, { - items: [ ...(!rowData.enrolled + items: [ + ...(!rowData.enrolled ? [ { text: m.users_row_menu_initiate_self_enrollment(), @@ -308,7 +308,8 @@ export const UsersTable = ({ users }: Props) => { }, }, ] - : []),] + : []), + ], }, { items: [ diff --git a/web/src/pages/UsersOverviewPage/modals/EnrollmentTokenModal/EnrollmentTokenModal.tsx b/web/src/pages/UsersOverviewPage/modals/EnrollmentTokenModal/EnrollmentTokenModal.tsx index f5368d61db..5f3a85534a 100644 --- a/web/src/pages/UsersOverviewPage/modals/EnrollmentTokenModal/EnrollmentTokenModal.tsx +++ b/web/src/pages/UsersOverviewPage/modals/EnrollmentTokenModal/EnrollmentTokenModal.tsx @@ -190,10 +190,7 @@ const ModalContent = ({ user }: ModalData) => { {(field) => ( - + )} diff --git a/web/src/shared/hooks/modalControls/modalTypes.ts b/web/src/shared/hooks/modalControls/modalTypes.ts index c3c0bef3c8..a3af739954 100644 --- a/web/src/shared/hooks/modalControls/modalTypes.ts +++ b/web/src/shared/hooks/modalControls/modalTypes.ts @@ -1,23 +1,23 @@ import z from 'zod'; import type { ActivityLogStream, AddDeviceResponse, User } from '../../api/types'; -import { - type OpenAddApiTokenModal, - type OpenAddNetworkDeviceModal, - type OpenAssignUsersToGroupsModal, - type OpenAuthKeyRenameModal, - type OpenCEGroupModal, - type OpenCEOpenIdClientModal, - type OpenCEWebhookModal, - type OpenDisplayListModal, - type OpenEditDeviceModal, - type OpenEditNetworkDeviceModal, - type OpenEditUserModal, - type OpenLicenseModal, - type OpenEnrollmentTokenModal, - type OpenNetworkDeviceConfigModal, - type OpenNetworkDeviceTokenModal, - type OpenRenameApiTokenModal, - type OpenUpgradeLicenseModal, +import type { + OpenAddApiTokenModal, + OpenAddNetworkDeviceModal, + OpenAssignUsersToGroupsModal, + OpenAuthKeyRenameModal, + OpenCEGroupModal, + OpenCEOpenIdClientModal, + OpenCEWebhookModal, + OpenDisplayListModal, + OpenEditDeviceModal, + OpenEditNetworkDeviceModal, + OpenEditUserModal, + OpenEnrollmentTokenModal, + OpenLicenseModal, + OpenNetworkDeviceConfigModal, + OpenNetworkDeviceTokenModal, + OpenRenameApiTokenModal, + OpenUpgradeLicenseModal, } from './types'; export const ModalName = { @@ -101,7 +101,10 @@ const modalOpenArgsSchema = z.discriminatedUnion('name', [ name: z.literal(ModalName.EditUserModal), data: z.custom(), }), - z.object({name: z.literal(ModalName.EnrollmentToken), data: z.custom()}), + z.object({ + name: z.literal(ModalName.EnrollmentToken), + data: z.custom(), + }), z.object({ name: z.literal(ModalName.EditUserModal), data: z.custom(), From fb034ebad5c017a89a669082bee1f5fce35aa594 Mon Sep 17 00:00:00 2001 From: jakub-tldr <78603704+jakub-tldr@users.noreply.github.com> Date: Thu, 29 Jan 2026 12:44:26 +0100 Subject: [PATCH 03/13] typo fix --- .../UsersOverviewPage/modals/AddUserModal/AddUserModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/pages/UsersOverviewPage/modals/AddUserModal/AddUserModal.tsx b/web/src/pages/UsersOverviewPage/modals/AddUserModal/AddUserModal.tsx index 9f5dae9633..7c721a3690 100644 --- a/web/src/pages/UsersOverviewPage/modals/AddUserModal/AddUserModal.tsx +++ b/web/src/pages/UsersOverviewPage/modals/AddUserModal/AddUserModal.tsx @@ -149,7 +149,7 @@ const EnrollmentStep = () => { From 9d8ab41a33a5927f58e316c827a6d080fb0f2364 Mon Sep 17 00:00:00 2001 From: jakub-tldr <78603704+jakub-tldr@users.noreply.github.com> Date: Thu, 29 Jan 2026 12:47:09 +0100 Subject: [PATCH 04/13] fix translation --- web/messages/en/modal.json | 1 + 1 file changed, 1 insertion(+) diff --git a/web/messages/en/modal.json b/web/messages/en/modal.json index a8deebc459..69bc3657b6 100644 --- a/web/messages/en/modal.json +++ b/web/messages/en/modal.json @@ -86,6 +86,7 @@ "modal_add_log_streaming_destination_title": "Add destination", "modal_add_user_groups_title": "Assign user to group(s)", "modal_initiate_self_enrollment_title": "Initiate self-enrollment for user", + "modal_add_user_enroll_title": "Start enrollment for user", "modal_add_user_choice_enroll_title": "Add user with self-enrollment option", "modal_add_user_choice_enroll_content": "User will be able to configure their account during the setup of their desktop client (e.g., set their own password, etc.).", "modal_add_user_choice_manual_title": "Add user manually", From 6f09be9f1499111aa20f3e0e3abfe2f91e609a1b Mon Sep 17 00:00:00 2001 From: jakub-tldr <78603704+jakub-tldr@users.noreply.github.com> Date: Thu, 29 Jan 2026 12:59:07 +0100 Subject: [PATCH 05/13] fixes --- .../pages/UsersOverviewPage/UsersTable.tsx | 16 +++++----- .../EnrollmentTokenModal.tsx | 29 ++++--------------- 2 files changed, 14 insertions(+), 31 deletions(-) diff --git a/web/src/pages/UsersOverviewPage/UsersTable.tsx b/web/src/pages/UsersOverviewPage/UsersTable.tsx index a2d0407fa2..ef7c06608f 100644 --- a/web/src/pages/UsersOverviewPage/UsersTable.tsx +++ b/web/src/pages/UsersOverviewPage/UsersTable.tsx @@ -294,10 +294,10 @@ export const UsersTable = ({ users }: Props) => { }, ], }, - { - items: [ - ...(!rowData.enrolled - ? [ + ...(!rowData.enrolled + ? [ + { + items: [ { text: m.users_row_menu_initiate_self_enrollment(), icon: 'add-user' as const, @@ -307,10 +307,10 @@ export const UsersTable = ({ users }: Props) => { }); }, }, - ] - : []), - ], - }, + ], + }, + ] + : []), { items: [ { diff --git a/web/src/pages/UsersOverviewPage/modals/EnrollmentTokenModal/EnrollmentTokenModal.tsx b/web/src/pages/UsersOverviewPage/modals/EnrollmentTokenModal/EnrollmentTokenModal.tsx index 5f3a85534a..2354ecc67c 100644 --- a/web/src/pages/UsersOverviewPage/modals/EnrollmentTokenModal/EnrollmentTokenModal.tsx +++ b/web/src/pages/UsersOverviewPage/modals/EnrollmentTokenModal/EnrollmentTokenModal.tsx @@ -79,7 +79,6 @@ const ModalContent = ({ user }: ModalData) => { }, queryKey: ['enrollment-token', user.username], refetchOnWindowFocus: false, - refetchOnMount: true, }); const formSchema = useMemo( @@ -118,7 +117,7 @@ const ModalContent = ({ user }: ModalData) => { const form = useAppForm({ defaultValues: { - email: user?.email ?? '', + email: user.email ?? '', }, validationLogic: formChangeLogic, validators: { @@ -169,46 +168,30 @@ const ModalContent = ({ user }: ModalData) => { text={enrollmentData.enrollment_token} /> {appInfo.smtp_enabled && ( -
{ - e.stopPropagation(); - e.preventDefault(); - form.handleSubmit(); - }} - > + <> { - setSendEmail((s) => !s); - }} + onClick={() => setSendEmail((s) => !s)} /> {sendEmail && ( <> - {(field) => ( - - )} + {(field) => } )} - + )} { - if (sendEmail) { - form.handleSubmit(); - } else { - closeModal(modalName); - } - }, + onClick: sendEmail ? form.handleSubmit : () => closeModal(modalName), }} /> From 8ea789bcb7e011568ca3ed0542e51a019f415615 Mon Sep 17 00:00:00 2001 From: jakub-tldr <78603704+jakub-tldr@users.noreply.github.com> Date: Thu, 29 Jan 2026 13:42:56 +0100 Subject: [PATCH 06/13] apply suggestions, fix bug when creating new user --- .../pages/UsersOverviewPage/UsersTable.tsx | 41 ++++++++++-------- .../modals/AddUserModal/AddUserModal.tsx | 2 +- .../EnrollmentTokenModal.tsx | 42 ++++++++----------- web/src/shared/hooks/modalControls/types.ts | 3 ++ 4 files changed, 44 insertions(+), 44 deletions(-) diff --git a/web/src/pages/UsersOverviewPage/UsersTable.tsx b/web/src/pages/UsersOverviewPage/UsersTable.tsx index ef7c06608f..2ef2359620 100644 --- a/web/src/pages/UsersOverviewPage/UsersTable.tsx +++ b/web/src/pages/UsersOverviewPage/UsersTable.tsx @@ -26,7 +26,7 @@ import { Button } from '../../shared/defguard-ui/components/Button/Button'; import type { ButtonProps } from '../../shared/defguard-ui/components/Button/types'; import { EmptyState } from '../../shared/defguard-ui/components/EmptyState/EmptyState'; import { EmptyStateFlexible } from '../../shared/defguard-ui/components/EmptyStateFlexible/EmptyStateFlexible'; -import { Icon } from '../../shared/defguard-ui/components/Icon'; +import { Icon, IconKind } from '../../shared/defguard-ui/components/Icon'; import { IconButtonMenu } from '../../shared/defguard-ui/components/IconButtonMenu/IconButtonMenu'; import { Search } from '../../shared/defguard-ui/components/Search/Search'; import { tableEditColumnSize } from '../../shared/defguard-ui/components/table/consts'; @@ -38,6 +38,7 @@ import { TableTop } from '../../shared/defguard-ui/components/table/TableTop/Tab import { isPresent } from '../../shared/defguard-ui/utils/isPresent'; import { openModal } from '../../shared/hooks/modalControls/modalsSubjects'; import { ModalName } from '../../shared/hooks/modalControls/modalTypes'; +import { useApp } from '../../shared/hooks/useApp'; import { getGroupsInfoQueryOptions } from '../../shared/query'; import { displayDate } from '../../shared/utils/displayDate'; import { useAddUserModal } from './modals/AddUserModal/useAddUserModal'; @@ -51,6 +52,7 @@ type RowData = UsersListItem; const columnHelper = createColumnHelper(); export const UsersTable = ({ users }: Props) => { + const appInfo = useApp((s) => s.appInfo); const reservedEmails = useMemo(() => users.map((u) => u.email.toLowerCase()), [users]); const reservedUsernames = useMemo(() => users.map((u) => u.username), [users]); @@ -235,6 +237,24 @@ export const UsersTable = ({ users }: Props) => { enableResizing: false, cell: (info) => { const rowData = info.row.original; + + const enrollmentSection = !rowData.enrolled + ? { + items: [ + { + text: m.users_row_menu_initiate_self_enrollment(), + icon: IconKind.AddUser, + onClick: () => { + openModal(ModalName.EnrollmentToken, { + user: rowData, + appInfo, + }); + }, + }, + ], + } + : null; + return ( { }, ], }, - ...(!rowData.enrolled - ? [ - { - items: [ - { - text: m.users_row_menu_initiate_self_enrollment(), - icon: 'add-user' as const, - onClick: () => { - openModal(ModalName.EnrollmentToken, { - user: rowData, - }); - }, - }, - ], - }, - ] - : []), + ...(enrollmentSection ? [enrollmentSection] : []), { items: [ { @@ -369,6 +373,7 @@ export const UsersTable = ({ users }: Props) => { groupsOptions, handleEditGroups, groups, + appInfo, ], ); diff --git a/web/src/pages/UsersOverviewPage/modals/AddUserModal/AddUserModal.tsx b/web/src/pages/UsersOverviewPage/modals/AddUserModal/AddUserModal.tsx index 7c721a3690..9e82005ed1 100644 --- a/web/src/pages/UsersOverviewPage/modals/AddUserModal/AddUserModal.tsx +++ b/web/src/pages/UsersOverviewPage/modals/AddUserModal/AddUserModal.tsx @@ -364,7 +364,7 @@ const AddUserModalForm = () => { username: created.username, }) ).data; - useAddUserModal.setState({ enrollResponse: enrollmentResponse }); + useAddUserModal.setState({ enrollResponse: enrollmentResponse, user: created }); } 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 2354ecc67c..98a2ad1372 100644 --- a/web/src/pages/UsersOverviewPage/modals/EnrollmentTokenModal/EnrollmentTokenModal.tsx +++ b/web/src/pages/UsersOverviewPage/modals/EnrollmentTokenModal/EnrollmentTokenModal.tsx @@ -1,5 +1,4 @@ -import { useStore } from '@tanstack/react-form'; -import { useMutation, useQuery } from '@tanstack/react-query'; +import { useQuery } from '@tanstack/react-query'; import { useEffect, useMemo, useState } from 'react'; import z from 'zod'; import { m } from '../../../../paraglide/messages'; @@ -25,7 +24,6 @@ import { } from '../../../../shared/hooks/modalControls/modalsSubjects'; import { ModalName } from '../../../../shared/hooks/modalControls/modalTypes'; import type { OpenEnrollmentTokenModal } from '../../../../shared/hooks/modalControls/types'; -import { useApp } from '../../../../shared/hooks/useApp'; const modalName = ModalName.EnrollmentToken; @@ -62,9 +60,8 @@ export const EnrollmentTokenModal = () => { ); }; -const ModalContent = ({ user }: ModalData) => { +const ModalContent = ({ user, appInfo }: ModalData) => { const [sendEmail, setSendEmail] = useState(false); - const appInfo = useApp((s) => s.appInfo); const { data: enrollmentData } = useQuery({ queryFn: async () => { @@ -105,16 +102,6 @@ const ModalContent = ({ user }: ModalData) => { [sendEmail], ); - const { mutateAsync: sendEnrollmentEmail } = useMutation({ - mutationFn: async (email: string) => { - return api.user.startEnrollment({ - username: user.username, - send_enrollment_notification: true, - email, - }); - }, - }); - const form = useAppForm({ defaultValues: { email: user.email ?? '', @@ -125,13 +112,15 @@ const ModalContent = ({ user }: ModalData) => { onChange: formSchema, }, onSubmit: async ({ value }) => { - await sendEnrollmentEmail(value.email); + await api.user.startEnrollment({ + username: user.username, + send_enrollment_notification: true, + email: value.email, + }); closeModal(modalName); }, }); - const isSubmitting = useStore(form.store, (s) => s.isSubmitting); - useEffect(() => { if (!form.state.isPristine) { form.validateAllFields('change'); @@ -187,13 +176,16 @@ const ModalContent = ({ user }: ModalData) => { )} - closeModal(modalName), - }} - /> + + {() => ( + closeModal(modalName), + }} + /> + )} + ); }; diff --git a/web/src/shared/hooks/modalControls/types.ts b/web/src/shared/hooks/modalControls/types.ts index 350701ec8d..3c4f30ad10 100644 --- a/web/src/shared/hooks/modalControls/types.ts +++ b/web/src/shared/hooks/modalControls/types.ts @@ -56,6 +56,9 @@ export interface OpenCEOpenIdClientModal { export interface OpenEnrollmentTokenModal { user: User; + appInfo: { + smtp_enabled: boolean; + }; } export interface OpenCEWebhookModal { From 618394ecfb5af72d85e2183b390e835f158500dc Mon Sep 17 00:00:00 2001 From: jakub-tldr <78603704+jakub-tldr@users.noreply.github.com> Date: Thu, 29 Jan 2026 14:29:39 +0100 Subject: [PATCH 07/13] change api call --- .../EnrollmentTokenModal/EnrollmentTokenModal.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/web/src/pages/UsersOverviewPage/modals/EnrollmentTokenModal/EnrollmentTokenModal.tsx b/web/src/pages/UsersOverviewPage/modals/EnrollmentTokenModal/EnrollmentTokenModal.tsx index 98a2ad1372..d4074854bf 100644 --- a/web/src/pages/UsersOverviewPage/modals/EnrollmentTokenModal/EnrollmentTokenModal.tsx +++ b/web/src/pages/UsersOverviewPage/modals/EnrollmentTokenModal/EnrollmentTokenModal.tsx @@ -1,4 +1,4 @@ -import { useQuery } from '@tanstack/react-query'; +import { useMutation, useQuery } from '@tanstack/react-query'; import { useEffect, useMemo, useState } from 'react'; import z from 'zod'; import { m } from '../../../../paraglide/messages'; @@ -63,6 +63,13 @@ export const EnrollmentTokenModal = () => { const ModalContent = ({ user, appInfo }: ModalData) => { const [sendEmail, setSendEmail] = useState(false); + const { mutateAsync: sendEnrollmentEmail } = useMutation({ + mutationFn: api.user.startEnrollment, + onSuccess: () => { + closeModal(modalName); + }, + }); + const { data: enrollmentData } = useQuery({ queryFn: async () => { const response = await api.user.startEnrollment({ @@ -112,12 +119,11 @@ const ModalContent = ({ user, appInfo }: ModalData) => { onChange: formSchema, }, onSubmit: async ({ value }) => { - await api.user.startEnrollment({ + await sendEnrollmentEmail({ username: user.username, send_enrollment_notification: true, email: value.email, }); - closeModal(modalName); }, }); From d50bfd6c6fcf0c97e8fd53e3a75917e75e7929ed Mon Sep 17 00:00:00 2001 From: jakub-tldr <78603704+jakub-tldr@users.noreply.github.com> Date: Thu, 29 Jan 2026 14:41:00 +0100 Subject: [PATCH 08/13] generate new token on each button click --- .../pages/UsersOverviewPage/UsersTable.tsx | 9 ++++++- .../EnrollmentTokenModal.tsx | 27 +++---------------- web/src/shared/hooks/modalControls/types.ts | 1 + 3 files changed, 13 insertions(+), 24 deletions(-) diff --git a/web/src/pages/UsersOverviewPage/UsersTable.tsx b/web/src/pages/UsersOverviewPage/UsersTable.tsx index 2ef2359620..5a2a2061a8 100644 --- a/web/src/pages/UsersOverviewPage/UsersTable.tsx +++ b/web/src/pages/UsersOverviewPage/UsersTable.tsx @@ -244,10 +244,17 @@ export const UsersTable = ({ users }: Props) => { { text: m.users_row_menu_initiate_self_enrollment(), icon: IconKind.AddUser, - onClick: () => { + onClick: async () => { + const enrollmentResponse = ( + await api.user.startEnrollment({ + send_enrollment_notification: false, + username: rowData.username, + }) + ).data; openModal(ModalName.EnrollmentToken, { user: rowData, appInfo, + enrollmentResponse, }); }, }, diff --git a/web/src/pages/UsersOverviewPage/modals/EnrollmentTokenModal/EnrollmentTokenModal.tsx b/web/src/pages/UsersOverviewPage/modals/EnrollmentTokenModal/EnrollmentTokenModal.tsx index d4074854bf..31a6f29349 100644 --- a/web/src/pages/UsersOverviewPage/modals/EnrollmentTokenModal/EnrollmentTokenModal.tsx +++ b/web/src/pages/UsersOverviewPage/modals/EnrollmentTokenModal/EnrollmentTokenModal.tsx @@ -1,4 +1,4 @@ -import { useMutation, useQuery } from '@tanstack/react-query'; +import { useMutation } from '@tanstack/react-query'; import { useEffect, useMemo, useState } from 'react'; import z from 'zod'; import { m } from '../../../../paraglide/messages'; @@ -60,7 +60,7 @@ export const EnrollmentTokenModal = () => { ); }; -const ModalContent = ({ user, appInfo }: ModalData) => { +const ModalContent = ({ user, appInfo, enrollmentResponse }: ModalData) => { const [sendEmail, setSendEmail] = useState(false); const { mutateAsync: sendEnrollmentEmail } = useMutation({ @@ -70,21 +70,6 @@ const ModalContent = ({ user, appInfo }: ModalData) => { }, }); - const { data: enrollmentData } = useQuery({ - queryFn: async () => { - const response = await api.user.startEnrollment({ - username: user.username, - send_enrollment_notification: false, - }); - return { - enrollment_url: response.data.enrollment_url, - enrollment_token: response.data.enrollment_token, - }; - }, - queryKey: ['enrollment-token', user.username], - refetchOnWindowFocus: false, - }); - const formSchema = useMemo( () => z @@ -133,10 +118,6 @@ const ModalContent = ({ user, appInfo }: ModalData) => { } }, [form.state.isPristine, form.validateAllFields]); - if (!isPresent(enrollmentData)) { - return; - } - return ( <>
@@ -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];