Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions public/locales/de-DE/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -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?"
Expand Down
3 changes: 3 additions & 0 deletions public/locales/en-US/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -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?"
Expand Down
51 changes: 47 additions & 4 deletions src/pages/Admin.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -656,7 +656,7 @@ const UserDetails = ({ user_id }) => {
<UserProfile user_id={user_id} />
<UserPassword user_id={user_id} />
<UserGroups user_id={user_id} />
<UserTenants />
<UserTenants user_id={user_id} />

<h3>{t("admin.danger-zone")}</h3>
<div class="button-group">
Expand Down Expand Up @@ -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 <>
<h3>{t("admin.tenants")}</h3>
Expand All @@ -838,18 +860,39 @@ const UserTenants = () => {
<tr>
<th>{t("common.id")}</th>
<th>{t("common.name")}</th>
<th>{t("common.action")}</th>
</tr>
</thead>
<tbody>
{tenants.value.data.map((tenant) => (
<tr key={tenant.id}>
<td>{tenant.id}</td>
<td>{tenant.name}</td>
<td><button class="danger" onClick={() => confirm_remove(tenant.id)}>{t("admin.user.remove-from-tenant")}</button></td>
</tr>
))}
</tbody>
</table>
: <p>{t("admin.user.no-tenants")}</p>} />

<div class="button-group">
<button onClick={() => (add_open.value = true)}>{t("admin.user.add-to-tenant")}</button>
</div>

<Dialog open={add_open} title={t("admin.user.add-to-tenant")}>
<form onSubmit={do_add}>
<label for="add-tenant-id">{t("admin.tenant.tenant-id")}</label>
<input id="add-tenant-id" type="text" value={new_tenant.value}
onInput={(e) => (new_tenant.value = e.currentTarget.value)} required />
<div class="button-group">
<button type="submit">{t("common.save")}</button>
<button type="button" class="secondary" onClick={() => (add_open.value = false)}>{t("common.cancel")}</button>
</div>
</form>
</Dialog>

<ConfirmDialog open={remove_open} confirm_label={t("common.remove")}
message={t("admin.user.confirm-remove-from-tenant")} on_confirm={do_remove} />
</>
}

Expand Down
33 changes: 33 additions & 0 deletions src/pages/Admin.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down