mirror of
https://github.com/bartvdbraak/omnidash.git
synced 2025-04-27 07:21:20 +00:00
feat: working of some
This commit is contained in:
parent
1d61eec4ea
commit
a50f2c12a8
9 changed files with 459 additions and 257 deletions
|
@ -22,7 +22,7 @@
|
|||
];
|
||||
</script>
|
||||
|
||||
<div class="hidden space-y-6 p-10 pb-16 md:block">
|
||||
<div class="space-y-6 p-10 pb-16">
|
||||
<div class="space-y-0.5">
|
||||
<h2 class="text-2xl font-bold tracking-tight">Settings</h2>
|
||||
<p class="text-muted-foreground">
|
||||
|
|
|
@ -1,24 +1,67 @@
|
|||
import type { PageServerLoad } from "./$types";
|
||||
import { superValidate } from "sveltekit-superforms";
|
||||
import { zod } from "sveltekit-superforms/adapters";
|
||||
import type { PageServerLoad } from "./$types";
|
||||
import { accountFormSchema } from "./account-form.svelte";
|
||||
import { usernameFormSchema } from "./username-form.svelte";
|
||||
import { emailRequestFormSchema, emailConfirmFormSchema } from "./email-form.svelte";
|
||||
import { passwordFormSchema } from "./password-form.svelte";
|
||||
import { fail, type Actions } from "@sveltejs/kit";
|
||||
|
||||
export const load: PageServerLoad = async () => {
|
||||
return {
|
||||
form: await superValidate(zod(accountFormSchema)),
|
||||
usernameForm: await superValidate(zod(usernameFormSchema)),
|
||||
emailRequestForm: await superValidate(zod(emailRequestFormSchema)),
|
||||
emailConfirmForm: await superValidate(zod(emailConfirmFormSchema)),
|
||||
passwordForm: await superValidate(zod(passwordFormSchema)),
|
||||
};
|
||||
};
|
||||
|
||||
export const actions: Actions = {
|
||||
default: async (event) => {
|
||||
const form = await superValidate(event, zod(accountFormSchema));
|
||||
username: async ({ request, locals }) => {
|
||||
const form = await superValidate(request, zod(usernameFormSchema));
|
||||
if (!form.valid) {
|
||||
console.log(form);
|
||||
return fail(400, {
|
||||
form,
|
||||
});
|
||||
}
|
||||
await locals.pocketBase.collection('users').update(locals.id, form.data);
|
||||
return {
|
||||
form,
|
||||
};
|
||||
},
|
||||
emailRequest: async ({ request, locals }) => {
|
||||
const form = await superValidate(request, zod(emailRequestFormSchema));
|
||||
if (!form.valid) {
|
||||
return fail(400, {
|
||||
form,
|
||||
});
|
||||
}
|
||||
await locals.pocketBase.collection('users').requestEmailChange(form.data.newEmail);
|
||||
return {
|
||||
form,
|
||||
};
|
||||
},
|
||||
emailConfirm: async ({ request, locals }) => {
|
||||
const form = await superValidate(request, zod(emailConfirmFormSchema));
|
||||
if (!form.valid) {
|
||||
return fail(400, {
|
||||
form,
|
||||
});
|
||||
}
|
||||
await locals.pocketBase
|
||||
.collection('users')
|
||||
.confirmEmailChange(form.data.token, form.data.password);
|
||||
return {
|
||||
form,
|
||||
};
|
||||
},
|
||||
password: async ({ request, locals }) => {
|
||||
const form = await superValidate(request, zod(passwordFormSchema));
|
||||
if (!form.valid) {
|
||||
return fail(400, {
|
||||
form,
|
||||
});
|
||||
}
|
||||
await locals.pocketBase.collection('users').update(locals.id, form.data);
|
||||
return {
|
||||
form,
|
||||
};
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
<script lang="ts">
|
||||
import { Separator } from "$lib/components/ui/separator";
|
||||
import AccountForm from "./account-form.svelte";
|
||||
import type { PageData } from "./$types";
|
||||
import UsernameForm from "./username-form.svelte";
|
||||
import EmailForm from "./email-form.svelte";
|
||||
import PasswordForm from "./password-form.svelte";
|
||||
|
||||
export let data: PageData;
|
||||
</script>
|
||||
|
@ -10,9 +12,11 @@
|
|||
<div>
|
||||
<h3 class="text-lg font-medium">Account</h3>
|
||||
<p class="text-sm text-muted-foreground">
|
||||
Update your account settings. Set your preferred language and timezone.
|
||||
Update your account settings.
|
||||
</p>
|
||||
</div>
|
||||
<Separator />
|
||||
<AccountForm data={data.form} />
|
||||
<UsernameForm user={data.user} data={data.usernameForm} />
|
||||
<EmailForm user={data.user} requestData={data.emailRequestForm} confirmData={data.emailConfirmForm} />
|
||||
<PasswordForm user={data.user} data={data.passwordForm} />
|
||||
</div>
|
||||
|
|
|
@ -1,180 +0,0 @@
|
|||
<script lang="ts" context="module">
|
||||
import { z } from "zod";
|
||||
|
||||
const languages = [
|
||||
{ label: "English", value: "en" },
|
||||
{ label: "French", value: "fr" },
|
||||
{ label: "German", value: "de" },
|
||||
{ label: "Spanish", value: "es" },
|
||||
{ label: "Portuguese", value: "pt" },
|
||||
{ label: "Russian", value: "ru" },
|
||||
{ label: "Japanese", value: "ja" },
|
||||
{ label: "Korean", value: "ko" },
|
||||
{ label: "Chinese", value: "zh" },
|
||||
] as const;
|
||||
|
||||
type Language = (typeof languages)[number]["value"];
|
||||
|
||||
export const accountFormSchema = z.object({
|
||||
name: z
|
||||
.string({
|
||||
required_error: "Required.",
|
||||
})
|
||||
.min(2, "Name must be at least 2 characters.")
|
||||
.max(30, "Name must not be longer than 30 characters"),
|
||||
// Hack: https://github.com/colinhacks/zod/issues/2280
|
||||
language: z.enum(languages.map((lang) => lang.value) as [Language, ...Language[]]),
|
||||
dob: z
|
||||
.string()
|
||||
.datetime()
|
||||
// we're setting it optional so the user can clear the date and we don't run into
|
||||
// type issues, but we refine it to make sure it's not undefined
|
||||
.optional()
|
||||
.refine((date) => (date === undefined ? false : true), "Please select a valid date."),
|
||||
});
|
||||
|
||||
export type AccountFormSchema = typeof accountFormSchema;
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { Calendar as CalendarIcon, CaretSort, Check } from "radix-icons-svelte";
|
||||
import SuperDebug, { type SuperValidated, type Infer, superForm } from "sveltekit-superforms";
|
||||
import { zodClient } from "sveltekit-superforms/adapters";
|
||||
import * as Form from "$lib/components/ui/form";
|
||||
import * as Popover from "$lib/components/ui/popover";
|
||||
import * as Command from "$lib/components/ui/command";
|
||||
import { Calendar } from "$lib/components/ui/calendar";
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import { buttonVariants } from "$lib/components/ui/button";
|
||||
import { cn } from "$lib/utils";
|
||||
import { browser, dev } from "$app/environment";
|
||||
import { PUBLIC_DEBUG_FORMS } from "$env/static/public";
|
||||
import {
|
||||
DateFormatter,
|
||||
getLocalTimeZone,
|
||||
type DateValue,
|
||||
parseDate,
|
||||
} from "@internationalized/date";
|
||||
|
||||
export let data: SuperValidated<Infer<AccountFormSchema>>;
|
||||
|
||||
const form = superForm(data, {
|
||||
validators: zodClient(accountFormSchema),
|
||||
});
|
||||
const { form: formData, enhance, validate } = form;
|
||||
|
||||
const df = new DateFormatter("en-US", {
|
||||
dateStyle: "long",
|
||||
});
|
||||
|
||||
let dobValue: DateValue | undefined = $formData.dob ? parseDate($formData.dob) : undefined;
|
||||
</script>
|
||||
|
||||
<form method="POST" class="space-y-8" use:enhance>
|
||||
<Form.Field name="name" {form}>
|
||||
<Form.Control let:attrs>
|
||||
<Form.Label>Name</Form.Label>
|
||||
<Input {...attrs} bind:value={$formData.name} />
|
||||
</Form.Control>
|
||||
<Form.FieldErrors />
|
||||
</Form.Field>
|
||||
<Form.Field {form} name="dob" class="flex flex-col">
|
||||
<Form.Control let:attrs>
|
||||
<Form.Label>Date of Birth</Form.Label>
|
||||
<Popover.Root>
|
||||
<Popover.Trigger
|
||||
class={cn(
|
||||
buttonVariants({ variant: "outline" }),
|
||||
"w-[240px] justify-start text-left font-normal",
|
||||
!dobValue && "text-muted-foreground"
|
||||
)}
|
||||
{...attrs}
|
||||
>
|
||||
<CalendarIcon class="mr-2 h-4 w-4" />
|
||||
{dobValue ? df.format(dobValue.toDate(getLocalTimeZone())) : "Pick a date"}
|
||||
</Popover.Trigger>
|
||||
<Popover.Content class="w-auto p-0" align="start">
|
||||
<Calendar
|
||||
bind:value={dobValue}
|
||||
isDateDisabled={(currDate) => {
|
||||
const currDateObj = currDate.toDate(getLocalTimeZone());
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
|
||||
if (currDateObj > today || currDate.year < 1900) return true;
|
||||
|
||||
return false;
|
||||
}}
|
||||
onValueChange={(value) => {
|
||||
if (value === undefined) {
|
||||
$formData.dob = undefined;
|
||||
validate("dob");
|
||||
return;
|
||||
}
|
||||
$formData.dob = value.toDate(getLocalTimeZone()).toISOString();
|
||||
validate("dob");
|
||||
}}
|
||||
/>
|
||||
</Popover.Content>
|
||||
<input hidden bind:value={$formData.dob} name={attrs.name} />
|
||||
</Popover.Root>
|
||||
</Form.Control>
|
||||
<Form.FieldErrors />
|
||||
</Form.Field>
|
||||
|
||||
<Form.Field {form} name="language" class="flex flex-col">
|
||||
<Popover.Root>
|
||||
<Form.Control let:attrs>
|
||||
<Form.Label>Language</Form.Label>
|
||||
<Popover.Trigger
|
||||
role="combobox"
|
||||
class={cn(
|
||||
buttonVariants({ variant: "outline" }),
|
||||
"w-[200px] justify-between",
|
||||
!$formData.language && "text-muted-foreground"
|
||||
)}
|
||||
{...attrs}
|
||||
>
|
||||
{languages.find((lang) => lang.value === $formData.language)?.label ||
|
||||
"Select a language"}
|
||||
<CaretSort class="ml-2 size-4 shrink-0 opacity-50" />
|
||||
</Popover.Trigger>
|
||||
<input hidden bind:value={$formData.language} name={attrs.name} />
|
||||
</Form.Control>
|
||||
<Popover.Content class="w-[200px] p-0">
|
||||
<Command.Root>
|
||||
<Command.Input placeholder="Search language..." />
|
||||
<Command.Empty>No language found.</Command.Empty>
|
||||
<Command.List>
|
||||
{#each languages as language}
|
||||
<Command.Item
|
||||
{...form}
|
||||
value={language.label}
|
||||
onSelect={() => {
|
||||
$formData.language = language.value;
|
||||
validate("language");
|
||||
}}
|
||||
>
|
||||
<Check
|
||||
class={cn(
|
||||
"mr-2 size-4",
|
||||
language.value === $formData.language
|
||||
? "opacity-100"
|
||||
: "opacity-0"
|
||||
)}
|
||||
/>
|
||||
{language.label}
|
||||
</Command.Item>
|
||||
{/each}
|
||||
</Command.List>
|
||||
</Command.Root>
|
||||
</Popover.Content>
|
||||
</Popover.Root>
|
||||
</Form.Field>
|
||||
|
||||
<Form.Button>Update account</Form.Button>
|
||||
</form>
|
||||
|
||||
{#if dev && PUBLIC_DEBUG_FORMS == 'true' && browser}
|
||||
<SuperDebug data={$formData} />
|
||||
{/if}
|
134
src/routes/(user)/settings/account/email-form.svelte
Normal file
134
src/routes/(user)/settings/account/email-form.svelte
Normal file
|
@ -0,0 +1,134 @@
|
|||
<script lang="ts" context="module">
|
||||
import { z } from 'zod';
|
||||
export const emailRequestFormSchema = z.object({
|
||||
newEmail: z.string().email()
|
||||
});
|
||||
export const emailConfirmFormSchema = z.object({
|
||||
token: z.string(),
|
||||
password: z.string()
|
||||
});
|
||||
export type EmailRequestFormSchema = typeof emailRequestFormSchema;
|
||||
export type EmailConfirmFormSchema = typeof emailConfirmFormSchema;
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import * as Form from '$lib/components/ui/form';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { type SuperValidated, type Infer, superForm } from 'sveltekit-superforms';
|
||||
import SuperDebug from 'sveltekit-superforms';
|
||||
import { zodClient } from 'sveltekit-superforms/adapters';
|
||||
import { browser, dev } from '$app/environment';
|
||||
import { PUBLIC_DEBUG_FORMS } from '$env/static/public';
|
||||
import type { LayoutData } from '../$types';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { Separator } from '$lib/components/ui/separator';
|
||||
import { Icons } from '$lib/components/site';
|
||||
|
||||
export let user: LayoutData['user'];
|
||||
export let requestData: SuperValidated<Infer<EmailRequestFormSchema>>;
|
||||
export let confirmData: SuperValidated<Infer<EmailConfirmFormSchema>>;
|
||||
let isLoading = false;
|
||||
|
||||
const requestForm = superForm(requestData, {
|
||||
validators: zodClient(emailRequestFormSchema),
|
||||
onSubmit: () => {
|
||||
isLoading = true;
|
||||
toast.success('Sending verification token...');
|
||||
},
|
||||
onUpdated: ({ form: f }) => {
|
||||
isLoading = false;
|
||||
if (f.valid) {
|
||||
toast.success('Verification token has been sent.');
|
||||
} else {
|
||||
toast.error('Please fix the errors in the form.');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const confirmForm = superForm(confirmData, {
|
||||
validators: zodClient(emailConfirmFormSchema),
|
||||
onSubmit: () => {
|
||||
isLoading = true;
|
||||
toast.loading('Updating email...');
|
||||
},
|
||||
onUpdated: ({ form: f }) => {
|
||||
isLoading = false;
|
||||
if (f.valid) {
|
||||
toast.success('Your email has been updated.');
|
||||
} else {
|
||||
toast.error('Please fix the errors in the form.');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const { form: requestFormData, enhance: requestEnhance } = requestForm;
|
||||
const { form: confirmFormData, enhance: confirmEnhance } = confirmForm;
|
||||
</script>
|
||||
|
||||
<Card.Root>
|
||||
<Card.Header>
|
||||
<Card.Title>Request an email change</Card.Title>
|
||||
<Card.Description
|
||||
>Receive a verification token on this email, which you can enter in the next section.</Card.Description
|
||||
>
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
<form method="POST" action="?/emailRequest" class="space-y-8" use:requestEnhance>
|
||||
<Form.Field form={requestForm} name="newEmail">
|
||||
<Form.Control let:attrs>
|
||||
<Form.Label>New email</Form.Label>
|
||||
<div class="flex space-x-2">
|
||||
<Input placeholder={user?.email} {...attrs} bind:value={$requestFormData.newEmail} />
|
||||
<Form.Button variant="secondary" disabled={isLoading}>Request token</Form.Button>
|
||||
</div>
|
||||
</Form.Control>
|
||||
<Form.FieldErrors />
|
||||
</Form.Field>
|
||||
</form>
|
||||
{#if dev && PUBLIC_DEBUG_FORMS == 'true' && browser}
|
||||
<div class="pt-4">
|
||||
<SuperDebug data={$requestFormData} />
|
||||
</div>
|
||||
{/if}
|
||||
</Card.Content>
|
||||
|
||||
<Separator />
|
||||
|
||||
<Card.Header>
|
||||
<Card.Title>Confirm email change</Card.Title>
|
||||
<Card.Description
|
||||
>Enter your verification token below to confirm the email change.</Card.Description
|
||||
>
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
<form method="POST" action="?/emailConfirm" class="space-y-2" use:confirmEnhance>
|
||||
<Form.Field form={confirmForm} name="token">
|
||||
<Form.Control let:attrs>
|
||||
<Form.Label>Token</Form.Label>
|
||||
<Input {...attrs} bind:value={$confirmFormData.token} />
|
||||
</Form.Control>
|
||||
<Form.FieldErrors />
|
||||
</Form.Field>
|
||||
<Form.Field form={confirmForm} name="password">
|
||||
<Form.Control let:attrs>
|
||||
<Form.Label>Password</Form.Label>
|
||||
<Input {...attrs} bind:value={$confirmFormData.password} type="password" />
|
||||
</Form.Control>
|
||||
<Form.FieldErrors />
|
||||
</Form.Field>
|
||||
<Form.Button disabled={isLoading}>
|
||||
{#if isLoading}
|
||||
<Icons.spinner class="mr-2 h-4 w-4 animate-spin" />
|
||||
{/if}
|
||||
Update password
|
||||
</Form.Button>
|
||||
</form>
|
||||
|
||||
{#if dev && PUBLIC_DEBUG_FORMS == 'true' && browser}
|
||||
<div class="pt-4">
|
||||
<SuperDebug data={$confirmFormData} />
|
||||
</div>
|
||||
{/if}
|
||||
</Card.Content>
|
||||
</Card.Root>
|
92
src/routes/(user)/settings/account/password-form.svelte
Normal file
92
src/routes/(user)/settings/account/password-form.svelte
Normal file
|
@ -0,0 +1,92 @@
|
|||
<script lang="ts" context="module">
|
||||
import { z } from 'zod';
|
||||
export const passwordFormSchema = z.object({
|
||||
oldPassword: z.string(),
|
||||
password: z.string().min(8),
|
||||
passwordConfirm: z.string().min(8)
|
||||
});
|
||||
export type PasswordFormSchema = typeof passwordFormSchema;
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import * as Form from '$lib/components/ui/form';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { type SuperValidated, type Infer, superForm } from 'sveltekit-superforms';
|
||||
import SuperDebug from 'sveltekit-superforms';
|
||||
import { zodClient } from 'sveltekit-superforms/adapters';
|
||||
import { browser, dev } from '$app/environment';
|
||||
import { PUBLIC_DEBUG_FORMS } from '$env/static/public';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { Icons } from '$lib/components/site';
|
||||
|
||||
export let data: SuperValidated<Infer<PasswordFormSchema>>;
|
||||
let isLoading = false;
|
||||
|
||||
const form = superForm(data, {
|
||||
validators: zodClient(passwordFormSchema),
|
||||
onSubmit: () => {
|
||||
isLoading = true;
|
||||
toast.loading('Updating password...');
|
||||
},
|
||||
onUpdated: ({ form: f }) => {
|
||||
if (f.valid) {
|
||||
toast.success('Your password has been updated.');
|
||||
} else {
|
||||
toast.error('Please fix the errors in the form.');
|
||||
}
|
||||
isLoading = false;
|
||||
},
|
||||
onError: (e) => {
|
||||
toast.error(e.result.error.message);
|
||||
isLoading = false;
|
||||
}
|
||||
});
|
||||
|
||||
const { form: formData, enhance } = form;
|
||||
</script>
|
||||
|
||||
<Card.Root>
|
||||
<Card.Header>
|
||||
<Card.Title>Change your password</Card.Title>
|
||||
<Card.Description>You can change your password here.</Card.Description>
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
<form method="POST" action="?/password" class="space-y-2" use:enhance>
|
||||
<Form.Field {form} name="oldPassword">
|
||||
<Form.Control let:attrs>
|
||||
<Form.Label>Current password</Form.Label>
|
||||
<Input {...attrs} bind:value={$formData.oldPassword} type="password" />
|
||||
</Form.Control>
|
||||
<Form.FieldErrors />
|
||||
</Form.Field>
|
||||
<Form.Field {form} name="password">
|
||||
<Form.Control let:attrs>
|
||||
<Form.Label>New password</Form.Label>
|
||||
<Input {...attrs} bind:value={$formData.password} type="password" />
|
||||
</Form.Control>
|
||||
<Form.FieldErrors />
|
||||
</Form.Field>
|
||||
<Form.Field {form} name="passwordConfirm">
|
||||
<Form.Control let:attrs>
|
||||
<Form.Label>Confirm new password</Form.Label>
|
||||
<Input {...attrs} bind:value={$formData.passwordConfirm} type="password" />
|
||||
</Form.Control>
|
||||
<Form.FieldErrors />
|
||||
</Form.Field>
|
||||
|
||||
<Form.Button disabled={isLoading}>
|
||||
{#if isLoading}
|
||||
<Icons.spinner class="mr-2 h-4 w-4 animate-spin" />
|
||||
{/if}
|
||||
Update password
|
||||
</Form.Button>
|
||||
</form>
|
||||
|
||||
{#if dev && PUBLIC_DEBUG_FORMS == 'true' && browser}
|
||||
<div class="pt-4">
|
||||
<SuperDebug data={$formData} />
|
||||
</div>
|
||||
{/if}
|
||||
</Card.Content>
|
||||
</Card.Root>
|
76
src/routes/(user)/settings/account/username-form.svelte
Normal file
76
src/routes/(user)/settings/account/username-form.svelte
Normal file
|
@ -0,0 +1,76 @@
|
|||
<script lang="ts" context="module">
|
||||
import { z } from 'zod';
|
||||
export const usernameFormSchema = z.object({
|
||||
username: z.string().min(2).max(16)
|
||||
});
|
||||
export type UsernameFormSchema = typeof usernameFormSchema;
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import * as Form from '$lib/components/ui/form';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { type SuperValidated, type Infer, superForm } from 'sveltekit-superforms';
|
||||
import SuperDebug from 'sveltekit-superforms';
|
||||
import { zodClient } from 'sveltekit-superforms/adapters';
|
||||
import { browser, dev } from '$app/environment';
|
||||
import { PUBLIC_DEBUG_FORMS } from '$env/static/public';
|
||||
import type { LayoutData } from '../$types';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { Icons } from '$lib/components/site';
|
||||
|
||||
export let user: LayoutData['user'];
|
||||
export let data: SuperValidated<Infer<UsernameFormSchema>>;
|
||||
let isLoading = false;
|
||||
|
||||
const form = superForm(data, {
|
||||
validators: zodClient(usernameFormSchema),
|
||||
onSubmit: () => {
|
||||
isLoading = true;
|
||||
toast.loading('Updating username...');
|
||||
},
|
||||
onUpdated: ({ form: f }) => {
|
||||
isLoading = false;
|
||||
if (f.valid) {
|
||||
toast.success('Your username has been updated.');
|
||||
} else {
|
||||
toast.error('Please fix the errors in the form.');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const { form: formData, enhance } = form;
|
||||
</script>
|
||||
|
||||
<Card.Root>
|
||||
<Card.Header>
|
||||
<Card.Title>Change your username</Card.Title>
|
||||
<Card.Description>
|
||||
You can modify the username used for logging in and as your handle.
|
||||
</Card.Description>
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
<form method="POST" action="?/username" class="space-y-8" use:enhance>
|
||||
<Form.Field {form} name="username">
|
||||
<Form.Control let:attrs>
|
||||
<Form.Label>Username</Form.Label>
|
||||
<Input placeholder={user?.username} {...attrs} bind:value={$formData.username} />
|
||||
</Form.Control>
|
||||
<Form.FieldErrors />
|
||||
</Form.Field>
|
||||
|
||||
<Form.Button disabled={isLoading}>
|
||||
{#if isLoading}
|
||||
<Icons.spinner class="mr-2 h-4 w-4 animate-spin" />
|
||||
{/if}
|
||||
Update password
|
||||
</Form.Button>
|
||||
</form>
|
||||
|
||||
{#if dev && PUBLIC_DEBUG_FORMS == 'true' && browser}
|
||||
<div class="pt-4">
|
||||
<SuperDebug data={$formData} />
|
||||
</div>
|
||||
{/if}
|
||||
</Card.Content>
|
||||
</Card.Root>
|
|
@ -1,93 +1,104 @@
|
|||
<script lang="ts" context="module">
|
||||
import { z } from "zod";
|
||||
import { z } from 'zod';
|
||||
|
||||
export const appearanceFormSchema = z.object({
|
||||
theme: z.enum(["light", "dark"], {
|
||||
required_error: "Please select a theme.",
|
||||
}),
|
||||
font: z.enum(["inter", "manrope", "system"], {
|
||||
invalid_type_error: "Select a font",
|
||||
required_error: "Please select a font.",
|
||||
}),
|
||||
theme: z.enum(['system', 'light', 'dark'], {
|
||||
required_error: 'Please select a theme.'
|
||||
})
|
||||
});
|
||||
|
||||
export type AppearanceFormSchema = typeof appearanceFormSchema;
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { ChevronDown } from "radix-icons-svelte";
|
||||
import { browser, dev } from "$app/environment";
|
||||
import { PUBLIC_DEBUG_FORMS } from "$env/static/public";
|
||||
import SuperDebug, { type SuperValidated, type Infer, superForm } from "sveltekit-superforms";
|
||||
import * as Form from "$lib/components/ui/form";
|
||||
import * as RadioGroup from "$lib/components/ui/radio-group";
|
||||
import Label from "$lib/components/ui/label/label.svelte";
|
||||
import { zodClient } from "sveltekit-superforms/adapters";
|
||||
import { cn } from "$lib/utils";
|
||||
import { buttonVariants } from "$lib/components/ui/button";
|
||||
import { browser, dev } from '$app/environment';
|
||||
import { PUBLIC_DEBUG_FORMS } from '$env/static/public';
|
||||
import SuperDebug, { type SuperValidated, type Infer, superForm } from 'sveltekit-superforms';
|
||||
import * as Form from '$lib/components/ui/form';
|
||||
import * as RadioGroup from '$lib/components/ui/radio-group';
|
||||
import Label from '$lib/components/ui/label/label.svelte';
|
||||
import { zodClient } from 'sveltekit-superforms/adapters';
|
||||
export let data: SuperValidated<Infer<AppearanceFormSchema>>;
|
||||
|
||||
const form = superForm(data, {
|
||||
validators: zodClient(appearanceFormSchema),
|
||||
validators: zodClient(appearanceFormSchema)
|
||||
});
|
||||
|
||||
const { form: formData, enhance } = form;
|
||||
</script>
|
||||
|
||||
<form method="POST" use:enhance class="space-y-8">
|
||||
<Form.Field {form} name="font">
|
||||
<Form.Control let:attrs>
|
||||
<Form.Label>Font</Form.Label>
|
||||
<div class="relative w-max">
|
||||
<select
|
||||
{...attrs}
|
||||
class={cn(
|
||||
buttonVariants({ variant: "outline" }),
|
||||
"w-[200px] appearance-none font-normal"
|
||||
)}
|
||||
bind:value={$formData.font}
|
||||
>
|
||||
<option value="inter">Inter</option>
|
||||
<option value="manrope">Manrope</option>
|
||||
<option value="system">System</option>
|
||||
</select>
|
||||
<ChevronDown class="absolute right-3 top-2.5 size-4 opacity-50" />
|
||||
</div>
|
||||
</Form.Control>
|
||||
<Form.Description>Set the font you want to use in the dashboard.</Form.Description>
|
||||
<Form.FieldErrors />
|
||||
</Form.Field>
|
||||
<Form.Fieldset {form} name="theme">
|
||||
<Form.Legend>Theme</Form.Legend>
|
||||
<Form.Description>Select the theme for the dashboard.</Form.Description>
|
||||
<Form.FieldErrors />
|
||||
<RadioGroup.Root
|
||||
class="grid max-w-md grid-cols-2 gap-8 pt-2"
|
||||
class="grid grid-cols-3 gap-8 pt-2"
|
||||
orientation="horizontal"
|
||||
bind:value={$formData.theme}
|
||||
>
|
||||
<Form.Control let:attrs>
|
||||
<Label class="[&:has([data-state=checked])>div]:border-primary">
|
||||
<RadioGroup.Item {...attrs} value="system" class="sr-only" />
|
||||
<!-- <div class="container">
|
||||
<div class="div1">
|
||||
<div class="items-center rounded-md border-2 border-muted p-1 hover:border-accent">
|
||||
<div class="space-y-2 rounded-sm bg-[#ecedef] p-2">
|
||||
<div class="space-y-2 rounded-md bg-white p-2 shadow-sm">
|
||||
<div class="h-2 w-4 rounded-lg bg-[#ecedef]" />
|
||||
<div class="h-2 w-full rounded-lg bg-[#ecedef]" />
|
||||
</div>
|
||||
<div class="flex items-center space-x-2 rounded-md bg-white p-2 shadow-sm">
|
||||
<div class="h-4 w-4 rounded-full bg-[#ecedef]" />
|
||||
<div class="h-2 w-full rounded-lg bg-[#ecedef]" />
|
||||
</div>
|
||||
<div class="flex items-center space-x-2 rounded-md bg-white p-2 shadow-sm">
|
||||
<div class="h-4 w-4 rounded-full bg-[#ecedef]" />
|
||||
<div class="h-2 w-full rounded-lg bg-[#ecedef]" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="div2"> -->
|
||||
<div
|
||||
class="items-center rounded-md border-2 border-muted bg-popover p-1 hover:bg-accent hover:text-accent-foreground"
|
||||
>
|
||||
<div class="space-y-2 rounded-sm bg-slate-950 p-2">
|
||||
<div class="space-y-2 rounded-md bg-slate-800 p-2 shadow-sm">
|
||||
<div class="h-2 w-4 rounded-lg bg-slate-400" />
|
||||
<div class="h-2 w-full rounded-lg bg-slate-400" />
|
||||
</div>
|
||||
<div class="flex items-center space-x-2 rounded-md bg-slate-800 p-2 shadow-sm">
|
||||
<div class="h-4 w-4 rounded-full bg-slate-400" />
|
||||
<div class="h-2 w-full rounded-lg bg-slate-400" />
|
||||
</div>
|
||||
<div class="flex items-center space-x-2 rounded-md bg-slate-800 p-2 shadow-sm">
|
||||
<div class="h-4 w-4 rounded-full bg-slate-400" />
|
||||
<div class="h-2 w-full rounded-lg bg-slate-400" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- </div>
|
||||
</div> -->
|
||||
<span class="block w-full p-2 text-center font-normal"> System </span>
|
||||
</Label>
|
||||
</Form.Control>
|
||||
<Form.Control let:attrs>
|
||||
<Label class="[&:has([data-state=checked])>div]:border-primary">
|
||||
<RadioGroup.Item {...attrs} value="light" class="sr-only" />
|
||||
<div
|
||||
class="items-center rounded-md border-2 border-muted p-1 hover:border-accent"
|
||||
>
|
||||
<div class="items-center rounded-md border-2 border-muted p-1 hover:border-accent">
|
||||
<div class="space-y-2 rounded-sm bg-[#ecedef] p-2">
|
||||
<div class="space-y-2 rounded-md bg-white p-2 shadow-sm">
|
||||
<div class="h-2 w-[80px] rounded-lg bg-[#ecedef]" />
|
||||
<div class="h-2 w-[100px] rounded-lg bg-[#ecedef]" />
|
||||
<div class="h-2 w-4 rounded-lg bg-[#ecedef]" />
|
||||
<div class="h-2 w-full rounded-lg bg-[#ecedef]" />
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center space-x-2 rounded-md bg-white p-2 shadow-sm"
|
||||
>
|
||||
<div class="flex items-center space-x-2 rounded-md bg-white p-2 shadow-sm">
|
||||
<div class="h-4 w-4 rounded-full bg-[#ecedef]" />
|
||||
<div class="h-2 w-[100px] rounded-lg bg-[#ecedef]" />
|
||||
<div class="h-2 w-full rounded-lg bg-[#ecedef]" />
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center space-x-2 rounded-md bg-white p-2 shadow-sm"
|
||||
>
|
||||
<div class="flex items-center space-x-2 rounded-md bg-white p-2 shadow-sm">
|
||||
<div class="h-4 w-4 rounded-full bg-[#ecedef]" />
|
||||
<div class="h-2 w-[100px] rounded-lg bg-[#ecedef]" />
|
||||
<div class="h-2 w-full rounded-lg bg-[#ecedef]" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -102,20 +113,16 @@
|
|||
>
|
||||
<div class="space-y-2 rounded-sm bg-slate-950 p-2">
|
||||
<div class="space-y-2 rounded-md bg-slate-800 p-2 shadow-sm">
|
||||
<div class="h-2 w-[80px] rounded-lg bg-slate-400" />
|
||||
<div class="h-2 w-[100px] rounded-lg bg-slate-400" />
|
||||
<div class="h-2 w-4 rounded-lg bg-slate-400" />
|
||||
<div class="h-2 w-full rounded-lg bg-slate-400" />
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center space-x-2 rounded-md bg-slate-800 p-2 shadow-sm"
|
||||
>
|
||||
<div class="flex items-center space-x-2 rounded-md bg-slate-800 p-2 shadow-sm">
|
||||
<div class="h-4 w-4 rounded-full bg-slate-400" />
|
||||
<div class="h-2 w-[100px] rounded-lg bg-slate-400" />
|
||||
<div class="h-2 w-full rounded-lg bg-slate-400" />
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center space-x-2 rounded-md bg-slate-800 p-2 shadow-sm"
|
||||
>
|
||||
<div class="flex items-center space-x-2 rounded-md bg-slate-800 p-2 shadow-sm">
|
||||
<div class="h-4 w-4 rounded-full bg-slate-400" />
|
||||
<div class="h-2 w-[100px] rounded-lg bg-slate-400" />
|
||||
<div class="h-2 w-full rounded-lg bg-slate-400" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -131,3 +138,23 @@
|
|||
{#if dev && PUBLIC_DEBUG_FORMS == 'true' && browser}
|
||||
<SuperDebug data={$formData} />
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.div1,
|
||||
.div2 {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.div2 {
|
||||
clip-path: polygon(0 0, 100% 0, 0 100%);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
import { PUBLIC_DEBUG_FORMS } from '$env/static/public';
|
||||
import type { LayoutData } from '../$types';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { Icons } from '$lib/components/site';
|
||||
|
||||
export let user: LayoutData['user'];
|
||||
export let data: SuperValidated<Infer<ProfileFormSchema>>;
|
||||
|
@ -26,7 +27,7 @@
|
|||
validators: zodClient(profileFormSchema),
|
||||
onSubmit: () => {
|
||||
isLoading = true;
|
||||
toast.success('Updating your name...');
|
||||
toast.loading('Updating your name...');
|
||||
},
|
||||
onUpdated: ({ form: f }) => {
|
||||
isLoading = false;
|
||||
|
@ -59,7 +60,12 @@
|
|||
<Form.FieldErrors />
|
||||
</Form.Field>
|
||||
|
||||
<Form.Button disabled={isLoading}>Update name</Form.Button>
|
||||
<Form.Button disabled={isLoading}>
|
||||
{#if isLoading}
|
||||
<Icons.spinner class="mr-2 h-4 w-4 animate-spin" />
|
||||
{/if}
|
||||
Update password
|
||||
</Form.Button>
|
||||
</form>
|
||||
|
||||
{#if dev && PUBLIC_DEBUG_FORMS == 'true' && browser}
|
||||
|
|
Loading…
Reference in a new issue