diff --git a/public/locales/de-DE/translation.json b/public/locales/de-DE/translation.json index 5f7d6a9..9aeec54 100644 --- a/public/locales/de-DE/translation.json +++ b/public/locales/de-DE/translation.json @@ -590,6 +590,9 @@ "remove-from-group": "Aus Gruppe entfernen", "confirm-remove-from-group": "Möchten Sie den Benutzer wirklich aus dieser Gruppe entfernen?", "add-to-group": "Zur Gruppe hinzufügen", + "remove-from-tenant": "Aus Mandant entfernen", + "confirm-remove-from-tenant": "Möchten Sie den Benutzer wirklich aus diesem Mandanten entfernen?", + "add-to-tenant": "Zu Mandant hinzufügen", "no-tenants": "Der Benutzer ist derzeit kein Mitglied eines Mandanten.", "delete": "Benutzer löschen", "confirm-delete": "Möchten Sie diesen Benutzer wirklich löschen?" diff --git a/public/locales/en-US/translation.json b/public/locales/en-US/translation.json index ac20fbe..460fd59 100644 --- a/public/locales/en-US/translation.json +++ b/public/locales/en-US/translation.json @@ -590,6 +590,9 @@ "remove-from-group": "Remove from group", "confirm-remove-from-group": "Do you really want to remove the user from this group?", "add-to-group": "Add to group", + "remove-from-tenant": "Remove from tenant", + "confirm-remove-from-tenant": "Do you really want to remove the user from this tenant?", + "add-to-tenant": "Add to tenant", "no-tenants": "User is currently not a member of any tenant.", "delete": "Delete User", "confirm-delete": "Do you really want to delete this user?" diff --git a/src/pages/Admin.jsx b/src/pages/Admin.jsx index 92ac95e..9bcfb42 100644 --- a/src/pages/Admin.jsx +++ b/src/pages/Admin.jsx @@ -656,7 +656,7 @@ const UserDetails = ({ user_id }) => { - +

{t("admin.danger-zone")}

@@ -823,10 +823,32 @@ const UserGroups = ({ user_id }) => { } -const UserTenants = () => { +const UserTenants = ({ user_id }) => { const - { api: { tenant: { by_member: tenants } } } = useContext(AppState), - [t] = useTranslation() + state = useContext(AppState), + { api: { tenant: { by_member: tenants } } } = state, + [t] = useTranslation(), + add_open = useSignal(false), + remove_open = useSignal(false), + pending_remove = useSignal(null), + new_tenant = useSignal('') + + const + refetch = () => engine_rest.tenant.by_member(state, user_id), + confirm_remove = (tenant_id) => { + pending_remove.value = tenant_id + remove_open.value = true + }, + do_remove = () => + void engine_rest.tenant.remove_user(state, pending_remove.value, user_id).then(refetch), + do_add = e => { + e.preventDefault() + void engine_rest.tenant.add_user(state, new_tenant.value, user_id).then(() => { + new_tenant.value = '' + add_open.value = false + refetch() + }) + } return <>

{t("admin.tenants")}

@@ -838,6 +860,7 @@ const UserTenants = () => { {t("common.id")} {t("common.name")} + {t("common.action")} @@ -845,11 +868,31 @@ const UserTenants = () => { {tenant.id} {tenant.name} + ))} :

{t("admin.user.no-tenants")}

} /> + +
+ +
+ + +
+ + (new_tenant.value = e.currentTarget.value)} required /> +
+ + +
+
+
+ + } diff --git a/src/pages/Admin.test.jsx b/src/pages/Admin.test.jsx index 250f5d9..9690d23 100644 --- a/src/pages/Admin.test.jsx +++ b/src/pages/Admin.test.jsx @@ -177,6 +177,39 @@ describe("AdminPage", () => { expect(engine_rest.user.delete.mock.lastCall[0]).toBe(state); expect(engine_rest.user.delete.mock.lastCall[1]).toBe("jdoe"); }); + + it("adds a tenant membership from the user details page", () => { + mockParams = { page_id: "users", selection_id: "jdoe" }; + const { container, getAllByText } = renderPage(state); + + fireEvent.click(getAllByText("admin.user.add-to-tenant")[0]); + const input = container.querySelector("#add-tenant-id"); + fireEvent.input(input, { target: { value: "tenant-a" } }); + fireEvent.submit(input.closest("form")); + + expect(engine_rest.tenant.add_user).toHaveBeenCalled(); + const call = engine_rest.tenant.add_user.mock.lastCall; + expect(call[0]).toBe(state); + expect(call[1]).toBe("tenant-a"); + expect(call[2]).toBe("jdoe"); + }); + + it("removes a tenant membership from the user details page", () => { + mockParams = { page_id: "users", selection_id: "jdoe" }; + signal_response(state.api.tenant.by_member, [ + { id: "tenant-a", name: "Tenant A" }, + ]); + const { container, getByText } = renderPage(state); + + fireEvent.click(getByText("admin.user.remove-from-tenant")); + fireEvent.click(container.querySelector("dialog[open] button.danger")); + + expect(engine_rest.tenant.remove_user).toHaveBeenCalled(); + const call = engine_rest.tenant.remove_user.mock.lastCall; + expect(call[0]).toBe(state); + expect(call[1]).toBe("tenant-a"); + expect(call[2]).toBe("jdoe"); + }); }); describe("Groups", () => {