mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-03-31 03:36:36 +00:00
refactor: migrate shadcn-components to Svelte 5 and TW4 (#551)
Co-authored-by: Elias Schneider <login@eliasschneider.com>
This commit is contained in:
committed by
Elias Schneider
parent
05b443d984
commit
28c85990ba
@@ -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} />
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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')!,
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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]}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user