refactor: migrate shadcn-components to Svelte 5 and TW4 (#551)

Co-authored-by: Elias Schneider <login@eliasschneider.com>
This commit is contained in:
Kyle Mendell
2025-05-21 12:15:27 -05:00
committed by Elias Schneider
parent 05b443d984
commit 28c85990ba
197 changed files with 8142 additions and 7471 deletions

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import { page } from '$app/stores';
import { page } from '$app/state';
import Error from '$lib/components/error.svelte';
</script>
<Error message={$page.error!.message} />
<Error message={page.error!.message} />

View File

@@ -1,5 +1,6 @@
<script lang="ts">
import SignInWrapper from '$lib/components/login-wrapper.svelte';
import ScopeItem from '$lib/components/scope-item.svelte';
import { Button } from '$lib/components/ui/button';
import * as Card from '$lib/components/ui/card';
import { m } from '$lib/paraglide/messages';
@@ -8,25 +9,31 @@
import appConfigStore from '$lib/stores/application-configuration-store';
import userStore from '$lib/stores/user-store';
import { getWebauthnErrorMessage } from '$lib/utils/error-util';
import { LucideMail, LucideUser, LucideUsers } from '@lucide/svelte';
import { startAuthentication } from '@simplewebauthn/browser';
import { LucideMail, LucideUser, LucideUsers } from 'lucide-svelte';
import { onMount } from 'svelte';
import { slide } from 'svelte/transition';
import type { PageData } from './$types';
import ClientProviderImages from './components/client-provider-images.svelte';
import ScopeItem from '$lib/components/scope-item.svelte';
const webauthnService = new WebAuthnService();
const oidService = new OidcService();
let isLoading = false;
let success = false;
let errorMessage: string | null = null;
let authorizationRequired = false;
let authorizationConfirmed = false;
let {
scope,
nonce,
client,
authorizeState,
callbackURL,
codeChallenge,
codeChallengeMethod
}: PageData = $props();
export let data: PageData;
let { scope, nonce, client, state, callbackURL, codeChallenge, codeChallengeMethod } = data;
let isLoading = $state(false);
let success = $state(false);
let errorMessage: string | null = $state(null);
let authorizationRequired = $state(false);
let authorizationConfirmed = $state(false);
onMount(() => {
if ($userStore) {
@@ -40,7 +47,7 @@
// Get access token if not signed in
if (!$userStore?.id) {
const loginOptions = await webauthnService.getLoginOptions();
const authResponse = await startAuthentication({optionsJSON: loginOptions});
const authResponse = await startAuthentication({ optionsJSON: loginOptions });
const user = await webauthnService.finishLogin(authResponse);
userStore.setUser(user);
}
@@ -70,7 +77,7 @@
setTimeout(() => {
const redirectURL = new URL(callbackURL);
redirectURL.searchParams.append('code', code);
redirectURL.searchParams.append('state', state);
redirectURL.searchParams.append('state', authorizeState);
window.location.href = redirectURL.toString();
}, 1000);
@@ -84,10 +91,7 @@
{#if client == null}
<p>{m.client_not_found()}</p>
{:else}
<SignInWrapper
animate={!$appConfigStore.disableAnimations}
showAlternativeSignInMethodButton={$userStore == null}
>
<SignInWrapper showAlternativeSignInMethodButton={$userStore == null}>
<ClientProviderImages {client} {success} error={!!errorMessage} />
<h1 class="font-playfair mt-5 text-3xl font-bold sm:text-4xl">
{m.sign_in_to({ name: client.name })}
@@ -140,14 +144,18 @@
</Card.Root>
</div>
{/if}
<div class="flex w-full justify-stretch gap-2">
<Button onclick={() => history.back()} class="w-full" variant="secondary">{m.cancel()}</Button
>
{#if !errorMessage}
<Button class="w-full" {isLoading} on:click={authorize}>{m.sign_in()}</Button>
{:else}
<Button class="w-full" on:click={() => (errorMessage = null)}>{m.try_again()}</Button>
{/if}
<!-- Wrap the buttons in a container with the same width as in the login code page -->
<div class="w-full max-w-[450px]">
<div class="mt-8 flex justify-between gap-2">
<Button onclick={() => history.back()} class="flex-1" variant="secondary"
>{m.cancel()}</Button
>
{#if !errorMessage}
<Button class="flex-1" {isLoading} onclick={authorize}>{m.sign_in()}</Button>
{:else}
<Button class="flex-1" onclick={() => (errorMessage = null)}>{m.try_again()}</Button>
{/if}
</div>
</div>
</SignInWrapper>
{/if}

View File

@@ -10,7 +10,7 @@ export const load: PageLoad = async ({ url }) => {
return {
scope: url.searchParams.get('scope')!,
nonce: url.searchParams.get('nonce') || undefined,
state: url.searchParams.get('state')!,
authorizeState: url.searchParams.get('state')!,
callbackURL: url.searchParams.get('redirect_uri')!,
client,
codeChallenge: url.searchParams.get('code_challenge')!,

View File

@@ -35,7 +35,7 @@
? 'translate-x-[108px]'
: ''}"
>
<Logo class="h-10 w-10" />
<Logo class="size-10" />
</div>
<ConnectArrow
@@ -50,22 +50,22 @@
: ''} {animationDone ? (success ? 'bg-green-200' : 'bg-red-200') : 'bg-muted'}"
>
{#if animationDone && success}
<div class="flex h-10 w-10 items-center justify-center">
<CheckmarkAnimated class="h-7 w-7" />
<div class="flex size-10 items-center justify-center">
<CheckmarkAnimated class="size-7" />
</div>
{:else if animationDone && error}
<div class="flex h-10 w-10 items-center justify-center">
<CrossAnimated class="h-5 w-5" />
<div class="flex size-10 items-center justify-center">
<CrossAnimated class="size-5" />
</div>
{:else if client.hasLogo}
<img
class="h-10 w-10"
class="size-10"
src="/api/oidc/clients/{client.id}/logo"
draggable={false}
alt={m.client_logo()}
/>
{:else}
<div class="flex h-10 w-10 items-center justify-center text-3xl font-bold">
<div class="flex size-10 items-center justify-center text-3xl font-bold">
{client.name.charAt(0).toUpperCase()}
</div>
{/if}

View File

@@ -41,7 +41,7 @@
// Get access token if not signed in
if (!$userStore) {
const loginOptions = await webauthnService.getLoginOptions();
const authResponse = await startAuthentication({optionsJSON: loginOptions});
const authResponse = await startAuthentication({ optionsJSON: loginOptions });
const user = await webauthnService.finishLogin(authResponse);
userStore.setUser(user);
}
@@ -117,7 +117,7 @@
>{m.authorize()}</Button
>
{:else}
<Button class="w-full" on:click={() => (errorMessage = null)}>{m.try_again()}</Button>
<Button class="w-full" onclick={() => (errorMessage = null)}>{m.try_again()}</Button>
{/if}
</div>
{/if}

View File

@@ -20,7 +20,7 @@
isLoading = true;
try {
const loginOptions = await webauthnService.getLoginOptions();
const authResponse = await startAuthentication({optionsJSON: loginOptions});
const authResponse = await startAuthentication({ optionsJSON: loginOptions });
const user = await webauthnService.finishLogin(authResponse);
userStore.setUser(user);
@@ -52,7 +52,7 @@
{m.authenticate_yourself_with_your_passkey_to_access_the_admin_panel()}
</p>
{/if}
<Button class="mt-10" {isLoading} on:click={authenticate}
<Button class="mt-10" {isLoading} onclick={authenticate}
>{error ? m.try_again() : m.authenticate()}</Button
>
</SignInWrapper>

View File

@@ -6,7 +6,7 @@
import * as Card from '$lib/components/ui/card';
import { m } from '$lib/paraglide/messages';
import appConfigStore from '$lib/stores/application-configuration-store';
import { LucideChevronRight, LucideMail, LucideRectangleEllipsis } from 'lucide-svelte';
import { LucideChevronRight, LucideMail, LucideRectangleEllipsis } from '@lucide/svelte';
const methods = [
{
@@ -34,7 +34,7 @@
<SignInWrapper>
<div class="flex h-full flex-col justify-center">
<div class="bg-muted mx-auto rounded-2xl p-3">
<Logo class="h-10 w-10" />
<Logo class="size-10" />
</div>
<h1 class="font-playfair mt-5 text-3xl font-bold sm:text-4xl">{m.alternative_sign_in()}</h1>
<p class="text-muted-foreground mt-3">
@@ -44,15 +44,15 @@
{#each methods as method}
<a href={method.href + page.url.search}>
<Card.Root>
<Card.Content class="flex items-center justify-between p-4">
<Card.Content class="flex items-center justify-between px-4">
<div class="flex gap-3">
<method.icon class="text-primary h-7 w-7" />
<method.icon class="text-primary size-7" />
<div class="text-start">
<h3 class="text-lg font-semibold">{method.title}</h3>
<p class="text-muted-foreground text-sm">{method.description}</p>
</div>
</div>
<Button variant="ghost"><LucideChevronRight class="h-5 w-5" /></Button>
<Button variant="ghost"><LucideChevronRight class="size-5" /></Button>
</Card.Content>
</Card.Root>
</a>

View File

@@ -67,11 +67,11 @@
class="w-full max-w-[450px]"
>
<Input id="Email" class="mt-7" placeholder={m.code()} bind:value={code} type="text" />
<div class="mt-8 flex justify-stretch gap-2">
<Button variant="secondary" class="w-full" href={'/login/alternative' + page.url.search}
<div class="mt-8 flex justify-between gap-2">
<Button variant="secondary" class="flex-1" href={'/login/alternative' + page.url.search}
>{m.go_back()}</Button
>
<Button class="w-full" type="submit" {isLoading}>{m.submit()}</Button>
<Button class="flex-1" type="submit" {isLoading}>{m.submit()}</Button>
</div>
</form>
</SignInWrapper>

View File

@@ -41,19 +41,19 @@
<p class="text-muted-foreground mt-2" in:fade>
{error}. {m.please_try_again()}
</p>
<div class="mt-10 flex w-full justify-stretch gap-2">
<Button variant="secondary" class="w-full" href="/">{m.go_back()}</Button>
<Button class="w-full" onclick={() => (error = undefined)}>{m.try_again()}</Button>
<div class="mt-10 flex justify-between gap-2">
<Button variant="secondary" class="flex-1" href="/">{m.go_back()}</Button>
<Button class="flex-1" onclick={() => (error = undefined)}>{m.try_again()}</Button>
</div>
{:else if success}
<p class="text-muted-foreground mt-2" in:fade>
{m.an_email_has_been_sent_to_the_provided_email_if_it_exists_in_the_system()}
</p>
<div class="mt-8 flex w-full justify-stretch gap-2">
<Button variant="secondary" class="w-full" href={'/login/alternative' + page.url.search}
<div class="mt-8 flex justify-between gap-2">
<Button variant="secondary" class="flex-1" href={'/login/alternative' + page.url.search}
>{m.go_back()}</Button
>
<Button class="w-full" href={'/login/alternative/code' + page.url.search}
<Button class="flex-1" href={'/login/alternative/code' + page.url.search}
>{m.enter_code()}</Button
>
</div>
@@ -69,11 +69,11 @@
{m.enter_your_email_address_to_receive_an_email_with_a_login_code()}
</p>
<Input id="Email" class="mt-7" placeholder={m.your_email()} bind:value={email} />
<div class="mt-8 flex justify-stretch gap-2">
<Button variant="secondary" class="w-full" href={'/login/alternative' + page.url.search}
<div class="mt-8 flex justify-between gap-2">
<Button variant="secondary" class="flex-1" href={'/login/alternative' + page.url.search}
>{m.go_back()}</Button
>
<Button class="w-full" type="submit" {isLoading}>{m.submit()}</Button>
<Button class="flex-1" type="submit" {isLoading}>{m.submit()}</Button>
</div>
</form>
{/if}

View File

@@ -18,16 +18,16 @@
{error ? 'bg-red-200' : success ? 'bg-green-200' : 'bg-muted'}"
>
{#if error || success}
<div class="flex h-10 w-10 items-center justify-center">
<div class="flex size-10 items-center justify-center">
{#if error}
<CrossAnimated class="h-5 w-5" />
<CrossAnimated class="size-5" />
{:else}
<CheckmarkAnimated class="h-5 w-5" />
<CheckmarkAnimated class="size-5" />
{/if}
</div>
{:else}
<div in:fade={{ duration: 300 }}>
<Logo class="h-10 w-10" />
<Logo class="size-10" />
</div>
{/if}
</div>

View File

@@ -44,6 +44,6 @@
<p class="text-muted-foreground mt-2">
{m.you_are_about_to_sign_in_to_the_initial_admin_account()}
</p>
<Button class="mt-5" {isLoading} on:click={authenticate}>{m.continue()}</Button>
<Button class="mt-5" {isLoading} onclick={authenticate}>{m.continue()}</Button>
{/if}
</SignInWrapper>

View File

@@ -30,7 +30,7 @@
<SignInWrapper animate={!$appConfigStore.disableAnimations}>
<div class="flex justify-center">
<div class="bg-muted rounded-2xl p-3">
<Logo class="h-10 w-10" />
<Logo class="size-10" />
</div>
</div>
<h1 class="font-playfair mt-5 text-4xl font-bold">{m.sign_out()}</h1>

View File

@@ -5,7 +5,7 @@
import appConfigStore from '$lib/stores/application-configuration-store';
import userStore from '$lib/stores/user-store';
import { cn } from '$lib/utils/style';
import { LucideExternalLink, LucideSettings } from 'lucide-svelte';
import { LucideExternalLink, LucideSettings } from '@lucide/svelte';
import type { Snippet } from 'svelte';
import { fade, fly } from 'svelte/transition';
import type { LayoutData } from './$types';
@@ -41,14 +41,14 @@
<section>
<div class="bg-muted/40 flex min-h-[calc(100vh-64px)] w-full flex-col justify-between">
<main
in:fade={{ duration: 300 }}
in:fade={{ duration: 200 }}
class="mx-auto flex w-full max-w-[1640px] flex-col gap-x-8 gap-y-8 overflow-hidden p-4 md:p-8 lg:flex-row"
>
<div class="min-w-[200px] xl:min-w-[250px]">
<div in:fly={{ x: -15, duration: 300 }} class="sticky top-6">
<div in:fly={{ x: -15, duration: 200 }} class="sticky top-6">
<div class="mx-auto grid w-full gap-2">
<h1 class="mb-4 flex items-center gap-2 text-2xl font-semibold">
<LucideSettings class="h-5 w-5" />
<LucideSettings class="size-5" />
{m.settings()}
</h1>
</div>
@@ -62,7 +62,7 @@
? 'text-primary bg-card rounded-md px-3 py-1.5 font-medium shadow-sm transition-all'
: 'hover:text-foreground hover:bg-muted/70 rounded-md px-3 py-1.5 transition-all hover:-translate-y-[2px] hover:shadow-sm'
)}
style={`animation-delay: ${150 + i * 75}ms;`}
style={`animation-delay: ${150 + i * 50}ms;`}
>
{label}
</a>
@@ -75,7 +75,7 @@
style={`animation-delay: ${150 + links.length * 75}ms;`}
>
{m.update_pocket_id()}
<LucideExternalLink class="my-auto inline-block h-3 w-3" />
<LucideExternalLink class="my-auto inline-block size-3" />
</a>
{/if}
</nav>

View File

@@ -16,7 +16,7 @@
LucideAlertTriangle,
RectangleEllipsis,
UserCog
} from 'lucide-svelte';
} from '@lucide/svelte';
import { toast } from 'svelte-sonner';
import AccountForm from './account-form.svelte';
import LocalePicker from './locale-picker.svelte';
@@ -49,7 +49,7 @@
async function createPasskey() {
try {
const opts = await webauthnService.getRegistrationOptions();
const attResp = await startRegistration({optionsJSON: opts});
const attResp = await startRegistration({ optionsJSON: opts });
const passkey = await webauthnService.finishRegistration(attResp);
passkeys = await webauthnService.listCredentials();
@@ -75,7 +75,7 @@
</Alert.Description>
</div>
<div>
<Button class="mt-2 md:mt-0" on:click={createPasskey}>
<Button class="mt-2 md:mt-0" onclick={createPasskey}>
{m.add_passkey()}
</Button>
</div>
@@ -100,7 +100,7 @@
<div class="flex flex-col items-start justify-between gap-3 sm:flex-row sm:items-center">
<div>
<Card.Title>
<RectangleEllipsis class="text-primary/80 h-5 w-5" />
<RectangleEllipsis class="text-primary/80 size-5" />
{m.login_code()}
</Card.Title>
<Card.Description>
@@ -108,7 +108,7 @@
</Card.Description>
</div>
<Button variant="outline" class="w-full" on:click={() => (showLoginCodeModal = true)}>
<Button variant="outline" class="w-full" onclick={() => (showLoginCodeModal = true)}>
{m.create()}
</Button>
</div>
@@ -124,7 +124,7 @@
<Card.Root>
<Card.Header>
<Card.Title>
<UserCog class="text-primary/80 h-5 w-5" />
<UserCog class="text-primary/80 size-5" />
{m.account_details()}
</Card.Title>
</Card.Header>
@@ -146,14 +146,14 @@
<div class="flex items-center justify-between">
<div>
<Card.Title>
<KeyRound class="text-primary/80 h-5 w-5" />
<KeyRound class="text-primary/80 size-5" />
{m.passkeys()}
</Card.Title>
<Card.Description>
{m.manage_your_passkeys_that_you_can_use_to_authenticate_yourself()}
</Card.Description>
</div>
<Button variant="outline" class="ml-3" on:click={createPasskey}>
<Button variant="outline" class="ml-3" onclick={createPasskey}>
{m.add_passkey()}
</Button>
</div>
@@ -173,14 +173,14 @@
<div class="flex flex-col items-start justify-between gap-3 sm:flex-row sm:items-center">
<div>
<Card.Title>
<RectangleEllipsis class="text-primary/80 h-5 w-5" />
<RectangleEllipsis class="text-primary/80 size-5" />
{m.login_code()}
</Card.Title>
<Card.Description>
{m.create_a_one_time_login_code_to_sign_in_from_a_different_device_without_a_passkey()}
</Card.Description>
</div>
<Button variant="outline" on:click={() => (showLoginCodeModal = true)}>
<Button variant="outline" onclick={() => (showLoginCodeModal = true)}>
{m.create()}
</Button>
</div>
@@ -195,10 +195,9 @@
<div class="flex flex-col items-start justify-between gap-3 sm:flex-row sm:items-center">
<div>
<Card.Title>
<Languages class="text-primary/80 h-5 w-5" />
<Languages class="text-primary/80 size-5" />
{m.language()}
</Card.Title>
<Card.Description>
{m.select_the_language_you_want_to_use()}
</Card.Description>

View File

@@ -29,15 +29,9 @@
}
</script>
<Select.Root
selected={{
label: locales[currentLocale],
value: currentLocale
}}
onSelectedChange={(v) => updateLocale(v!.value)}
>
<Select.Root type="single" value={currentLocale} onValueChange={(v) => updateLocale(v as Locale)}>
<Select.Trigger class="h-9 max-w-[200px]" aria-label="Select locale">
<Select.Value>{locales[currentLocale]}</Select.Value>
{locales[currentLocale]}
</Select.Trigger>
<Select.Content>
{#each Object.entries(locales) as [value, label]}

View File

@@ -42,7 +42,7 @@
</script>
<Dialog.Root open={!!code} {onOpenChange}>
<Dialog.Content class="max-w-md">
<Dialog.Content class="max-w-md" onOpenAutoFocus={(e) => e.preventDefault()}>
<Dialog.Header>
<Dialog.Title>{m.login_code()}</Dialog.Title>
<Dialog.Description
@@ -64,8 +64,8 @@
class="mb-2"
value={loginCodeLink}
size={180}
color={$mode === 'dark' ? '#FFFFFF' : '#000000'}
backgroundColor={$mode === 'dark' ? '#000000' : '#FFFFFF'}
color={mode.current === 'dark' ? '#FFFFFF' : '#000000'}
backgroundColor={mode.current === 'dark' ? '#000000' : '#FFFFFF'}
/>
<CopyToClipboard value={loginCodeLink!}>
<p data-testId="login-code-link">{loginCodeLink!}</p>

View File

@@ -5,7 +5,7 @@
import WebauthnService from '$lib/services/webauthn-service';
import type { Passkey } from '$lib/types/passkey.type';
import { axiosErrorToast } from '$lib/utils/error-util';
import { LucideKeyRound } from 'lucide-svelte';
import { LucideKeyRound } from '@lucide/svelte';
import { toast } from 'svelte-sonner';
import RenamePasskeyModal from './rename-passkey-modal.svelte';

View File

@@ -5,7 +5,7 @@
import ApiKeyService from '$lib/services/api-key-service';
import type { ApiKeyCreate, ApiKeyResponse } from '$lib/types/api-key.type';
import { axiosErrorToast } from '$lib/utils/error-util';
import { LucideMinus, ShieldEllipsis, ShieldPlus } from 'lucide-svelte';
import { LucideMinus, ShieldEllipsis, ShieldPlus } from '@lucide/svelte';
import { slide } from 'svelte/transition';
import ApiKeyDialog from './api-key-dialog.svelte';
import ApiKeyForm from './api-key-form.svelte';
@@ -45,16 +45,16 @@
<div class="flex items-center justify-between">
<div>
<Card.Title>
<ShieldPlus class="text-primary/80 h-5 w-5" />
<ShieldPlus class="text-primary/80 size-5" />
{m.create_api_key()}
</Card.Title>
<Card.Description>{m.add_a_new_api_key_for_programmatic_access()}</Card.Description>
</div>
{#if !expandAddApiKey}
<Button on:click={() => (expandAddApiKey = true)}>{m.add_api_key()}</Button>
<Button onclick={() => (expandAddApiKey = true)}>{m.add_api_key()}</Button>
{:else}
<Button class="h-8 p-3" variant="ghost" on:click={() => (expandAddApiKey = false)}>
<LucideMinus class="h-5 w-5" />
<Button class="h-8 p-3" variant="ghost" onclick={() => (expandAddApiKey = false)}>
<LucideMinus class="size-5" />
</Button>
{/if}
</div>
@@ -73,7 +73,7 @@
<Card.Root>
<Card.Header>
<Card.Title>
<ShieldEllipsis class="text-primary/80 h-5 w-5" />
<ShieldEllipsis class="text-primary/80 size-5" />
{m.manage_api_keys()}
</Card.Title>
</Card.Header>

View File

@@ -19,7 +19,7 @@
</script>
<Dialog.Root open={!!apiKeyResponse} {onOpenChange}>
<Dialog.Content class="max-w-md" closeButton={false}>
<Dialog.Content class="max-w-md">
<Dialog.Header>
<Dialog.Title>{m.api_key_created()}</Dialog.Title>
<Dialog.Description>
@@ -45,7 +45,7 @@
</div>
{/if}
<Dialog.Footer class="mt-3">
<Button variant="default" on:click={() => onOpenChange(false)}>{m.close()}</Button>
<Button variant="default" onclick={() => onOpenChange(false)}>{m.close()}</Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog.Root>

View File

@@ -8,7 +8,7 @@
import type { ApiKey } from '$lib/types/api-key.type';
import type { Paginated, SearchPaginationSortRequest } from '$lib/types/pagination.type';
import { axiosErrorToast } from '$lib/utils/error-util';
import { LucideBan } from 'lucide-svelte';
import { LucideBan } from '@lucide/svelte';
import { toast } from 'svelte-sonner';
let {
@@ -68,11 +68,8 @@
<Table.Cell>{formatDate(item.expiresAt)}</Table.Cell>
<Table.Cell>{formatDate(item.lastUsedAt)}</Table.Cell>
<Table.Cell class="flex justify-end">
<Button
on:click={() => revokeApiKey(item)}
size="sm"
variant="outline"
aria-label={m.revoke()}><LucideBan class="h-3 w-3 text-red-500" /></Button
<Button onclick={() => revokeApiKey(item)} size="sm" variant="outline" aria-label={m.revoke()}
><LucideBan class="size-3 text-red-500" /></Button
>
</Table.Cell>
{/snippet}

View File

@@ -5,7 +5,7 @@
import appConfigStore from '$lib/stores/application-configuration-store';
import type { AllAppConfig } from '$lib/types/application-configuration';
import { axiosErrorToast } from '$lib/utils/error-util';
import { LucideImage, Mail, SlidersHorizontal, UserSearch } from 'lucide-svelte';
import { LucideImage, Mail, SlidersHorizontal, UserSearch } from '@lucide/svelte';
import { toast } from 'svelte-sonner';
import AppConfigEmailForm from './forms/app-config-email-form.svelte';
import AppConfigGeneralForm from './forms/app-config-general-form.svelte';

View File

@@ -106,11 +106,12 @@
<div class="grid gap-2">
<Label class="mb-0" for="smtp-tls">{m.smtp_tls_option()}</Label>
<Select.Root
selected={{ value: $inputs.smtpTls.value, label: tlsOptions[$inputs.smtpTls.value] }}
onSelectedChange={(v) => ($inputs.smtpTls.value = v!.value)}
type="single"
value={$inputs.smtpTls.value}
onValueChange={(v) => ($inputs.smtpTls.value = v as typeof $inputs.smtpTls.value)}
>
<Select.Trigger>
<Select.Value placeholder={m.email_tls_option()} />
<Select.Trigger class="w-full" placeholder={m.email_tls_option()}>
{tlsOptions[$inputs.smtpTls.value]}
</Select.Trigger>
<Select.Content>
<Select.Item value="none" label="None" />

View File

@@ -23,7 +23,7 @@
<div class="flex flex-col gap-8">
<ApplicationImage
id="favicon"
imageClass="h-14 w-14 p-2"
imageClass="size-14 p-2"
label={m.favicon()}
bind:image={favicon}
imageURL="/api/application-configuration/favicon"
@@ -31,7 +31,7 @@
/>
<ApplicationImage
id="logo-light"
imageClass="h-32 w-32"
imageClass="size-32"
label={m.light_mode_logo()}
bind:image={logoLight}
imageURL="/api/application-configuration/logo?light=true"
@@ -39,7 +39,7 @@
/>
<ApplicationImage
id="logo-dark"
imageClass="h-32 w-32"
imageClass="size-32"
label={m.dark_mode_logo()}
bind:image={logoDark}
imageURL="/api/application-configuration/logo?light=false"

View File

@@ -8,7 +8,7 @@
import clientSecretStore from '$lib/stores/client-secret-store';
import type { OidcClientCreateWithLogo } from '$lib/types/oidc.type';
import { axiosErrorToast } from '$lib/utils/error-util';
import { LucideMinus, ShieldCheck, ShieldPlus } from 'lucide-svelte';
import { LucideMinus, ShieldCheck, ShieldPlus } from '@lucide/svelte';
import { toast } from 'svelte-sonner';
import { slide } from 'svelte/transition';
import OIDCClientForm from './oidc-client-form.svelte';
@@ -49,7 +49,7 @@
<div class="flex items-center justify-between">
<div>
<Card.Title>
<ShieldPlus class="text-primary/80 h-5 w-5" />
<ShieldPlus class="text-primary/80 size-5" />
{m.create_oidc_client()}
</Card.Title>
<Card.Description
@@ -59,10 +59,10 @@
>
</div>
{#if !expandAddClient}
<Button on:click={() => (expandAddClient = true)}>{m.add_oidc_client()}</Button>
<Button onclick={() => (expandAddClient = true)}>{m.add_oidc_client()}</Button>
{:else}
<Button class="h-8 p-3" variant="ghost" on:click={() => (expandAddClient = false)}>
<LucideMinus class="h-5 w-5" />
<Button class="h-8 p-3" variant="ghost" onclick={() => (expandAddClient = false)}>
<LucideMinus class="size-5" />
</Button>
{/if}
</div>
@@ -81,7 +81,7 @@
<Card.Root>
<Card.Header>
<Card.Title>
<ShieldCheck class="text-primary/80 h-5 w-5" />
<ShieldCheck class="text-primary/80 size-5" />
{m.manage_oidc_clients()}
</Card.Title>
</Card.Header>

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import { beforeNavigate } from '$app/navigation';
import { page } from '$app/stores';
import { page } from '$app/state';
import CollapsibleCard from '$lib/components/collapsible-card.svelte';
import { openConfirmDialog } from '$lib/components/confirm-dialog';
import CopyToClipboard from '$lib/components/copy-to-clipboard.svelte';
@@ -12,7 +12,7 @@
import clientSecretStore from '$lib/stores/client-secret-store';
import type { OidcClientCreateWithLogo } from '$lib/types/oidc.type';
import { axiosErrorToast } from '$lib/utils/error-util';
import { LucideChevronLeft, LucideRefreshCcw } from 'lucide-svelte';
import { LucideChevronLeft, LucideRefreshCcw } from '@lucide/svelte';
import { toast } from 'svelte-sonner';
import { slide } from 'svelte/transition';
import OidcForm from '../oidc-client-form.svelte';
@@ -28,12 +28,12 @@
const oidcService = new OidcService();
const setupDetails = $state({
[m.authorization_url()]: `https://${$page.url.hostname}/authorize`,
[m.oidc_discovery_url()]: `https://${$page.url.hostname}/.well-known/openid-configuration`,
[m.token_url()]: `https://${$page.url.hostname}/api/oidc/token`,
[m.userinfo_url()]: `https://${$page.url.hostname}/api/oidc/userinfo`,
[m.logout_url()]: `https://${$page.url.hostname}/api/oidc/end-session`,
[m.certificate_url()]: `https://${$page.url.hostname}/.well-known/jwks.json`,
[m.authorization_url()]: `https://${page.url.hostname}/authorize`,
[m.oidc_discovery_url()]: `https://${page.url.hostname}/.well-known/openid-configuration`,
[m.token_url()]: `https://${page.url.hostname}/api/oidc/token`,
[m.userinfo_url()]: `https://${page.url.hostname}/api/oidc/userinfo`,
[m.logout_url()]: `https://${page.url.hostname}/api/oidc/end-session`,
[m.certificate_url()]: `https://${page.url.hostname}/.well-known/jwks.json`,
[m.pkce()]: client.pkceEnabled ? m.enabled() : m.disabled()
});
@@ -102,7 +102,7 @@
<div>
<a class="text-muted-foreground flex text-sm" href="/settings/admin/oidc-clients"
><LucideChevronLeft class="h-5 w-5" /> {m.back()}</a
><LucideChevronLeft class="size-5" /> {m.back()}</a
>
</div>
<Card.Root>
@@ -136,7 +136,7 @@
onclick={createClientSecret}
size="sm"
variant="ghost"
aria-label="Create new client secret"><LucideRefreshCcw class="h-3 w-3" /></Button
aria-label="Create new client secret"><LucideRefreshCcw class="size-3" /></Button
>
</div>
{/if}
@@ -157,7 +157,7 @@
{#if !showAllDetails}
<div class="mt-4 flex justify-center">
<Button on:click={() => (showAllDetails = true)} size="sm" variant="ghost"
<Button onclick={() => (showAllDetails = true)} size="sm" variant="ghost"
>{m.show_more_details()}</Button
>
</div>
@@ -177,6 +177,6 @@
>
<UserGroupSelection bind:selectedGroupIds={client.allowedUserGroupIds} />
<div class="mt-5 flex justify-end">
<Button on:click={() => updateUserGroupClients(client.allowedUserGroupIds)}>{m.save()}</Button>
<Button onclick={() => updateUserGroupClients(client.allowedUserGroupIds)}>{m.save()}</Button>
</div>
</CollapsibleCard>

View File

@@ -3,7 +3,7 @@
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import { m } from '$lib/paraglide/messages';
import { LucideMinus, LucidePlus } from 'lucide-svelte';
import { LucideMinus, LucidePlus } from '@lucide/svelte';
import type { Snippet } from 'svelte';
import type { HTMLAttributes } from 'svelte/elements';
@@ -27,14 +27,18 @@
<div class="flex flex-col gap-y-2">
{#each callbackURLs as _, i}
<div class="flex gap-x-2">
<Input data-testid={`callback-url-${i + 1}`} bind:value={callbackURLs[i]} />
<Input
aria-invalid={!!error}
data-testid={`callback-url-${i + 1}`}
bind:value={callbackURLs[i]}
/>
{#if callbackURLs.length > 1 || allowEmpty}
<Button
variant="outline"
size="sm"
on:click={() => (callbackURLs = callbackURLs.filter((_, index) => index !== i))}
onclick={() => (callbackURLs = callbackURLs.filter((_, index) => index !== i))}
>
<LucideMinus class="h-4 w-4" />
<LucideMinus class="size-4" />
</Button>
{/if}
</div>
@@ -42,15 +46,15 @@
</div>
</FormInput>
{#if error}
<p class="mt-1 text-sm text-red-500">{error}</p>
<p class="text-destructive mt-1 text-xs">{error}</p>
{/if}
<Button
class="mt-2"
variant="secondary"
size="sm"
on:click={() => (callbackURLs = [...callbackURLs, ''])}
onclick={() => (callbackURLs = [...callbackURLs, ''])}
>
<LucidePlus class="mr-1 h-4 w-4" />
<LucidePlus class="mr-1 size-4" />
{callbackURLs.length === 0 ? m.add() : m.add_another()}
</Button>
</div>

View File

@@ -112,7 +112,7 @@
<Label for="logo">{m.logo()}</Label>
<div class="mt-2 flex items-end gap-3">
{#if logoDataURL}
<div class="bg-muted h-32 w-32 rounded-2xl p-3">
<div class="bg-muted size-32 rounded-2xl p-3">
<img
class="m-auto max-h-full max-w-full object-contain"
src={logoDataURL}
@@ -132,7 +132,7 @@
</Button>
</FileInput>
{#if logoDataURL}
<Button variant="outline" on:click={resetLogo}>{m.remove_logo()}</Button>
<Button variant="outline" onclick={resetLogo}>{m.remove_logo()}</Button>
{/if}
</div>
</div>

View File

@@ -8,7 +8,7 @@
import type { OidcClient } from '$lib/types/oidc.type';
import type { Paginated, SearchPaginationSortRequest } from '$lib/types/pagination.type';
import { axiosErrorToast } from '$lib/utils/error-util';
import { LucidePencil, LucideTrash } from 'lucide-svelte';
import { LucidePencil, LucideTrash } from '@lucide/svelte';
import { toast } from 'svelte-sonner';
let {
@@ -56,7 +56,7 @@
<Table.Cell class="w-8 font-medium">
{#if item.hasLogo}
<div class="bg-secondary rounded-2xl p-3">
<div class="h-8 w-8">
<div class="size-8">
<img
class="m-auto max-h-full max-w-full object-contain"
src="/api/oidc/clients/{item.id}/logo"
@@ -72,13 +72,10 @@
href="/settings/admin/oidc-clients/{item.id}"
size="sm"
variant="outline"
aria-label={m.edit()}><LucidePencil class="h-3 w-3 " /></Button
aria-label={m.edit()}><LucidePencil class="size-3 " /></Button
>
<Button
on:click={() => deleteClient(item)}
size="sm"
variant="outline"
aria-label={m.delete()}><LucideTrash class="h-3 w-3 text-red-500" /></Button
<Button onclick={() => deleteClient(item)} size="sm" variant="outline" aria-label={m.delete()}
><LucideTrash class="size-3 text-red-500" /></Button
>
</Table.Cell>
{/snippet}

View File

@@ -6,7 +6,7 @@
import UserGroupService from '$lib/services/user-group-service';
import type { UserGroupCreate } from '$lib/types/user-group.type';
import { axiosErrorToast } from '$lib/utils/error-util';
import { LucideMinus, UserCog, UserPlus } from 'lucide-svelte';
import { LucideMinus, UserCog, UserPlus } from '@lucide/svelte';
import { toast } from 'svelte-sonner';
import { slide } from 'svelte/transition';
import UserGroupForm from './user-group-form.svelte';
@@ -45,17 +45,17 @@
<div class="flex items-center justify-between">
<div>
<Card.Title>
<UserPlus class="text-primary/80 h-5 w-5" />
<UserPlus class="text-primary/80 size-5" />
{m.create_user_group()}
</Card.Title>
<Card.Description>{m.create_a_new_group_that_can_be_assigned_to_users()}</Card.Description
>
</div>
{#if !expandAddUserGroup}
<Button on:click={() => (expandAddUserGroup = true)}>{m.add_group()}</Button>
<Button onclick={() => (expandAddUserGroup = true)}>{m.add_group()}</Button>
{:else}
<Button class="h-8 p-3" variant="ghost" on:click={() => (expandAddUserGroup = false)}>
<LucideMinus class="h-5 w-5" />
<Button class="h-8 p-3" variant="ghost" onclick={() => (expandAddUserGroup = false)}>
<LucideMinus class="size-5" />
</Button>
{/if}
</div>
@@ -74,7 +74,7 @@
<Card.Root>
<Card.Header>
<Card.Title>
<UserCog class="text-primary/80 h-5 w-5" />
<UserCog class="text-primary/80 size-5" />
{m.manage_user_groups()}
</Card.Title>
</Card.Header>

View File

@@ -9,7 +9,7 @@
import appConfigStore from '$lib/stores/application-configuration-store';
import type { UserGroupCreate } from '$lib/types/user-group.type';
import { axiosErrorToast } from '$lib/utils/error-util';
import { LucideChevronLeft } from 'lucide-svelte';
import { LucideChevronLeft } from '@lucide/svelte';
import { toast } from 'svelte-sonner';
import UserGroupForm from '../user-group-form.svelte';
import UserSelection from '../user-selection.svelte';
@@ -62,10 +62,10 @@
<div class="flex items-center justify-between">
<a class="text-muted-foreground flex text-sm" href="/settings/admin/user-groups"
><LucideChevronLeft class="h-5 w-5" /> {m.back()}</a
><LucideChevronLeft class="size-5" /> {m.back()}</a
>
{#if !!userGroup.ldapId}
<Badge variant="default" class="">{m.ldap()}</Badge>
<Badge class="rounded-full" variant="default">{m.ldap()}</Badge>
{/if}
</div>
<Card.Root>
@@ -92,7 +92,7 @@
<div class="mt-5 flex justify-end">
<Button
disabled={!!userGroup.ldapId && $appConfigStore.ldapEnabled}
on:click={() => updateUserGroupUsers(userGroup.userIds)}>{m.save()}</Button
onclick={() => updateUserGroupUsers(userGroup.userIds)}>{m.save()}</Button
>
</div>
</Card.Content>

View File

@@ -1,4 +1,5 @@
<script lang="ts">
import { goto } from '$app/navigation';
import AdvancedTable from '$lib/components/advanced-table.svelte';
import { openConfirmDialog } from '$lib/components/confirm-dialog/';
import { Badge } from '$lib/components/ui/badge/index';
@@ -11,8 +12,8 @@
import type { Paginated, SearchPaginationSortRequest } from '$lib/types/pagination.type';
import type { UserGroup, UserGroupWithUserCount } from '$lib/types/user-group.type';
import { axiosErrorToast } from '$lib/utils/error-util';
import { LucidePencil, LucideTrash } from 'lucide-svelte';
import Ellipsis from 'lucide-svelte/icons/ellipsis';
import { LucidePencil, LucideTrash } from '@lucide/svelte';
import Ellipsis from '@lucide/svelte/icons/ellipsis';
import { toast } from 'svelte-sonner';
let {
@@ -64,28 +65,28 @@
<Table.Cell>{item.userCount}</Table.Cell>
{#if $appConfigStore.ldapEnabled}
<Table.Cell>
<Badge variant={item.ldapId ? 'default' : 'outline'}
<Badge class="rounded-full" variant={item.ldapId ? 'default' : 'outline'}
>{item.ldapId ? m.ldap() : m.local()}</Badge
>
</Table.Cell>
{/if}
<Table.Cell class="flex justify-end">
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild let:builder>
<Button aria-haspopup="true" size="icon" variant="ghost" builders={[builder]}>
<Ellipsis class="h-4 w-4" />
<DropdownMenu.Trigger>
<Button aria-haspopup="true" size="icon" variant="ghost">
<Ellipsis class="size-4" />
<span class="sr-only">{m.toggle_menu()}</span>
</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content align="end">
<DropdownMenu.Item href="/settings/admin/user-groups/{item.id}"
><LucidePencil class="mr-2 h-4 w-4" /> {m.edit()}</DropdownMenu.Item
<DropdownMenu.Item onclick={() => goto(`/settings/admin/user-groups/${item.id}}`)}
><LucidePencil class="mr-2 size-4" /> {m.edit()}</DropdownMenu.Item
>
{#if !item.ldapId || !$appConfigStore.ldapEnabled}
<DropdownMenu.Item
class="text-red-500 focus:!text-red-700"
on:click={() => deleteUserGroup(item)}
><LucideTrash class="mr-2 h-4 w-4" />{m.delete()}</DropdownMenu.Item
onclick={() => deleteUserGroup(item)}
><LucideTrash class="mr-2 size-4" />{m.delete()}</DropdownMenu.Item
>
{/if}
</DropdownMenu.Content>

View File

@@ -6,7 +6,7 @@
import appConfigStore from '$lib/stores/application-configuration-store';
import type { UserCreate } from '$lib/types/user.type';
import { axiosErrorToast } from '$lib/utils/error-util';
import { LucideMinus, UserPen, UserPlus } from 'lucide-svelte';
import { LucideMinus, UserPen, UserPlus } from '@lucide/svelte';
import { toast } from 'svelte-sonner';
import { slide } from 'svelte/transition';
import UserForm from './user-form.svelte';
@@ -45,7 +45,7 @@
<div class="flex items-center justify-between">
<div>
<Card.Title>
<UserPlus class="text-primary/80 h-5 w-5" />
<UserPlus class="text-primary/80 size-5" />
{m.create_user()}
</Card.Title>
<Card.Description
@@ -55,10 +55,10 @@
>
</div>
{#if !expandAddUser}
<Button on:click={() => (expandAddUser = true)}>{m.add_user()}</Button>
<Button onclick={() => (expandAddUser = true)}>{m.add_user()}</Button>
{:else}
<Button class="h-8 p-3" variant="ghost" on:click={() => (expandAddUser = false)}>
<LucideMinus class="h-5 w-5" />
<Button class="h-8 p-3" variant="ghost" onclick={() => (expandAddUser = false)}>
<LucideMinus class="size-5" />
</Button>
{/if}
</div>
@@ -77,7 +77,7 @@
<Card.Root>
<Card.Header>
<Card.Title>
<UserPen class="text-primary/80 h-5 w-5" />
<UserPen class="text-primary/80 size-5" />
{m.manage_users()}
</Card.Title>
</Card.Header>

View File

@@ -12,7 +12,7 @@
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 { LucideChevronLeft } from '@lucide/svelte';
import { toast } from 'svelte-sonner';
import UserForm from '../user-form.svelte';
@@ -82,10 +82,10 @@
<div class="flex items-center justify-between">
<a class="text-muted-foreground flex text-sm" href="/settings/admin/users"
><LucideChevronLeft class="h-5 w-5" /> {m.back()}</a
><LucideChevronLeft class="size-5" /> {m.back()}</a
>
{#if !!user.ldapId}
<Badge variant="default" class="">{m.ldap()}</Badge>
<Badge class="rounded-full" variant="default">{m.ldap()}</Badge>
{/if}
</div>
<Card.Root>
@@ -119,7 +119,7 @@
/>
<div class="mt-5 flex justify-end">
<Button
on:click={() => updateUserGroups(user.userGroupIds)}
onclick={() => updateUserGroups(user.userGroupIds)}
disabled={!!user.ldapId && $appConfigStore.ldapEnabled}
type="submit">{m.save()}</Button
>
@@ -133,6 +133,6 @@
>
<CustomClaimsInput bind:customClaims={user.customClaims} />
<div class="mt-5 flex justify-end">
<Button on:click={updateCustomClaims} type="submit">{m.save()}</Button>
<Button onclick={updateCustomClaims} type="submit">{m.save()}</Button>
</div>
</CollapsibleCard>

View File

@@ -19,8 +19,8 @@
LucideTrash,
LucideUserCheck,
LucideUserX
} from 'lucide-svelte';
import Ellipsis from 'lucide-svelte/icons/ellipsis';
} from '@lucide/svelte';
import Ellipsis from '@lucide/svelte/icons/ellipsis';
import { toast } from 'svelte-sonner';
let {
@@ -116,16 +116,16 @@
<Table.Cell>{item.email}</Table.Cell>
<Table.Cell>{item.username}</Table.Cell>
<Table.Cell>
<Badge variant="outline">{item.isAdmin ? m.admin() : m.user()}</Badge>
<Badge class="rounded-full" variant="outline">{item.isAdmin ? m.admin() : m.user()}</Badge>
</Table.Cell>
<Table.Cell>
<Badge variant={item.disabled ? 'destructive' : 'default'}>
<Badge class="rounded-full" variant={item.disabled ? 'destructive' : 'default'}>
{item.disabled ? m.disabled() : m.enabled()}
</Badge>
</Table.Cell>
{#if $appConfigStore.ldapEnabled}
<Table.Cell>
<Badge variant={item.ldapId ? 'default' : 'outline'}
<Badge class="rounded-full" variant={item.ldapId ? 'default' : 'outline'}
>{item.ldapId ? m.ldap() : m.local()}</Badge
>
</Table.Cell>
@@ -133,24 +133,24 @@
<Table.Cell>
<DropdownMenu.Root>
<DropdownMenu.Trigger class={buttonVariants({ variant: 'ghost', size: 'icon' })}>
<Ellipsis class="h-4 w-4" />
<Ellipsis class="size-4" />
<span class="sr-only">{m.toggle_menu()}</span>
</DropdownMenu.Trigger>
<DropdownMenu.Content align="end">
<DropdownMenu.Item onclick={() => (userIdToCreateOneTimeLink = item.id)}
><LucideLink class="mr-2 h-4 w-4" />{m.login_code()}</DropdownMenu.Item
><LucideLink class="mr-2 size-4" />{m.login_code()}</DropdownMenu.Item
>
<DropdownMenu.Item onclick={() => goto(`/settings/admin/users/${item.id}`)}
><LucidePencil class="mr-2 h-4 w-4" /> {m.edit()}</DropdownMenu.Item
><LucidePencil class="mr-2 size-4" /> {m.edit()}</DropdownMenu.Item
>
{#if !item.ldapId || !$appConfigStore.ldapEnabled}
{#if item.disabled}
<DropdownMenu.Item onclick={() => enableUser(item)}
><LucideUserCheck class="mr-2 h-4 w-4" />{m.enable()}</DropdownMenu.Item
><LucideUserCheck class="mr-2 size-4" />{m.enable()}</DropdownMenu.Item
>
{:else}
<DropdownMenu.Item onclick={() => disableUser(item)}
><LucideUserX class="mr-2 h-4 w-4" />{m.disable()}</DropdownMenu.Item
><LucideUserX class="mr-2 size-4" />{m.disable()}</DropdownMenu.Item
>
{/if}
{/if}
@@ -158,7 +158,7 @@
<DropdownMenu.Item
class="text-red-500 focus:!text-red-700"
onclick={() => deleteUser(item)}
><LucideTrash class="mr-2 h-4 w-4" />{m.delete()}</DropdownMenu.Item
><LucideTrash class="mr-2 size-4" />{m.delete()}</DropdownMenu.Item
>
{/if}
</DropdownMenu.Content>

View File

@@ -3,7 +3,7 @@
import * as Card from '$lib/components/ui/card';
import { m } from '$lib/paraglide/messages';
import userStore from '$lib/stores/user-store';
import { LogsIcon } from 'lucide-svelte';
import { LogsIcon } from '@lucide/svelte';
import AuditLogSwitcher from './audit-log-switcher.svelte';
let { data } = $props();
@@ -22,7 +22,7 @@
<Card.Root>
<Card.Header>
<Card.Title>
<LogsIcon class="text-primary/80 h-5 w-5" />
<LogsIcon class="text-primary/80 size-5" />
{m.audit_log()}
</Card.Title>
<Card.Description>{m.see_your_account_activities_from_the_last_3_months()}</Card.Description>

View File

@@ -50,9 +50,9 @@
<div class="mb-6 grid grid-cols-1 gap-4 md:grid-cols-3">
<div>
{#await auditLogService.listUsers()}
<Select.Root>
<Select.Root type="single">
<Select.Trigger class="w-full" disabled>
<Select.Value placeholder={m.all_users()} />
{m.all_users()}
</Select.Trigger>
</Select.Root>
{:then users}
@@ -70,29 +70,27 @@
{/await}
</div>
<div>
<Select.Root
selected={{
value: filters.event,
label: eventTypes[filters.event as keyof typeof eventTypes]
}}
onSelectedChange={(v) => (filters.event = v!.value)}
>
<Select.Trigger class="w-full">
<Select.Value placeholder={m.all_events()} />
</Select.Trigger>
<Select.Content>
<Select.Item value="">{m.all_events()}</Select.Item>
{#each Object.entries(eventTypes) as [value, label]}
<Select.Item {value}>{label}</Select.Item>
{/each}
</Select.Content>
</Select.Root>
<SearchableSelect
class="w-full"
items={[
{ value: '', label: m.all_events() },
...Object.entries(eventTypes).map(([value, label]) => ({
value,
label
}))
]}
bind:value={filters.event}
/>
</div>
<div>
{#await auditLogService.listClientNames()}
<Select.Root>
<Select.Root
type="single"
value={filters.clientName}
onValueChange={(v) => (filters.clientName = v)}
>
<Select.Trigger class="w-full" disabled>
<Select.Value placeholder={m.all_clients()} />
{m.all_clients()}
</Select.Trigger>
</Select.Root>
{:then clientNames}