Refactor UI & Actions #16

Merged
glazk0 merged 1 commit from dev into main 2024-03-30 00:20:46 +01:00
20 changed files with 254 additions and 305 deletions

View file

@ -3,13 +3,17 @@
import * as Breadcrumb from '$lib/components/ui/breadcrumb';
$: segments = $page.url.pathname.slice(1).split('/');
$: breadcrumb = segments.map((segment, index) => {
return {
name: segment.charAt(0).toUpperCase() + segment.slice(1),
href: '/' + segments.slice(0, index + 1).join('/')
};
}) as { name: string; href: string }[];
export let breadcrumb: { name: string; href: string }[] = [];
$: page.subscribe(({ url: { pathname } }) => {
breadcrumb = pathname
.split('/')
.slice(1)
.map((segment, index, segments) => ({
name: segment.replace(/-/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase()),
href: '/' + segments.slice(0, index + 1).join('/')
}));
});
</script>
<Breadcrumb.Root>

View file

@ -30,6 +30,7 @@
<Boring name={$page.data.user?.pseudo} variant="beam" />
</Avatar.Fallback>
</Avatar.Root>
<span class="sr-only">Menu utilisateur</span>
</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content class="w-56">

View file

@ -6,12 +6,10 @@
<nav class="w-full border-b border-muted p-4">
<div class="flex items-center justify-between">
<Breadcrumb />
<div class="flex items-center gap-2">
<MobileNav />
<Breadcrumb />
</div>
<div class="flex items-center">
<NavbarUser />
<MobileNav />
</div>
</div>
</nav>

View file

@ -1,19 +1,17 @@
import { z } from 'zod';
export const loginSchema = z.object({
pseudo: z.string({ required_error: "Nom d'utilisateur requis", })
pseudo: z.string()
.trim()
.min(1, { message: "Nom d'utilisateur requis" }),
passwd: z.string({ required_error: 'Mot de passe requis' })
passwd: z.string()
.trim()
.min(1, { message: 'Mot de passe requis' }),
});
export const registerSchema = z.object({
email: z
.string({
required_error: 'Email requis'
})
.string()
.trim()
.max(64, {
message: 'Email trop long (max 64 caractères)'
@ -22,20 +20,15 @@ export const registerSchema = z.object({
message: 'Email invalide'
}),
firstname: z.string()
.trim(),
.trim().min(1, { message: "Prénom requis" }),
lastname: z.string()
.trim(),
pseudo: z.string({
required_error: 'Nom d\'utilisateur requis'
}).trim(),
.trim().min(1, { message: "Nom requis" }),
pseudo: z.string().trim().min(1, { message: "Nom d'utilisateur requis" }),
});
export const registerConfirmationSchema = z.object({
email: z
.string({
required_error: 'Email requis'
})
.string()
.trim()
.max(64, {
message: 'Email trop long (max 64 caractères)'
@ -44,41 +37,61 @@ export const registerConfirmationSchema = z.object({
message: 'Email invalide'
}),
firstname: z.string()
.trim(),
.trim()
.min(1, { message: "Prénom requis" }),
lastname: z.string()
.trim(),
pseudo: z.string({
required_error: 'Nom d\'utilisateur requis'
}).trim(),
passwd: z.string({
required_error: 'Mot de passe requis'
})
.trim()
.min(1, { message: "Nom requis" }),
pseudo: z.string().trim().min(1, { message: "Nom d'utilisateur requis" }),
passwd: z.string()
.trim()
.min(1, { message: 'Mot de passe requis' }),
code: z.string({
required_error: 'Code manquant'
})
confirm: z.string()
.trim()
.min(1, { message: 'Confirmation du mot de passe requise' }),
code: z.string()
.trim()
.regex(/^[0-9]{4}$/, { message: 'Code invalide, il doit contenir 4 chiffres' })
.trim(),
}).refine((data) => data.passwd == data.confirm, {
message: 'Les mots de passe ne correspondent pas',
path: ['confirm']
});
export const requestPasswordResetSchema = z.object({
email: z.string({ required_error: 'Email requis' })
email: z.string()
.trim()
.email({ message: 'Email invalide' })
.min(1, { message: 'Email requis' })
.max(64, {
message: 'Email trop long (max 64 caractères)'
})
.email({
message: 'Email invalide'
}),
});
export const resetPasswordSchema = z.object({
email: z.string({ required_error: 'Email requis' })
email: z.string()
.trim()
.email({ message: 'Email invalide' })
.min(1, { message: 'Email requis' }),
password: z.string({ required_error: 'Mot de passe requis' })
.max(64, {
message: 'Email trop long (max 64 caractères)'
})
.email({
message: 'Email invalide'
}),
password: z.string()
.trim()
.min(1, { message: 'Mot de passe requis' }),
code: z.string({
required_error: 'Code manquant'
})
.regex(/^[0-9]{4}$/, { message: 'Code invalide, il doit contenir 4 chiffres' }),
confirm: z.string()
.trim()
.min(1, { message: 'Confirmation du mot de passe requise' }),
code: z.string()
.regex(/^[0-9]{4}$/, { message: 'Code invalide, il doit contenir 4 chiffres' }),
}).refine((data) => data.password == data.confirm, {
message: 'Les mots de passe ne correspondent pas',
path: ['confirm']
});
export const settingSchema = z.object({
firstname: z.string().trim().min(1, { message: "Prénom requis" }),
lastname: z.string().trim().min(1, { message: "Nom requis" }),
pseudo: z.string().trim().min(1, { message: "Nom d'utilisateur requis" }),
});

View file

@ -1,8 +1,5 @@
import { z } from 'zod';
export const groupSchema = z.object({
name: z.string({
required_error: 'Un nom est requis',
})
.min(1, 'Un nom est requis',),
name: z.string().trim().min(1, 'Un nom de groupe est requis'),
});

View file

@ -1,8 +1,6 @@
import { z } from 'zod';
export const puzzleSchema = z.object({
answer: z.string({
required_error: 'Une réponse est requise',
})
answer: z.string()
.min(1, 'Une réponse est requise')
});

View file

@ -34,15 +34,7 @@
>
<div class="flex w-full flex-col justify-between md:flex-row md:items-center md:gap-4">
<span class="text-lg font-semibold">{data.event.name}</span>
{#if data.event.start && data.event.end}
<span class="text-muted-foreground">
{new Date(data.event.start).toLocaleDateString()} - {new Date(
data.event.end
).toLocaleDateString()}
</span>
{:else}
<span class="text-muted-foreground">Aucune date</span>
{/if}
<span class="text-muted-foreground"> Participer en équipe de 1 à 4 joueurs </span>
</div>
<Button href="/chapters/{data.event.id}">Participer</Button>
</div>

View file

@ -29,7 +29,7 @@
Voir les groupes
</Button>
{#if data.chapter.start && data.chapter.end}
<Button href="/leaderboard/{data.chapter.id}">
<Button href="/chapters/{data.chapter.id}/leaderboard">
<BarChart2 class="mr-2 h-4 w-4" />
Voir le classement
</Button>

View file

@ -1,12 +1,10 @@
import { API_URL } from '$env/static/private';
import type { PageServerLoad } from './$types';
import { redirect } from '@sveltejs/kit';
import type { LeaderboardEvent } from '$lib/types';
import type { PageServerLoad } from './$types';
export const load = (async ({ locals: { user }, fetch, cookies, params: { chapterId } }) => {
export const load: PageServerLoad = async ({ locals: { user }, fetch, cookies, params: { chapterId } }) => {
if (!user) redirect(302, '/login');
@ -20,14 +18,18 @@ export const load = (async ({ locals: { user }, fetch, cookies, params: { chapte
}
});
if (!res.ok) redirect(302, '/');
if (!res.ok) return {
leaderboard: []
}
const leaderboard = (await res.json()) as LeaderboardEvent;
if (!leaderboard) redirect(302, '/');
if (!leaderboard) return {
leaderboard: []
}
return {
title: "Classement",
leaderboard
};
}) satisfies PageServerLoad;
};

View file

@ -26,14 +26,15 @@
</Button>
</div>
</header>
<main class="flex flex-col justify-between gap-4 pb-4">
<main class="pb-4">
<div class="overflow-x-auto">
<table class="w-full min-w-max table-auto">
<table class="w-full min-w-max table">
<thead
class="border-x border-b border-t border-border bg-card/50 text-sm text-muted-foreground"
>
<tr>
<th scope="col" class="text-left">#</th>
<th scope="col" class="text-left">Equipe</th>
<th scope="col" class="text-left">Joueurs</th>
<th scope="col" class="text-right">Score</th>
<th scope="col" class="text-right">Essais</th>
@ -42,14 +43,15 @@
<tbody class="border-x border-b border-border bg-card align-middle">
{#if !data.leaderboard.groups.length}
<tr>
<td colspan="4" class="text-center text-muted-foreground"
>Aucun groupe n'a encore de score</td
>
<td colspan="5" class="text-center text-muted-foreground">
Aucun groupe n'a encore de score
</td>
</tr>
{:else}
{#each data.leaderboard.groups.filter( (g) => g.players.reduce((a, b) => a + b.score, 0) ) as group (group)}
<tr class={cn(SCORE_COLORS[group.rank - 1])}>
<td>{group.rank}</td>
<td>{group.name}</td>
<td class="text-lg">
{#if group.players?.length}
<span>{group.players.map((player) => player?.pseudo).join(', ')} </span>

View file

@ -19,7 +19,7 @@ export const load = (async ({ locals: { user }, fetch, cookies }) => {
if (!res.ok) {
return {
leaderboard: [] as Leaderboard[]
leaderboard: []
};
}

View file

@ -15,10 +15,12 @@
<p class="text-muted-foreground">Suivez la progression des élèves en direct</p>
</div>
</header>
<main class="flex flex-col justify-between gap-4 pb-4">
<main class="pb-4">
<div class="overflow-x-auto">
<table class="w-full min-w-max table-auto">
<thead class="border-x border-b text-muted-foreground border-t border-border bg-card/50 text-sm">
<table class="table w-full min-w-max">
<thead
class="border-x border-b border-t border-border bg-card/50 text-sm text-muted-foreground"
>
<tr>
<th scope="col" class="text-left">#</th>
<th scope="col" class="text-left">Nom</th>

View file

@ -2,17 +2,11 @@ import { API_URL } from '$env/static/private';
import { fail, redirect, type Actions } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
import { settingSchema } from '$lib/validations/auth';
import { zod } from 'sveltekit-superforms/adapters';
import { superValidate } from 'sveltekit-superforms/server';
import { z } from 'zod';
import { setError, superValidate } from 'sveltekit-superforms/server';
const settingSchema = z.object({
firstname: z.string().min(1, { message: "Prénom requis" }),
lastname: z.string().min(1, { message: "Nom requis" }),
pseudo: z.string().min(1, { message: "Nom d'utilisateur requi" }),
});
export const load = (async ({ locals: { user } }) => {
export const load: PageServerLoad = async ({ locals: { user } }) => {
if (!user) redirect(302, '/login');
const form = await superValidate(user, zod(settingSchema));
@ -21,10 +15,13 @@ export const load = (async ({ locals: { user } }) => {
title: 'Paramètres',
form
};
}) satisfies PageServerLoad;
};
export const actions: Actions = {
default: async ({ request, cookies, locals: { user } }) => {
if (!user) return fail(401);
export const actions = {
default: async ({ request, cookies }) => {
const session = cookies.get('session');
const form = await superValidate(request, zod(settingSchema));
@ -38,21 +35,20 @@ export const actions = {
headers: {
Authorization: `Bearer ${session}`
},
body: JSON.stringify(form.data)
body: JSON.stringify({
...form.data
})
});
if (res.ok) {
return {
success: true
};
if (!res.ok) {
if (res.status === 400) {
return setError(form, "pseudo", "Ce pseudo est déjà utilisé");
}
return setError(form, "pseudo", "Une erreur est survenue lors de la sauvegarde des paramètres");
}
if (res.status === 400) {
form.errors.pseudo = ['Le pseudo est déjà utilisé'];
return fail(400, { form });
}
return fail(500, { form });
return {
form
};
}
} satisfies Actions;
};

View file

@ -1,106 +1,76 @@
<script lang="ts">
import { page } from '$app/stores';
import type { PageData } from './$types';
import { Loader2 } from 'lucide-svelte';
import Loader from 'lucide-svelte/icons/loader-circle';
import { toast } from 'svelte-sonner';
import { zodClient } from 'sveltekit-superforms/adapters';
import { superForm } from 'sveltekit-superforms/client';
import Button from '$lib/components/ui/button/button.svelte';
import * as Form from '$lib/components/ui/form';
import Input from '$lib/components/ui/input/input.svelte';
import plausible from '$lib/stores/plausible';
import Label from '$lib/components/ui/label/label.svelte';
import { settingSchema } from '$lib/validations/auth';
export let data: PageData;
let submitting = false;
const { form, errors, enhance } = superForm(data.form, {
onSubmit() {
submitting = true;
},
const form = superForm(data.form, {
validators: zodClient(settingSchema),
delayMs: 500,
multipleSubmits: 'prevent',
onResult({ result }) {
if (result.type === 'success') {
toast.message('Succès', { description: 'Vos informations ont été mises à jour.' });
}
submitting = false;
}
});
const { form: formData, enhance, delayed } = form;
// TODO: Handle overflow X on small screens
</script>
<section>
<form class="flex flex-col gap-4" method="POST" use:enhance>
<form class="flex flex-col gap-2" method="POST" use:enhance>
<div class="flex items-center justify-between">
<h2 class="text-xl font-bold">Paramètres généraux</h2>
<Button type="submit" variant="default" disabled={submitting}>
{#if submitting}
<Loader2 class="mr-2 h-4 w-4 animate-spin" />
<Form.Button disabled={$delayed}>
{#if $delayed}
<Loader class="mr-2 h-4 w-4 animate-spin" />
{/if}
Modifier
</Button>
</Form.Button>
</div>
<label for="email">Email</label>
<Label>Email</Label>
<Input
name="email"
type="email"
placeholder="philipzcwbarlow@peerat.dev"
value={data.user?.email}
value={$page.data.user?.email}
disabled
placeholder="philiphzcwbarlow@peerat.dev"
/>
<label for="firstname">Prénom</label>
<Input
name="firstname"
type="text"
placeholder="Philip"
aria-invalid={$errors.firstname ? 'true' : undefined}
bind:value={$form.firstname}
/>
{#if $errors.firstname}
<span class="text-sm text-red-500">{$errors.firstname}</span>
{/if}
<label for="lastname">Nom</label>
<Input
name="lastname"
type="text"
placeholder="Barlow"
aria-invalid={$errors.lastname ? 'true' : undefined}
bind:value={$form.lastname}
/>
{#if $errors.lastname}
<span class="text-sm text-red-500">{$errors.lastname}</span>
{/if}
<label for="pseudo"> Nom d'utilisateur </label>
<Input
name="pseudo"
type="text"
placeholder="Cypher Wolf"
aria-invalid={$errors.pseudo ? 'true' : undefined}
bind:value={$form.pseudo}
/>
{#if $errors.pseudo}
<span class="text-sm text-red-500">{$errors.pseudo}</span>
{/if}
<div class="flex items-center justify-between">
<label for="optout"> Ne pas me tracer de manière anonyme </label>
<input
class="h-4 w-4"
name="optout"
type="checkbox"
bind:value={$plausible}
on:change={() => plausible.set(!$plausible)}
checked={$plausible}
/>
</div>
<p class="text-highlight-secondary text-sm">
Nous utilisons Plausible pour analyser l'utilisation de notre site web de manière anonyme.
</p>
<p class="text-sm text-muted-foreground">Votre email ne peut pas être modifié.</p>
<Form.Field {form} name="firstname">
<Form.Control let:attrs>
<Form.Label>Prénom</Form.Label>
<Input {...attrs} bind:value={$formData.firstname} placeholder="Philip" />
</Form.Control>
<Form.FieldErrors />
</Form.Field>
<Form.Field {form} name="lastname">
<Form.Control let:attrs>
<Form.Label>Nom de famille</Form.Label>
<Input {...attrs} bind:value={$formData.lastname} placeholder="Barlow" />
</Form.Control>
<Form.FieldErrors />
</Form.Field>
<Form.Field {form} name="pseudo">
<Form.Control let:attrs>
<Form.Label>Nom d'utilisateur</Form.Label>
<Input {...attrs} bind:value={$formData.pseudo} placeholder="Cypherwolf" />
</Form.Control>
<Form.Description>Ce nom sera visible par les autres utilisateurs.</Form.Description>
<Form.FieldErrors />
</Form.Field>
</form>
</section>

View file

@ -4,7 +4,7 @@ import { fail, redirect, type Actions } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
import { zod } from 'sveltekit-superforms/adapters';
import { superValidate } from 'sveltekit-superforms/server';
import { setError, superValidate } from 'sveltekit-superforms/server';
import { loginSchema } from '$lib/validations/auth';
@ -37,19 +37,13 @@ export const actions: Actions = {
});
if (!res.ok) {
form.errors.passwd = ["Nom d'utilisateur ou mot de passe incorrect"];
return fail(400, { form });
return setError(form, 'passwd', "Nom d'utilisateur ou mot de passe incorrect");
}
const token = res.headers.get('Authorization')?.split(' ').pop();
const token = res.headers.get('Authorization')?.split('Bearer ').pop();
if (!token) {
form.errors.passwd = ["Une erreur est survenue, veuillez réessayer plus tard"];
return fail(500, { form });
return setError(form, 'passwd', "Une erreur est survenue, veuillez réessayer plus tard");
}
cookies.set('session', token, {

View file

@ -1,7 +1,7 @@
<script lang="ts">
import type { PageData } from './$types';
import { Loader2 } from 'lucide-svelte';
import Loader from 'lucide-svelte/icons/loader-circle';
import { zodClient } from 'sveltekit-superforms/adapters';
import { superForm } from 'sveltekit-superforms/client';
@ -31,7 +31,7 @@
<div class="container flex h-screen">
<div class="flex w-full flex-col items-center justify-center">
<div class="flex w-full max-w-xs flex-col gap-4">
<h1 class="mx-auto text-xl font-bold">Connexion</h1>
<h2 class="mx-auto text-xl font-bold">Connexion</h2>
<form class="flex flex-col justify-center gap-2" method="POST" use:enhance>
<Form.Field {form} name="pseudo">
<Form.Control let:attrs>
@ -54,7 +54,7 @@
</Form.Field>
<Form.Button disabled={$delayed}>
{#if $delayed}
<Loader2 class="mr-2 h-4 w-4 animate-spin" />
<Loader class="mr-2 h-4 w-4 animate-spin" />
{/if}
Se connecter
</Form.Button>

View file

@ -6,12 +6,11 @@ import { fail, redirect, type Actions } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
import { zod } from 'sveltekit-superforms/adapters';
import { superValidate } from 'sveltekit-superforms/server';
import { setError, superValidate } from 'sveltekit-superforms/server';
import { registerConfirmationSchema, registerSchema } from '$lib/validations/auth';
export const load = (async ({ locals: { user } }) => {
export const load: PageServerLoad = async ({ locals: { user } }) => {
if (user) redirect(302, '/');
const registerForm = await superValidate(zod(registerSchema));
@ -22,10 +21,11 @@ export const load = (async ({ locals: { user } }) => {
registerForm,
registerConfirmationForm
};
}) satisfies PageServerLoad;
}
export const actions = {
export const actions: Actions = {
register: async ({ request }) => {
const form = await superValidate(request, zod(registerSchema));
if (!form.valid) {
@ -39,26 +39,19 @@ export const actions = {
})
});
if (res.ok) {
return {
success: true
};
if (!res.ok) {
if (res.status === 400) {
const { email_valid, username_valid } = await res.json();
if (!email_valid) return setError(form, 'email', 'Un compte avec cette adresse email existe déjà');
if (!username_valid) return setError(form, 'pseudo', 'Ce pseudo est déjà utilisé');
}
return setError(form, 'email', "Une erreur est survenue lors de l'inscription");
}
if (res.status === 400) {
const { email_valid, username_valid } = await res.json();
if (!email_valid) form.errors.email = ['Un compte avec cette adresse email existe déjà'];
if (!username_valid) form.errors.pseudo = ['Ce pseudo est déjà utilisé'];
return fail(400, { form });
}
form.errors.pseudo = ["Une erreur s'est produite"];
return fail(400, {
return {
form
});
};
},
confirmation: async ({ request, cookies }) => {
const form = await superValidate(request, zod(registerConfirmationSchema));
@ -79,46 +72,27 @@ export const actions = {
})
});
if (res.ok) {
const token = res.headers.get('Authorization')?.split('Bearer ')[1];
if (!token) {
form.errors.code = [`Une erreur s'est produite lors de la confirmation de votre compte.`];
return fail(400, {
form
});
}
cookies.set('session', token, {
path: '/',
secure: !dev,
sameSite: 'strict',
});
redirect(302, '/');
}
if (res.status === 400) {
try {
if (!res.ok) {
if (res.status === 400) {
const { email_valid, username_valid } = await res.json();
if (email_valid) form.errors.email = ['Un compte avec cette adresse email existe déjà'];
if (username_valid) form.errors.pseudo = ['Ce pseudo est déjà utilisé'];
return fail(400, { form });
} catch (e) {
console.error(e);
form.errors.code = ['Le code envoyé est invalide.'];
return fail(400, { form });
if (!email_valid) return setError(form, 'email', 'Un compte avec cette adresse email existe déjà');
if (!username_valid) return setError(form, 'pseudo', "Ce nom d'utilisateur est déjà utilisé");
}
return setError(form, 'code', "Une erreur est survenue lors de la confirmation");
}
form.errors.code = [`Le code envoyé est invalide.`];
const token = res.headers.get('Authorization')?.split('Bearer ').pop();
return fail(400, {
form
if (!token) {
return setError(form, 'code', "Une erreur est survenue, veuillez réessayer plus tard");
}
cookies.set('session', token, {
path: '/',
secure: !dev,
sameSite: 'strict',
});
}
} satisfies Actions;
redirect(302, '/');
},
}

View file

@ -1,17 +1,17 @@
<script lang="ts">
import { fade } from 'svelte/transition';
import type { PageData, Snapshot } from './$types';
import type { PageData } from './$types';
import Loader from 'lucide-svelte/icons/loader-circle';
import { toast } from 'svelte-sonner';
import { zod, zodClient } from 'sveltekit-superforms/adapters';
import { zodClient } from 'sveltekit-superforms/adapters';
import { superForm } from 'sveltekit-superforms/client';
import * as Form from '$lib/components/ui/form';
import Input from '$lib/components/ui/input/input.svelte';
import { registerConfirmationSchema, registerSchema } from '$lib/validations/auth';
import Button from '$lib/components/ui/button/button.svelte';
import { enhance } from '$app/forms';
export let data: PageData;
@ -21,6 +21,7 @@
validators: zodClient(registerSchema),
delayMs: 500,
multipleSubmits: 'prevent',
invalidateAll: false,
onResult({ result }) {
switch (result.type) {
case 'success':
@ -30,6 +31,7 @@
registerConfirmationFormData.set({
...$registerFormData,
passwd: '',
confirm: '',
code: ''
});
confirmation = true;
@ -38,9 +40,6 @@
(field as HTMLInputElement)?.focus();
}, 100);
break;
case 'error':
confirmation = false;
break;
}
}
});
@ -52,7 +51,7 @@
} = registerForm;
const registerConfirmationForm = superForm(data.registerConfirmationForm, {
validators: zod(registerConfirmationSchema),
validators: zodClient(registerConfirmationSchema),
delayMs: 500,
multipleSubmits: 'prevent'
});
@ -62,18 +61,13 @@
enhance: registerConfirmationEnhance,
delayed: registerConfirmationDelayed
} = registerConfirmationForm;
export const snapshot: Snapshot = {
capture: () => confirmation,
restore: (value) => (confirmation = value)
};
</script>
<div class="container flex h-screen">
<div class="flex w-full flex-col items-center justify-center">
<div class="flex w-full max-w-xs flex-col gap-4">
{#if confirmation}
<h1 class="mx-auto text-xl font-bold">Confirmation</h1>
<h2 class="mx-auto text-xl font-bold">Confirmation</h2>
<form
class="flex flex-col justify-center gap-2"
method="POST"
@ -143,6 +137,18 @@
</Form.Control>
<Form.FieldErrors />
</Form.Field>
<Form.Field form={registerConfirmationForm} name="confirm">
<Form.Control let:attrs>
<Form.Label>Confirmer le mot de passe</Form.Label>
<Input
{...attrs}
type="password"
bind:value={$registerConfirmationFormData.confirm}
placeholder="************"
/>
</Form.Control>
<Form.FieldErrors />
</Form.Field>
<Form.Field form={registerConfirmationForm} name="code">
<Form.Control let:attrs>
<Form.Label>Code</Form.Label>
@ -156,16 +162,12 @@
</Form.Field>
</div>
<Form.Button type="submit" disabled={$registerConfirmationDelayed}>
<Form.Button disabled={$registerConfirmationDelayed}>
{#if $registerConfirmationDelayed}
<Loader class="mr-2 h-4 w-4 animate-spin" />
{/if}
Continuer
</Form.Button>
<!-- <Button variant="link" formaction="?/register" on:click={() => toast('Code renvoyé')}>
Renvoyer le code
</Button> -->
</form>
{:else}
<h1 class="mx-auto text-xl font-bold">Inscription</h1>
@ -208,14 +210,12 @@
</Form.Control>
<Form.FieldErrors />
</Form.Field>
<div class="flex flex-col gap-2">
<Form.Button type="submit" disabled={$registerDelayed}>
{#if $registerDelayed}
<Loader className="mr-2 h-4 w-4 animate-spin" />
{/if}
S'inscrire
</Form.Button>
</div>
<Form.Button disabled={$registerDelayed}>
{#if $registerDelayed}
<Loader className="mr-2 h-4 w-4 animate-spin" />
{/if}
S'inscrire
</Form.Button>
</form>
{/if}
<ul class="flex justify-between">

View file

@ -6,7 +6,7 @@ import { fail, redirect, type Actions } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
import { zod } from 'sveltekit-superforms/adapters';
import { superValidate } from 'sveltekit-superforms/server';
import { setError, superValidate } from 'sveltekit-superforms/server';
import { requestPasswordResetSchema, resetPasswordSchema } from '$lib/validations/auth';
@ -23,7 +23,7 @@ export const load = (async ({ locals: { user } }) => {
};
}) satisfies PageServerLoad;
export const actions = {
export const actions: Actions = {
request: async ({ request, fetch }) => {
const form = await superValidate(request, zod(requestPasswordResetSchema));
@ -33,16 +33,17 @@ export const actions = {
const res = await fetch(`${API_URL}/user/fpw`, {
method: 'POST',
body: JSON.stringify(form.data)
body: JSON.stringify({
...form.data
})
});
if (!res.ok) {
form.errors.email = ["Une erreur s'est produite ou l'email n'existe pas"];
return fail(400, { form });
return setError(form, 'email', "Une erreur s'est produite ou l'email n'existe pas");
}
return {
success: true
form
}
},
confirmation: async ({ request, cookies, fetch }) => {
@ -62,11 +63,10 @@ export const actions = {
});
if (res.ok) {
const token = res.headers.get('Authorization')?.split('Bearer ')[1];
const token = res.headers.get('Authorization')?.split('Bearer ').pop();
if (!token) {
form.errors.code = ["Une erreur s'est produite"];
return fail(400, { form });
return setError(form, 'code', "Une erreur est survenue, veuillez réessayer plus tard");
}
cookies.set('session', token, {
@ -79,13 +79,9 @@ export const actions = {
}
if (res.status === 400) {
form.errors.code = ['Code invalide'];
} else {
form.errors.code = [`Une erreur s'est produite`];
return setError(form, 'code', "Le code de confirmation est incorrect");
}
return fail(400, {
form
});
return setError(form, 'code', "Une erreur est survenue, veuillez réessayer plus tard");
}
} satisfies Actions;
}

View file

@ -1,15 +1,15 @@
<script lang="ts">
import { fade } from 'svelte/transition';
import type { PageData, Snapshot } from './$types';
import type { PageData } from './$types';
import { Loader2 } from 'lucide-svelte';
import Loader from 'lucide-svelte/icons/loader-circle';
import { toast } from 'svelte-sonner';
import { zodClient } from 'sveltekit-superforms/adapters';
import { superForm } from 'sveltekit-superforms/client';
import * as Form from '$lib/components/ui/form';
import Input from '$lib/components/ui/input/input.svelte';
import { requestPasswordResetSchema } from '$lib/validations/auth';
import { requestPasswordResetSchema, resetPasswordSchema } from '$lib/validations/auth';
export let data: PageData;
@ -19,6 +19,7 @@
validators: zodClient(requestPasswordResetSchema),
delayMs: 500,
multipleSubmits: 'prevent',
invalidateAll: false,
onResult({ result }) {
switch (result.type) {
case 'success':
@ -28,6 +29,7 @@
resetPasswordFormData.set({
...$requestPasswordResetFormData,
password: '',
confirm: '',
code: ''
});
confirmation = true;
@ -50,6 +52,7 @@
} = requestPasswordResetForm;
const resetPasswordForm = superForm(data.resetPasswordForm, {
validators: zodClient(resetPasswordSchema),
delayMs: 500,
multipleSubmits: 'prevent'
});
@ -59,18 +62,13 @@
enhance: resetPasswordEnhance,
delayed: resetPasswordDelayed
} = resetPasswordForm;
export const snapshot: Snapshot = {
capture: () => confirmation,
restore: (value) => (confirmation = value)
};
</script>
<div class="container flex h-screen">
<div class="flex w-full flex-col items-center justify-center">
<div class="flex w-full max-w-xs flex-col gap-4">
{#if confirmation}
<h1 class="mx-auto text-xl font-bold">Confirmation</h1>
<h2 class="mx-auto text-xl font-bold">Confirmation</h2>
<form
class="flex flex-col justify-center gap-2"
method="POST"
@ -107,6 +105,18 @@
</Form.Control>
<Form.FieldErrors />
</Form.Field>
<Form.Field form={resetPasswordForm} name="confirm">
<Form.Control let:attrs>
<Form.Label>Confirmer le mot de passe</Form.Label>
<Input
{...attrs}
type="password"
bind:value={$resetPasswordFormData.confirm}
placeholder="************"
/>
</Form.Control>
<Form.FieldErrors />
</Form.Field>
<Form.Field form={resetPasswordForm} name="code">
<Form.Control let:attrs>
<Form.Label>Code</Form.Label>
@ -118,7 +128,7 @@
<Form.Button disabled={$resetPasswordDelayed}>
{#if $resetPasswordDelayed}
<Loader2 class="mr-2 h-4 w-4 animate-spin" />
<Loader class="mr-2 h-4 w-4 animate-spin" />
{/if}
Changer le mot de passe
</Form.Button>
@ -157,7 +167,7 @@
</Form.Field>
<Form.Button disabled={$requestPasswordResetDelayed}>
{#if $requestPasswordResetDelayed}
<Loader2 class="mr-2 h-4 w-4 animate-spin" />
<Loader class="mr-2 h-4 w-4 animate-spin" />
{/if}
Continuer
</Form.Button>