mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-03-29 10:46:38 +00:00
refactor: update forms and other areas to use new shadcn components (#1115)
Co-authored-by: Elias Schneider <login@eliasschneider.com> Co-authored-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import FormattedMessage from '$lib/components/formatted-message.svelte';
|
||||
import SignInWrapper from '$lib/components/login-wrapper.svelte';
|
||||
import ScopeItem from '$lib/components/scope-item.svelte';
|
||||
import ScopeList from '$lib/components/scope-list.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
@@ -10,7 +10,6 @@
|
||||
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, type AuthenticationResponseJSON } from '@simplewebauthn/browser';
|
||||
import { onMount } from 'svelte';
|
||||
import { slide } from 'svelte/transition';
|
||||
@@ -140,30 +139,8 @@
|
||||
/>
|
||||
</p>
|
||||
</Card.Header>
|
||||
<Card.Content data-testid="scopes">
|
||||
<div class="flex flex-col gap-3">
|
||||
{#if scope!.includes('email')}
|
||||
<ScopeItem
|
||||
icon={LucideMail}
|
||||
name={m.email()}
|
||||
description={m.view_your_email_address()}
|
||||
/>
|
||||
{/if}
|
||||
{#if scope!.includes('profile')}
|
||||
<ScopeItem
|
||||
icon={LucideUser}
|
||||
name={m.profile()}
|
||||
description={m.view_your_profile_information()}
|
||||
/>
|
||||
{/if}
|
||||
{#if scope!.includes('groups')}
|
||||
<ScopeItem
|
||||
icon={LucideUsers}
|
||||
name={m.groups()}
|
||||
description={m.view_the_groups_you_are_a_member_of()}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
<Card.Content>
|
||||
<ScopeList {scope} />
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
</div>
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
import { page } from '$app/state';
|
||||
import SignInWrapper from '$lib/components/login-wrapper.svelte';
|
||||
import Logo from '$lib/components/logo.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import * as Item from '$lib/components/ui/item/index.js';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
import appConfigStore from '$lib/stores/application-configuration-store';
|
||||
import { LucideChevronRight, LucideMail, LucideRectangleEllipsis } from '@lucide/svelte';
|
||||
@@ -40,24 +39,26 @@
|
||||
<p class="text-muted-foreground mt-3">
|
||||
{m.if_you_do_not_have_access_to_your_passkey_you_can_sign_in_using_one_of_the_following_methods()}
|
||||
</p>
|
||||
<div class="mt-5 flex flex-col gap-3">
|
||||
<Item.Group class="mt-5 gap-3">
|
||||
{#each methods as method}
|
||||
<a href={method.href + page.url.search}>
|
||||
<Card.Root>
|
||||
<Card.Content class="flex items-center justify-between px-4">
|
||||
<div class="flex gap-3">
|
||||
<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="size-5" /></Button>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
</a>
|
||||
<Item.Root variant="outline" class="gap-5">
|
||||
{#snippet child({ props })}
|
||||
<a href={method.href + page.url.search} {...props}>
|
||||
<Item.Media class="text-primary !self-center !translate-y-0">
|
||||
<method.icon class="size-7" />
|
||||
</Item.Media>
|
||||
<Item.Content class="text-start">
|
||||
<Item.Title class="text-lg font-semibold">{method.title}</Item.Title>
|
||||
<Item.Description>{method.description}</Item.Description>
|
||||
</Item.Content>
|
||||
<Item.Actions>
|
||||
<LucideChevronRight class="size-5" />
|
||||
</Item.Actions>
|
||||
</a>
|
||||
{/snippet}
|
||||
</Item.Root>
|
||||
{/each}
|
||||
</div>
|
||||
</Item.Group>
|
||||
|
||||
<a class="text-muted-foreground mt-5 text-xs" href={'/login' + page.url.search}
|
||||
>{m.use_your_passkey_instead()}</a
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import * as Alert from '$lib/components/ui/alert';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import * as Item from '$lib/components/ui/item/index.js';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
import UserService from '$lib/services/user-service';
|
||||
import WebAuthnService from '$lib/services/webauthn-service';
|
||||
@@ -100,28 +101,24 @@
|
||||
|
||||
<!-- Login code card mobile -->
|
||||
<div class="block sm:hidden">
|
||||
<Card.Root>
|
||||
<Card.Header>
|
||||
<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 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" class="w-full" onclick={() => (showLoginCodeModal = true)}>
|
||||
{m.create()}
|
||||
</Button>
|
||||
</div>
|
||||
</Card.Header>
|
||||
</Card.Root>
|
||||
<Item.Root variant="outline">
|
||||
<Item.Media class="text-primary/80">
|
||||
<RectangleEllipsis class="size-5" />
|
||||
</Item.Media>
|
||||
<Item.Content>
|
||||
<Item.Title>{m.login_code()}</Item.Title>
|
||||
<Item.Description>
|
||||
{m.create_a_one_time_login_code_to_sign_in_from_a_different_device_without_a_passkey()}
|
||||
</Item.Description>
|
||||
</Item.Content>
|
||||
<Item.Actions class="w-full sm:w-auto">
|
||||
<Button variant="outline" class="w-full" onclick={() => (showLoginCodeModal = true)}>
|
||||
{m.create()}
|
||||
</Button>
|
||||
</Item.Actions>
|
||||
</Item.Root>
|
||||
</div>
|
||||
|
||||
<!-- Account details card -->
|
||||
<Card.Root>
|
||||
<Card.Header>
|
||||
<Card.Title>
|
||||
@@ -140,76 +137,63 @@
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<!-- Passkey management card -->
|
||||
<div>
|
||||
<Card.Root class="gap-3">
|
||||
<Card.Header>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<Card.Title>
|
||||
<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" onclick={createPasskey}>
|
||||
{m.add_passkey()}
|
||||
</Button>
|
||||
</div>
|
||||
</Card.Header>
|
||||
{#if passkeys.length != 0}
|
||||
<Card.Content>
|
||||
<PasskeyList bind:passkeys />
|
||||
</Card.Content>
|
||||
{/if}
|
||||
</Card.Root>
|
||||
</div>
|
||||
<Item.Group class="bg-card rounded-xl border p-4 shadow-sm">
|
||||
<Item.Root class="border-none bg-transparent p-0">
|
||||
<Item.Media class="text-primary/80">
|
||||
<KeyRound class="size-5" />
|
||||
</Item.Media>
|
||||
<Item.Content class="min-w-52">
|
||||
<Item.Title class="text-xl font-semibold">{m.passkeys()}</Item.Title>
|
||||
<Item.Description>
|
||||
{m.manage_your_passkeys_that_you_can_use_to_authenticate_yourself()}
|
||||
</Item.Description>
|
||||
</Item.Content>
|
||||
<Item.Actions>
|
||||
<Button variant="outline" onclick={createPasskey}>
|
||||
{m.add_passkey()}
|
||||
</Button>
|
||||
</Item.Actions>
|
||||
</Item.Root>
|
||||
{#if passkeys.length != 0}
|
||||
<PasskeyList bind:passkeys />
|
||||
{/if}
|
||||
</Item.Group>
|
||||
|
||||
<!-- Login code card -->
|
||||
<div class="hidden sm:block">
|
||||
<Card.Root>
|
||||
<Card.Header>
|
||||
<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 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" onclick={() => (showLoginCodeModal = true)}>
|
||||
{m.create()}
|
||||
</Button>
|
||||
</div>
|
||||
</Card.Header>
|
||||
</Card.Root>
|
||||
<Item.Root variant="card" class="border-border">
|
||||
<Item.Media class="text-primary/80">
|
||||
<RectangleEllipsis class="size-5" />
|
||||
</Item.Media>
|
||||
<Item.Content>
|
||||
<Item.Title>{m.login_code()}</Item.Title>
|
||||
<Item.Description>
|
||||
{m.create_a_one_time_login_code_to_sign_in_from_a_different_device_without_a_passkey()}
|
||||
</Item.Description>
|
||||
</Item.Content>
|
||||
<Item.Actions>
|
||||
<Button variant="outline" onclick={() => (showLoginCodeModal = true)}>
|
||||
{m.create()}
|
||||
</Button>
|
||||
</Item.Actions>
|
||||
</Item.Root>
|
||||
</div>
|
||||
|
||||
<!-- Language selection card -->
|
||||
<div>
|
||||
<Card.Root>
|
||||
<Card.Header>
|
||||
<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 size-5" />
|
||||
{m.language()}
|
||||
</Card.Title>
|
||||
<Card.Description>
|
||||
{m.select_the_language_you_want_to_use()}
|
||||
<br />
|
||||
<FormattedMessage m={m.contribute_to_translation()} />
|
||||
</Card.Description>
|
||||
</div>
|
||||
<LocalePicker />
|
||||
</div>
|
||||
</Card.Header>
|
||||
</Card.Root>
|
||||
</div>
|
||||
<Item.Root variant="card" class="border-border mb-2">
|
||||
<Item.Media class="text-primary/80">
|
||||
<Languages class="size-5" />
|
||||
</Item.Media>
|
||||
<Item.Content class="min-w-52">
|
||||
<Item.Title>{m.language()}</Item.Title>
|
||||
<Item.Description>
|
||||
{m.select_the_language_you_want_to_use()}
|
||||
<br />
|
||||
<FormattedMessage m={m.contribute_to_translation()} />
|
||||
</Item.Description>
|
||||
</Item.Content>
|
||||
<Item.Actions>
|
||||
<LocalePicker />
|
||||
</Item.Actions>
|
||||
</Item.Root>
|
||||
|
||||
<RenamePasskeyModal
|
||||
bind:passkey={passkeyToRename}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import FormInput from '$lib/components/form/form-input.svelte';
|
||||
import ProfilePictureSettings from '$lib/components/form/profile-picture-settings.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as Field from '$lib/components/ui/field/index.js';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
import UserService from '$lib/services/user-service';
|
||||
import appConfigStore from '$lib/stores/application-configuration-store';
|
||||
@@ -86,30 +87,20 @@
|
||||
resetCallback={resetProfilePicture}
|
||||
/>
|
||||
|
||||
<hr class="border-border" />
|
||||
<Field.Separator class="m-2" />
|
||||
|
||||
<fieldset disabled={userInfoInputDisabled}>
|
||||
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
<div>
|
||||
<FormInput label={m.first_name()} bind:input={$inputs.firstName} onInput={onNameInput} />
|
||||
</div>
|
||||
<div>
|
||||
<FormInput label={m.last_name()} bind:input={$inputs.lastName} onInput={onNameInput} />
|
||||
</div>
|
||||
<div>
|
||||
<FormInput
|
||||
label={m.display_name()}
|
||||
bind:input={$inputs.displayName}
|
||||
onInput={() => (hasManualDisplayNameEdit = true)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<FormInput label={m.username()} bind:input={$inputs.username} />
|
||||
</div>
|
||||
<div>
|
||||
<FormInput label={m.email()} bind:input={$inputs.email} />
|
||||
</div>
|
||||
</div>
|
||||
<Field.Group class="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<FormInput label={m.first_name()} bind:input={$inputs.firstName} onInput={onNameInput} />
|
||||
<FormInput label={m.last_name()} bind:input={$inputs.lastName} onInput={onNameInput} />
|
||||
<FormInput
|
||||
label={m.display_name()}
|
||||
bind:input={$inputs.displayName}
|
||||
onInput={() => (hasManualDisplayNameEdit = true)}
|
||||
/>
|
||||
<FormInput label={m.username()} bind:input={$inputs.username} />
|
||||
<FormInput label={m.email()} type="email" bind:input={$inputs.email} />
|
||||
</Field.Group>
|
||||
|
||||
<div class="flex justify-end pt-4">
|
||||
<Button {isLoading} type="submit">{m.save()}</Button>
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
</script>
|
||||
|
||||
<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.Trigger class="max-w-[200px]" aria-label="Select locale">
|
||||
{locales[currentLocale]}
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { openConfirmDialog } from '$lib/components/confirm-dialog/';
|
||||
import GlassRowItem from '$lib/components/passkey-row.svelte';
|
||||
import * as Item from '$lib/components/ui/item/index.js';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
import WebauthnService from '$lib/services/webauthn-service';
|
||||
import type { Passkey } from '$lib/types/passkey.type';
|
||||
@@ -36,7 +37,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="space-y-3">
|
||||
<Item.Group class="mt-3">
|
||||
{#each passkeys as passkey}
|
||||
<GlassRowItem
|
||||
label={passkey.name}
|
||||
@@ -46,7 +47,7 @@
|
||||
onDelete={() => deletePasskey(passkey)}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
</Item.Group>
|
||||
|
||||
<RenamePasskeyModal
|
||||
bind:passkey={passkeyToRename}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as Dialog from '$lib/components/ui/dialog';
|
||||
import * as Field from '$lib/components/ui/field';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
import WebAuthnService from '$lib/services/webauthn-service';
|
||||
import type { Passkey } from '$lib/types/passkey.type';
|
||||
@@ -52,7 +52,7 @@
|
||||
</Dialog.Header>
|
||||
<form onsubmit={preventDefault(onSubmit)}>
|
||||
<div class="grid items-center gap-4 sm:grid-cols-4">
|
||||
<Label for="name" class="sm:text-right">{m.name()}</Label>
|
||||
<Field.Label for="name" class="sm:text-right">{m.name()}</Field.Label>
|
||||
<Input id="name" bind:value={name} class="col-span-3" />
|
||||
</div>
|
||||
<Dialog.Footer class="mt-4">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import FileInput from '$lib/components/form/file-input.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import * as Field from '$lib/components/ui/field';
|
||||
import { cn } from '$lib/utils/style';
|
||||
import { LucideImageOff, LucideUpload, LucideX } from '@lucide/svelte';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
@@ -53,7 +53,7 @@
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col items-start md:flex-row md:items-center" {...restProps}>
|
||||
<Label class="w-52" for={id}>{label}</Label>
|
||||
<Field.Label class="w-52" for={id}>{label}</Field.Label>
|
||||
<FileInput {id} variant="secondary" {accept} onchange={onImageChange}>
|
||||
<div
|
||||
class={cn('group/image relative flex items-center rounded transition-colors', {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import FormInput from '$lib/components/form/form-input.svelte';
|
||||
import SwitchWithLabel from '$lib/components/form/switch-with-label.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import Label from '$lib/components/ui/label/label.svelte';
|
||||
import * as Field from '$lib/components/ui/field';
|
||||
import * as Select from '$lib/components/ui/select';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
import AppConfigService from '$lib/services/app-config-service';
|
||||
@@ -112,8 +112,8 @@
|
||||
<FormInput label={m.smtp_user()} bind:input={$inputs.smtpUser} />
|
||||
<FormInput label={m.smtp_password()} type="password" bind:input={$inputs.smtpPassword} />
|
||||
<FormInput label={m.smtp_from()} bind:input={$inputs.smtpFrom} />
|
||||
<div class="grid gap-2">
|
||||
<Label class="mb-0" for="smtp-tls">{m.smtp_tls_option()}</Label>
|
||||
<Field.Field>
|
||||
<Field.Label for="smtp-tls">{m.smtp_tls_option()}</Field.Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={$inputs.smtpTls.value}
|
||||
@@ -128,7 +128,7 @@
|
||||
<Select.Item value="tls" label="TLS" />
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
</Field.Field>
|
||||
<SwitchWithLabel
|
||||
id="skip-cert-verify"
|
||||
label={m.skip_certificate_verification()}
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
import FormInput from '$lib/components/form/form-input.svelte';
|
||||
import SwitchWithLabel from '$lib/components/form/switch-with-label.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Label } from '$lib/components/ui/label/index.js';
|
||||
import * as Select from '$lib/components/ui/select';
|
||||
import * as Field from '$lib/components/ui/field';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
import appConfigStore from '$lib/stores/application-configuration-store';
|
||||
import type { AllAppConfig } from '$lib/types/application-configuration';
|
||||
@@ -82,21 +81,21 @@
|
||||
bind:checked={$inputs.disableAnimations.value}
|
||||
/>
|
||||
|
||||
<div class="space-y-5">
|
||||
<Field.Field class="space-y-5">
|
||||
<div>
|
||||
<Label class="mb-0 text-sm font-medium">
|
||||
<Field.Label>
|
||||
{m.accent_color()}
|
||||
</Label>
|
||||
<p class="text-muted-foreground text-[0.8rem]">
|
||||
</Field.Label>
|
||||
<Field.Description>
|
||||
{m.select_an_accent_color_to_customize_the_appearance_of_pocket_id()}
|
||||
</p>
|
||||
</Field.Description>
|
||||
</div>
|
||||
<AccentColorPicker
|
||||
previousColor={appConfig.accentColor}
|
||||
bind:selectedColor={$inputs.accentColor.value}
|
||||
disabled={$appConfigStore.uiConfigDisabled}
|
||||
/>
|
||||
</div>
|
||||
</Field.Field>
|
||||
</div>
|
||||
<div class="mt-5 flex justify-end">
|
||||
<Button {isLoading} type="submit">{m.save()}</Button>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import CustomClaimsInput from '$lib/components/form/custom-claims-input.svelte';
|
||||
import UserGroupInput from '$lib/components/form/user-group-input.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import * as Field from '$lib/components/ui/field';
|
||||
import * as Select from '$lib/components/ui/select';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
import appConfigStore from '$lib/stores/application-configuration-store';
|
||||
@@ -58,68 +58,68 @@
|
||||
<form onsubmit={preventDefault(onSubmit)}>
|
||||
<fieldset class="flex flex-col gap-5" disabled={$appConfigStore.uiConfigDisabled}>
|
||||
<div class="grid gap-2">
|
||||
<div>
|
||||
<Label class="mb-0" for="enable-user-signup">{m.enable_user_signups()}</Label>
|
||||
<p class="text-muted-foreground text-[0.8rem]">
|
||||
<Field.Field>
|
||||
<Field.Label for="enable-user-signup">{m.enable_user_signups()}</Field.Label>
|
||||
<Field.Description>
|
||||
{m.enable_user_signups_description()}
|
||||
</p>
|
||||
</div>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={allowUserSignups}
|
||||
onValueChange={(v) => (allowUserSignups = v as typeof allowUserSignups)}
|
||||
>
|
||||
<Select.Trigger
|
||||
id="enable-user-signup"
|
||||
class="w-full"
|
||||
aria-label={m.enable_user_signups()}
|
||||
placeholder={m.enable_user_signups()}
|
||||
</Field.Description>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={allowUserSignups}
|
||||
onValueChange={(v) => (allowUserSignups = v as typeof allowUserSignups)}
|
||||
>
|
||||
{signupOptions[allowUserSignups]?.label}
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
<Select.Item value="disabled">
|
||||
<div class="flex flex-col items-start gap-1">
|
||||
<span class="font-medium">{signupOptions.disabled.label}</span>
|
||||
<span class="text-muted-foreground text-xs">
|
||||
{signupOptions.disabled.description}
|
||||
</span>
|
||||
</div>
|
||||
</Select.Item>
|
||||
<Select.Item value="withToken">
|
||||
<div class="flex flex-col items-start gap-1">
|
||||
<span class="font-medium">{signupOptions.withToken.label}</span>
|
||||
<span class="text-muted-foreground text-xs">
|
||||
{signupOptions.withToken.description}
|
||||
</span>
|
||||
</div>
|
||||
</Select.Item>
|
||||
<Select.Item value="open">
|
||||
<div class="flex flex-col items-start gap-1">
|
||||
<span class="font-medium">{signupOptions.open.label}</span>
|
||||
<span class="text-muted-foreground text-xs">
|
||||
{signupOptions.open.description}
|
||||
</span>
|
||||
</div>
|
||||
</Select.Item>
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
<Select.Trigger
|
||||
id="enable-user-signup"
|
||||
class="w-full"
|
||||
aria-label={m.enable_user_signups()}
|
||||
placeholder={m.enable_user_signups()}
|
||||
>
|
||||
{signupOptions[allowUserSignups]?.label}
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
<Select.Item value="disabled">
|
||||
<div class="flex flex-col items-start gap-1">
|
||||
<span class="font-medium">{signupOptions.disabled.label}</span>
|
||||
<span class="text-muted-foreground text-xs">
|
||||
{signupOptions.disabled.description}
|
||||
</span>
|
||||
</div>
|
||||
</Select.Item>
|
||||
<Select.Item value="withToken">
|
||||
<div class="flex flex-col items-start gap-1">
|
||||
<span class="font-medium">{signupOptions.withToken.label}</span>
|
||||
<span class="text-muted-foreground text-xs">
|
||||
{signupOptions.withToken.description}
|
||||
</span>
|
||||
</div>
|
||||
</Select.Item>
|
||||
<Select.Item value="open">
|
||||
<div class="flex flex-col items-start gap-1">
|
||||
<span class="font-medium">{signupOptions.open.label}</span>
|
||||
<span class="text-muted-foreground text-xs">
|
||||
{signupOptions.open.description}
|
||||
</span>
|
||||
</div>
|
||||
</Select.Item>
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</Field.Field>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label for="default-groups" class="mb-0">{m.user_groups()}</Label>
|
||||
<p class="text-muted-foreground mt-1 mb-2 text-xs">
|
||||
<Field.Field>
|
||||
<Field.Label for="default-groups">{m.user_groups()}</Field.Label>
|
||||
<Field.Description>
|
||||
{m.user_creation_groups_description()}
|
||||
</p>
|
||||
</Field.Description>
|
||||
<UserGroupInput bind:selectedGroupIds />
|
||||
</div>
|
||||
<div>
|
||||
<Label class="mb-0">{m.custom_claims()}</Label>
|
||||
<p class="text-muted-foreground mt-1 mb-2 text-xs">
|
||||
</Field.Field>
|
||||
<Field.Field>
|
||||
<Field.Label>{m.custom_claims()}</Field.Label>
|
||||
<Field.Description>
|
||||
{m.user_creation_claims_description()}
|
||||
</p>
|
||||
</Field.Description>
|
||||
<CustomClaimsInput bind:customClaims />
|
||||
</div>
|
||||
</Field.Field>
|
||||
|
||||
<div class="flex justify-end pt-2">
|
||||
<Button {isLoading} type="submit">{m.save()}</Button>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as Dialog from '$lib/components/ui/dialog';
|
||||
import * as Field from '$lib/components/ui/field';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Label } from '$lib/components/ui/label/index.js';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
import { preventDefault } from '$lib/utils/event-util';
|
||||
|
||||
@@ -48,8 +48,8 @@
|
||||
|
||||
<form onsubmit={preventDefault(applyCustomColor)}>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<Label for="custom-color-input" class="text-sm font-medium">{m.color_value()}</Label>
|
||||
<Field.Field>
|
||||
<Field.Label for="custom-color-input">{m.color_value()}</Field.Label>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-full transition">
|
||||
<Input
|
||||
@@ -68,7 +68,7 @@
|
||||
style="background-color: {customColorInput}"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</Field.Field>
|
||||
</div>
|
||||
|
||||
<Dialog.Footer class="mt-6">
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import CopyToClipboard from '$lib/components/copy-to-clipboard.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import Label from '$lib/components/ui/label/label.svelte';
|
||||
import * as Field from '$lib/components/ui/field';
|
||||
import UserGroupSelection from '$lib/components/user-group-selection.svelte';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
import OidcService from '$lib/services/oidc-service';
|
||||
@@ -177,14 +177,14 @@
|
||||
<Card.Content>
|
||||
<div class="flex flex-col">
|
||||
<div class="mb-2 flex flex-col sm:flex-row sm:items-center">
|
||||
<Label class="mb-0 w-50">{m.client_id()}</Label>
|
||||
<Field.Label class="w-50">{m.client_id()}</Field.Label>
|
||||
<CopyToClipboard value={client.id}>
|
||||
<span class="text-muted-foreground text-sm" data-testid="client-id"> {client.id}</span>
|
||||
</CopyToClipboard>
|
||||
</div>
|
||||
{#if !client.isPublic}
|
||||
<div class="mt-1 mb-2 flex flex-col sm:flex-row sm:items-center">
|
||||
<Label class="mb-0 w-50">{m.client_secret()}</Label>
|
||||
<Field.Label class="w-50">{m.client_secret()}</Field.Label>
|
||||
{#if $clientSecretStore}
|
||||
<CopyToClipboard value={$clientSecretStore}>
|
||||
<span class="text-muted-foreground text-sm" data-testid="client-secret">
|
||||
@@ -211,7 +211,7 @@
|
||||
<div transition:slide>
|
||||
{#each Object.entries(setupDetails) as [key, value]}
|
||||
<div class="mb-5 flex flex-col sm:flex-row sm:items-center">
|
||||
<Label class="mb-0 w-50">{key}</Label>
|
||||
<Field.Label class="w-50">{key}</Field.Label>
|
||||
<CopyToClipboard {value}>
|
||||
<span class="text-muted-foreground text-sm">{value}</span>
|
||||
</CopyToClipboard>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import * as Dialog from '$lib/components/ui/dialog';
|
||||
import * as Field from '$lib/components/ui/field';
|
||||
import Input from '$lib/components/ui/input/input.svelte';
|
||||
import Label from '$lib/components/ui/label/label.svelte';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
|
||||
let {
|
||||
@@ -23,7 +23,7 @@
|
||||
<Dialog.Title>{m.one_time_link()}</Dialog.Title>
|
||||
<Dialog.Description>{m.use_this_link_to_sign_in_once()}</Dialog.Description>
|
||||
</Dialog.Header>
|
||||
<Label for="one-time-link">{m.one_time_link()}</Label>
|
||||
<Field.Label for="one-time-link">{m.one_time_link()}</Field.Label>
|
||||
<Input id="one-time-link" value={oneTimeLink} readonly />
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script lang="ts">
|
||||
import FormInput from '$lib/components/form/form-input.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as Field from '$lib/components/ui/field';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import Label from '$lib/components/ui/label/label.svelte';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
import type { OidcClient, OidcClientFederatedIdentity } from '$lib/types/oidc.type';
|
||||
import { LucideMinus, LucidePlus } from '@lucide/svelte';
|
||||
@@ -67,7 +67,7 @@
|
||||
{#each federatedIdentities as identity, i}
|
||||
<div class="space-y-3 rounded-lg border p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<Label class="text-sm font-medium">Identity {i + 1}</Label>
|
||||
<Field.Label>Identity {i + 1}</Field.Label>
|
||||
{#if federatedIdentities.length > 0}
|
||||
<Button
|
||||
variant="outline"
|
||||
@@ -81,8 +81,8 @@
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-3 md:grid-cols-2">
|
||||
<div>
|
||||
<Label required for="issuer-{i}" class="text-xs">Issuer</Label>
|
||||
<Field.Field>
|
||||
<Field.Label required for="issuer-{i}">Issuer</Field.Label>
|
||||
<Input
|
||||
id="issuer-{i}"
|
||||
placeholder="https://example.com/"
|
||||
@@ -91,12 +91,12 @@
|
||||
aria-invalid={!!getFieldError(i, 'issuer')}
|
||||
/>
|
||||
{#if getFieldError(i, 'issuer')}
|
||||
<p class="text-destructive mt-1 text-xs">{getFieldError(i, 'issuer')}</p>
|
||||
<Field.Error>{getFieldError(i, 'issuer')}</Field.Error>
|
||||
{/if}
|
||||
</div>
|
||||
</Field.Field>
|
||||
|
||||
<div>
|
||||
<Label for="subject-{i}" class="text-xs">Subject</Label>
|
||||
<Field.Field>
|
||||
<Field.Label for="subject-{i}">Subject</Field.Label>
|
||||
<Input
|
||||
id="subject-{i}"
|
||||
placeholder="Defaults to the client ID"
|
||||
@@ -105,12 +105,12 @@
|
||||
aria-invalid={!!getFieldError(i, 'subject')}
|
||||
/>
|
||||
{#if getFieldError(i, 'subject')}
|
||||
<p class="text-destructive mt-1 text-xs">{getFieldError(i, 'subject')}</p>
|
||||
<Field.Error>{getFieldError(i, 'subject')}</Field.Error>
|
||||
{/if}
|
||||
</div>
|
||||
</Field.Field>
|
||||
|
||||
<div>
|
||||
<Label for="audience-{i}" class="text-xs">Audience</Label>
|
||||
<Field.Field>
|
||||
<Field.Label for="audience-{i}">Audience</Field.Label>
|
||||
<Input
|
||||
id="audience-{i}"
|
||||
placeholder="Defaults to the Pocket ID URL"
|
||||
@@ -119,12 +119,12 @@
|
||||
aria-invalid={!!getFieldError(i, 'audience')}
|
||||
/>
|
||||
{#if getFieldError(i, 'audience')}
|
||||
<p class="text-destructive mt-1 text-xs">{getFieldError(i, 'audience')}</p>
|
||||
<Field.Error>{getFieldError(i, 'audience')}</Field.Error>
|
||||
{/if}
|
||||
</div>
|
||||
</Field.Field>
|
||||
|
||||
<div>
|
||||
<Label for="jwks-{i}" class="text-xs">JWKS URL</Label>
|
||||
<Field.Field>
|
||||
<Field.Label for="jwks-{i}">JWKS URL</Field.Label>
|
||||
<Input
|
||||
id="jwks-{i}"
|
||||
placeholder="Defaults to {identity.issuer || '<issuer>'}/.well-known/jwks.json"
|
||||
@@ -133,9 +133,9 @@
|
||||
aria-invalid={!!getFieldError(i, 'jwks')}
|
||||
/>
|
||||
{#if getFieldError(i, 'jwks')}
|
||||
<p class="text-destructive mt-1 text-xs">{getFieldError(i, 'jwks')}</p>
|
||||
<Field.Error>{getFieldError(i, 'jwks')}</Field.Error>
|
||||
{/if}
|
||||
</div>
|
||||
</Field.Field>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import FormInput from '$lib/components/form/form-input.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as Field from '$lib/components/ui/field';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
import { LucideMinus, LucidePlus } from '@lucide/svelte';
|
||||
@@ -44,7 +45,7 @@
|
||||
</div>
|
||||
</FormInput>
|
||||
{#if error}
|
||||
<p class="text-destructive mt-1 text-xs">{error}</p>
|
||||
<Field.Error>{error}</Field.Error>
|
||||
{/if}
|
||||
<Button
|
||||
class="mt-2"
|
||||
|
||||
@@ -233,7 +233,7 @@
|
||||
onLogoChange={(input) => onLogoChange(input, true)}
|
||||
>
|
||||
{#snippet tabTriggers()}
|
||||
<Tabs.List class="grid h-9 w-full grid-cols-2">
|
||||
<Tabs.List class="grid h-8 w-full grid-cols-2">
|
||||
<Tabs.Trigger value="light-logo" class="px-3">
|
||||
<LucideSun class="size-4" />
|
||||
</Tabs.Trigger>
|
||||
@@ -253,7 +253,7 @@
|
||||
onLogoChange={(input) => onLogoChange(input, false)}
|
||||
>
|
||||
{#snippet tabTriggers()}
|
||||
<Tabs.List class="grid h-9 w-full grid-cols-2">
|
||||
<Tabs.List class="grid h-8 w-full grid-cols-2">
|
||||
<Tabs.Trigger value="light-logo" class="px-3">
|
||||
<LucideSun class="size-4" />
|
||||
</Tabs.Trigger>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import UrlFileInput from '$lib/components/form/url-file-input.svelte';
|
||||
import ImageBox from '$lib/components/image-box.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import * as Field from '$lib/components/ui/field';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
import { LucideX } from '@lucide/svelte';
|
||||
import type { Snippet } from 'svelte';
|
||||
@@ -26,7 +26,7 @@
|
||||
let id = `oidc-client-logo-${light ? 'light' : 'dark'}`;
|
||||
</script>
|
||||
|
||||
<Label for={id}>{m.logo()}</Label>
|
||||
<Field.Label for={id}>{m.logo()}</Field.Label>
|
||||
<div class="flex h-24 items-end gap-4">
|
||||
<div class="flex flex-col gap-2">
|
||||
{#if tabTriggers}
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
import * as Alert from '$lib/components/ui/alert';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as Dialog from '$lib/components/ui/dialog';
|
||||
import Label from '$lib/components/ui/label/label.svelte';
|
||||
import * as Field from '$lib/components/ui/field';
|
||||
import { Spinner } from '$lib/components/ui/spinner';
|
||||
import * as Tabs from '$lib/components/ui/tabs';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
import OidcService from '$lib/services/oidc-service';
|
||||
@@ -110,33 +111,30 @@
|
||||
<div class="overflow-auto px-4">
|
||||
{#if loadingPreview}
|
||||
<div class="flex items-center justify-center py-12">
|
||||
<div class="h-8 w-8 animate-spin rounded-full border-b-2 border-gray-900"></div>
|
||||
<Spinner class="size-8" />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="flex justify-start gap-3">
|
||||
<div>
|
||||
<Label class="text-sm font-medium">{m.users()}</Label>
|
||||
<div>
|
||||
<SearchableSelect
|
||||
class="w-48"
|
||||
selectText={m.select_user()}
|
||||
isLoading={isUserSearchLoading}
|
||||
items={Object.values(users).map((user) => ({
|
||||
value: user.id,
|
||||
label: user.username
|
||||
}))}
|
||||
value={user?.id || ''}
|
||||
oninput={(e) => onUserSearch(e.currentTarget.value)}
|
||||
onSelect={(value) => {
|
||||
user = users.find((u) => u.id === value) || null;
|
||||
loadPreviewData();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label class="text-sm font-medium">Scopes</Label>
|
||||
<Field.Field class="w-auto min-w-48">
|
||||
<Field.Label>{m.users()}</Field.Label>
|
||||
<SearchableSelect
|
||||
selectText={m.select_user()}
|
||||
isLoading={isUserSearchLoading}
|
||||
items={Object.values(users).map((user) => ({
|
||||
value: user.id,
|
||||
label: user.username
|
||||
}))}
|
||||
value={user?.id || ''}
|
||||
oninput={(e) => onUserSearch(e.currentTarget.value)}
|
||||
onSelect={(value) => {
|
||||
user = users.find((u) => u.id === value) || null;
|
||||
loadPreviewData();
|
||||
}}
|
||||
/>
|
||||
</Field.Field>
|
||||
<Field.Field class="w-auto">
|
||||
<Field.Label>{m.scopes()}</Field.Label>
|
||||
<MultiSelect
|
||||
items={[
|
||||
{ value: 'openid', label: 'openid' },
|
||||
@@ -146,7 +144,7 @@
|
||||
]}
|
||||
bind:selectedItems={scopes}
|
||||
/>
|
||||
</div>
|
||||
</Field.Field>
|
||||
</div>
|
||||
|
||||
{#if errorMessage && !loadingPreview}
|
||||
@@ -186,7 +184,7 @@
|
||||
{#snippet tabContent(data: any, title: string)}
|
||||
<div class="space-y-4">
|
||||
<div class="mb-6 flex items-center justify-between">
|
||||
<Label class="text-lg font-semibold">{title}</Label>
|
||||
<span class="text-lg font-semibold">{title}</span>
|
||||
<CopyToClipboard value={JSON.stringify(data, null, 2)}>
|
||||
<Button size="sm" variant="outline">{m.copy_all()}</Button>
|
||||
</CopyToClipboard>
|
||||
@@ -194,7 +192,7 @@
|
||||
<div class="space-y-3">
|
||||
{#each Object.entries(data || {}) as [key, value]}
|
||||
<div class="grid grid-cols-1 items-start gap-4 border-b pb-3 md:grid-cols-[200px_1fr]">
|
||||
<Label class="pt-1 text-sm font-medium">{key}</Label>
|
||||
<Field.Label class="pt-1">{key}</Field.Label>
|
||||
<div class="min-w-0">
|
||||
<CopyToClipboard value={typeof value === 'string' ? value : JSON.stringify(value)}>
|
||||
<div
|
||||
|
||||
Reference in New Issue
Block a user