feat: display groups on the account page (#296)

Co-authored-by: Elias Schneider <login@eliasschneider.com>
This commit is contained in:
Kyle Mendell
2025-03-06 15:25:03 -06:00
committed by GitHub
parent 37b24bed91
commit 0f14a93e1d
14 changed files with 223 additions and 22 deletions

View File

@@ -2,7 +2,6 @@
import AdvancedTable from '$lib/components/advanced-table.svelte';
import * as Table from '$lib/components/ui/table';
import UserGroupService from '$lib/services/user-group-service';
import type { OidcClient } from '$lib/types/oidc.type';
import type { Paginated } from '$lib/types/pagination.type';
import type { UserGroup } from '$lib/types/user-group.type';

View File

@@ -1,4 +1,5 @@
import type { Paginated, SearchPaginationSortRequest } from '$lib/types/pagination.type';
import type { UserGroup } from '$lib/types/user-group.type';
import type { User, UserCreate } from '$lib/types/user.type';
import APIService from './api-service';
@@ -25,6 +26,11 @@ export default class UserService extends APIService {
return res.data as User;
}
async getUserGroups(userId: string) {
const res = await this.api.get(`/users/${userId}/groups`);
return res.data as UserGroup[];
}
async update(id: string, user: UserCreate) {
const res = await this.api.put(`/users/${id}`, user);
return res.data as User;
@@ -69,4 +75,9 @@ export default class UserService extends APIService {
async requestOneTimeAccessEmail(email: string, redirectPath?: string) {
await this.api.post('/one-time-access-email', { email, redirectPath });
}
async updateUserGroups(id: string, userGroupIds: string[]) {
const res = await this.api.put(`/users/${id}/user-groups`, { userGroupIds });
return res.data as User;
}
}

View File

@@ -1,4 +1,5 @@
import type { CustomClaim } from './custom-claim.type';
import type { UserGroup } from './user-group.type';
export type User = {
id: string;
@@ -7,6 +8,7 @@ export type User = {
firstName: string;
lastName: string;
isAdmin: boolean;
userGroups: UserGroup[];
customClaims: CustomClaim[];
ldapId?: string;
};

View File

@@ -1,11 +1,13 @@
<script lang="ts">
import { beforeNavigate } from '$app/navigation';
import { page } from '$app/stores';
import CollapsibleCard from '$lib/components/collapsible-card.svelte';
import { openConfirmDialog } from '$lib/components/confirm-dialog';
import CopyToClipboard from '$lib/components/copy-to-clipboard.svelte';
import { Button } from '$lib/components/ui/button';
import * as Card from '$lib/components/ui/card';
import Label from '$lib/components/ui/label/label.svelte';
import UserGroupSelection from '$lib/components/user-group-selection.svelte';
import OidcService from '$lib/services/oidc-service';
import UserGroupService from '$lib/services/user-group-service';
import clientSecretStore from '$lib/stores/client-secret-store';
@@ -15,8 +17,6 @@
import { toast } from 'svelte-sonner';
import { slide } from 'svelte/transition';
import OidcForm from '../oidc-client-form.svelte';
import UserGroupSelection from '../user-group-selection.svelte';
import CollapsibleCard from '$lib/components/collapsible-card.svelte';
let { data } = $props();
let client = $state({

View File

@@ -5,5 +5,8 @@ import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ params, cookies }) => {
const userService = new UserService(cookies.get(ACCESS_TOKEN_COOKIE_NAME));
const user = await userService.get(params.id);
return user;
return {
user
};
};

View File

@@ -6,18 +6,34 @@
import { Button } from '$lib/components/ui/button';
import * as Card from '$lib/components/ui/card';
import CustomClaimService from '$lib/services/custom-claim-service';
import UserGroupService from '$lib/services/user-group-service';
import UserService from '$lib/services/user-service';
import appConfigStore from '$lib/stores/application-configuration-store';
import type { UserCreate } from '$lib/types/user.type';
import { axiosErrorToast } from '$lib/utils/error-util';
import { LucideChevronLeft } from 'lucide-svelte';
import { toast } from 'svelte-sonner';
import UserGroupSelection from '$lib/components/user-group-selection.svelte';
import UserForm from '../user-form.svelte';
let { data } = $props();
let user = $state(data);
let user = $state({
...data.user,
userGroupIds: data.user.userGroups.map((g) => g.id)
});
const userService = new UserService();
const customClaimService = new CustomClaimService();
const userGroupService = new UserGroupService();
async function updateUserGroups(userIds: string[]) {
await userService
.updateUserGroups(user.id, userIds)
.then(() => toast.success('User groups updated successfully'))
.catch((e) => {
axiosErrorToast(e);
});
}
async function updateUser(updatedUser: UserCreate) {
let success = true;
@@ -80,6 +96,28 @@
</Card.Content>
</Card.Root>
<CollapsibleCard
id="user-groups"
title="User Groups"
description="Manage which groups this user belongs to."
>
{#await userGroupService.list() then groups}
<UserGroupSelection
{groups}
bind:selectedGroupIds={user.userGroupIds}
selectionDisabled={!!user.ldapId && $appConfigStore.ldapEnabled}
/>
{/await}
<div class="mt-5 flex justify-end">
<Button
on:click={() => updateUserGroups(user.userGroupIds)}
disabled={!!user.ldapId && $appConfigStore.ldapEnabled}
type="submit">Save</Button
>
</div>
</CollapsibleCard>
<CollapsibleCard
id="user-custom-claims"
title="Custom Claims"
@@ -87,6 +125,6 @@
>
<CustomClaimsInput bind:customClaims={user.customClaims} />
<div class="mt-5 flex justify-end">
<Button onclick={updateCustomClaims} type="submit">Save</Button>
<Button on:click={updateCustomClaims} type="submit">Save</Button>
</div>
</CollapsibleCard>