From a50f2c12a848b51bfb997adda1b893a1c7e74d69 Mon Sep 17 00:00:00 2001 From: Bart van der Braak <bartvdbraak@gmail.com> Date: Wed, 21 Feb 2024 02:09:02 +0100 Subject: [PATCH] feat: working of some --- src/routes/(user)/settings/+layout.svelte | 2 +- .../(user)/settings/account/+page.server.ts | 55 +++++- .../(user)/settings/account/+page.svelte | 10 +- .../settings/account/account-form.svelte | 180 ------------------ .../(user)/settings/account/email-form.svelte | 134 +++++++++++++ .../settings/account/password-form.svelte | 92 +++++++++ .../settings/account/username-form.svelte | 76 ++++++++ .../appearance/appearance-form.svelte | 157 ++++++++------- .../(user)/settings/profile-form.svelte | 10 +- 9 files changed, 459 insertions(+), 257 deletions(-) delete mode 100644 src/routes/(user)/settings/account/account-form.svelte create mode 100644 src/routes/(user)/settings/account/email-form.svelte create mode 100644 src/routes/(user)/settings/account/password-form.svelte create mode 100644 src/routes/(user)/settings/account/username-form.svelte diff --git a/src/routes/(user)/settings/+layout.svelte b/src/routes/(user)/settings/+layout.svelte index 10bc0b3..e3b27fb 100644 --- a/src/routes/(user)/settings/+layout.svelte +++ b/src/routes/(user)/settings/+layout.svelte @@ -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"> diff --git a/src/routes/(user)/settings/account/+page.server.ts b/src/routes/(user)/settings/account/+page.server.ts index 1eaf695..23277d5 100644 --- a/src/routes/(user)/settings/account/+page.server.ts +++ b/src/routes/(user)/settings/account/+page.server.ts @@ -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, }; diff --git a/src/routes/(user)/settings/account/+page.svelte b/src/routes/(user)/settings/account/+page.svelte index db3e9b4..6918b98 100644 --- a/src/routes/(user)/settings/account/+page.svelte +++ b/src/routes/(user)/settings/account/+page.svelte @@ -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> diff --git a/src/routes/(user)/settings/account/account-form.svelte b/src/routes/(user)/settings/account/account-form.svelte deleted file mode 100644 index eca3eb3..0000000 --- a/src/routes/(user)/settings/account/account-form.svelte +++ /dev/null @@ -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} diff --git a/src/routes/(user)/settings/account/email-form.svelte b/src/routes/(user)/settings/account/email-form.svelte new file mode 100644 index 0000000..caec4d5 --- /dev/null +++ b/src/routes/(user)/settings/account/email-form.svelte @@ -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> diff --git a/src/routes/(user)/settings/account/password-form.svelte b/src/routes/(user)/settings/account/password-form.svelte new file mode 100644 index 0000000..a14e412 --- /dev/null +++ b/src/routes/(user)/settings/account/password-form.svelte @@ -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> diff --git a/src/routes/(user)/settings/account/username-form.svelte b/src/routes/(user)/settings/account/username-form.svelte new file mode 100644 index 0000000..12ff920 --- /dev/null +++ b/src/routes/(user)/settings/account/username-form.svelte @@ -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> diff --git a/src/routes/(user)/settings/appearance/appearance-form.svelte b/src/routes/(user)/settings/appearance/appearance-form.svelte index a369093..43f71d8 100644 --- a/src/routes/(user)/settings/appearance/appearance-form.svelte +++ b/src/routes/(user)/settings/appearance/appearance-form.svelte @@ -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> diff --git a/src/routes/(user)/settings/profile-form.svelte b/src/routes/(user)/settings/profile-form.svelte index 1af763d..f77fd3e 100644 --- a/src/routes/(user)/settings/profile-form.svelte +++ b/src/routes/(user)/settings/profile-form.svelte @@ -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}