Refactor UI & Actions #16
20 changed files with 254 additions and 305 deletions
|
@ -3,13 +3,17 @@
|
||||||
|
|
||||||
import * as Breadcrumb from '$lib/components/ui/breadcrumb';
|
import * as Breadcrumb from '$lib/components/ui/breadcrumb';
|
||||||
|
|
||||||
$: segments = $page.url.pathname.slice(1).split('/');
|
export let breadcrumb: { name: string; href: string }[] = [];
|
||||||
$: breadcrumb = segments.map((segment, index) => {
|
|
||||||
return {
|
$: page.subscribe(({ url: { pathname } }) => {
|
||||||
name: segment.charAt(0).toUpperCase() + segment.slice(1),
|
breadcrumb = pathname
|
||||||
href: '/' + segments.slice(0, index + 1).join('/')
|
.split('/')
|
||||||
};
|
.slice(1)
|
||||||
}) as { name: string; href: string }[];
|
.map((segment, index, segments) => ({
|
||||||
|
name: segment.replace(/-/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase()),
|
||||||
|
href: '/' + segments.slice(0, index + 1).join('/')
|
||||||
|
}));
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Breadcrumb.Root>
|
<Breadcrumb.Root>
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
<Boring name={$page.data.user?.pseudo} variant="beam" />
|
<Boring name={$page.data.user?.pseudo} variant="beam" />
|
||||||
</Avatar.Fallback>
|
</Avatar.Fallback>
|
||||||
</Avatar.Root>
|
</Avatar.Root>
|
||||||
|
<span class="sr-only">Menu utilisateur</span>
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenu.Trigger>
|
</DropdownMenu.Trigger>
|
||||||
<DropdownMenu.Content class="w-56">
|
<DropdownMenu.Content class="w-56">
|
||||||
|
|
|
@ -6,12 +6,10 @@
|
||||||
|
|
||||||
<nav class="w-full border-b border-muted p-4">
|
<nav class="w-full border-b border-muted p-4">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
|
<Breadcrumb />
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<MobileNav />
|
|
||||||
<Breadcrumb />
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center">
|
|
||||||
<NavbarUser />
|
<NavbarUser />
|
||||||
|
<MobileNav />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
|
@ -1,19 +1,17 @@
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
export const loginSchema = z.object({
|
export const loginSchema = z.object({
|
||||||
pseudo: z.string({ required_error: "Nom d'utilisateur requis", })
|
pseudo: z.string()
|
||||||
.trim()
|
.trim()
|
||||||
.min(1, { message: "Nom d'utilisateur requis" }),
|
.min(1, { message: "Nom d'utilisateur requis" }),
|
||||||
passwd: z.string({ required_error: 'Mot de passe requis' })
|
passwd: z.string()
|
||||||
.trim()
|
.trim()
|
||||||
.min(1, { message: 'Mot de passe requis' }),
|
.min(1, { message: 'Mot de passe requis' }),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const registerSchema = z.object({
|
export const registerSchema = z.object({
|
||||||
email: z
|
email: z
|
||||||
.string({
|
.string()
|
||||||
required_error: 'Email requis'
|
|
||||||
})
|
|
||||||
.trim()
|
.trim()
|
||||||
.max(64, {
|
.max(64, {
|
||||||
message: 'Email trop long (max 64 caractères)'
|
message: 'Email trop long (max 64 caractères)'
|
||||||
|
@ -22,20 +20,15 @@ export const registerSchema = z.object({
|
||||||
message: 'Email invalide'
|
message: 'Email invalide'
|
||||||
}),
|
}),
|
||||||
firstname: z.string()
|
firstname: z.string()
|
||||||
.trim(),
|
.trim().min(1, { message: "Prénom requis" }),
|
||||||
lastname: z.string()
|
lastname: z.string()
|
||||||
.trim(),
|
.trim().min(1, { message: "Nom requis" }),
|
||||||
pseudo: z.string({
|
pseudo: z.string().trim().min(1, { message: "Nom d'utilisateur requis" }),
|
||||||
required_error: 'Nom d\'utilisateur requis'
|
|
||||||
}).trim(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
export const registerConfirmationSchema = z.object({
|
export const registerConfirmationSchema = z.object({
|
||||||
email: z
|
email: z
|
||||||
.string({
|
.string()
|
||||||
required_error: 'Email requis'
|
|
||||||
})
|
|
||||||
.trim()
|
.trim()
|
||||||
.max(64, {
|
.max(64, {
|
||||||
message: 'Email trop long (max 64 caractères)'
|
message: 'Email trop long (max 64 caractères)'
|
||||||
|
@ -44,41 +37,61 @@ export const registerConfirmationSchema = z.object({
|
||||||
message: 'Email invalide'
|
message: 'Email invalide'
|
||||||
}),
|
}),
|
||||||
firstname: z.string()
|
firstname: z.string()
|
||||||
.trim(),
|
.trim()
|
||||||
|
.min(1, { message: "Prénom requis" }),
|
||||||
lastname: z.string()
|
lastname: z.string()
|
||||||
.trim(),
|
.trim()
|
||||||
pseudo: z.string({
|
.min(1, { message: "Nom requis" }),
|
||||||
required_error: 'Nom d\'utilisateur requis'
|
pseudo: z.string().trim().min(1, { message: "Nom d'utilisateur requis" }),
|
||||||
}).trim(),
|
passwd: z.string()
|
||||||
passwd: z.string({
|
|
||||||
required_error: 'Mot de passe requis'
|
|
||||||
})
|
|
||||||
.trim()
|
.trim()
|
||||||
.min(1, { message: 'Mot de passe requis' }),
|
.min(1, { message: 'Mot de passe requis' }),
|
||||||
code: z.string({
|
confirm: z.string()
|
||||||
required_error: 'Code manquant'
|
.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' })
|
.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({
|
export const requestPasswordResetSchema = z.object({
|
||||||
email: z.string({ required_error: 'Email requis' })
|
email: z.string()
|
||||||
.trim()
|
.trim()
|
||||||
.email({ message: 'Email invalide' })
|
.max(64, {
|
||||||
.min(1, { message: 'Email requis' })
|
message: 'Email trop long (max 64 caractères)'
|
||||||
|
})
|
||||||
|
.email({
|
||||||
|
message: 'Email invalide'
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const resetPasswordSchema = z.object({
|
export const resetPasswordSchema = z.object({
|
||||||
email: z.string({ required_error: 'Email requis' })
|
email: z.string()
|
||||||
.trim()
|
.trim()
|
||||||
.email({ message: 'Email invalide' })
|
.max(64, {
|
||||||
.min(1, { message: 'Email requis' }),
|
message: 'Email trop long (max 64 caractères)'
|
||||||
password: z.string({ required_error: 'Mot de passe requis' })
|
})
|
||||||
|
.email({
|
||||||
|
message: 'Email invalide'
|
||||||
|
}),
|
||||||
|
password: z.string()
|
||||||
.trim()
|
.trim()
|
||||||
.min(1, { message: 'Mot de passe requis' }),
|
.min(1, { message: 'Mot de passe requis' }),
|
||||||
code: z.string({
|
confirm: z.string()
|
||||||
required_error: 'Code manquant'
|
.trim()
|
||||||
})
|
.min(1, { message: 'Confirmation du mot de passe requise' }),
|
||||||
.regex(/^[0-9]{4}$/, { message: 'Code invalide, il doit contenir 4 chiffres' }),
|
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" }),
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
export const groupSchema = z.object({
|
export const groupSchema = z.object({
|
||||||
name: z.string({
|
name: z.string().trim().min(1, 'Un nom de groupe est requis'),
|
||||||
required_error: 'Un nom est requis',
|
|
||||||
})
|
|
||||||
.min(1, 'Un nom est requis',),
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
export const puzzleSchema = z.object({
|
export const puzzleSchema = z.object({
|
||||||
answer: z.string({
|
answer: z.string()
|
||||||
required_error: 'Une réponse est requise',
|
|
||||||
})
|
|
||||||
.min(1, 'Une réponse est requise')
|
.min(1, 'Une réponse est requise')
|
||||||
});
|
});
|
||||||
|
|
|
@ -34,15 +34,7 @@
|
||||||
>
|
>
|
||||||
<div class="flex w-full flex-col justify-between md:flex-row md:items-center md:gap-4">
|
<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>
|
<span class="text-lg font-semibold">{data.event.name}</span>
|
||||||
{#if data.event.start && data.event.end}
|
<span class="text-muted-foreground"> Participer en équipe de 1 à 4 joueurs </span>
|
||||||
<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}
|
|
||||||
</div>
|
</div>
|
||||||
<Button href="/chapters/{data.event.id}">Participer</Button>
|
<Button href="/chapters/{data.event.id}">Participer</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
Voir les groupes
|
Voir les groupes
|
||||||
</Button>
|
</Button>
|
||||||
{#if data.chapter.start && data.chapter.end}
|
{#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" />
|
<BarChart2 class="mr-2 h-4 w-4" />
|
||||||
Voir le classement
|
Voir le classement
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
import { API_URL } from '$env/static/private';
|
import { API_URL } from '$env/static/private';
|
||||||
|
import type { PageServerLoad } from './$types';
|
||||||
import { redirect } from '@sveltejs/kit';
|
import { redirect } from '@sveltejs/kit';
|
||||||
|
|
||||||
import type { LeaderboardEvent } from '$lib/types';
|
import type { LeaderboardEvent } from '$lib/types';
|
||||||
|
|
||||||
import type { PageServerLoad } from './$types';
|
export const load: PageServerLoad = async ({ locals: { user }, fetch, cookies, params: { chapterId } }) => {
|
||||||
|
|
||||||
export const load = (async ({ locals: { user }, fetch, cookies, params: { chapterId } }) => {
|
|
||||||
|
|
||||||
if (!user) redirect(302, '/login');
|
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;
|
const leaderboard = (await res.json()) as LeaderboardEvent;
|
||||||
|
|
||||||
if (!leaderboard) redirect(302, '/');
|
if (!leaderboard) return {
|
||||||
|
leaderboard: []
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: "Classement",
|
title: "Classement",
|
||||||
leaderboard
|
leaderboard
|
||||||
};
|
};
|
||||||
}) satisfies PageServerLoad;
|
};
|
|
@ -26,14 +26,15 @@
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<main class="flex flex-col justify-between gap-4 pb-4">
|
<main class="pb-4">
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
<table class="w-full min-w-max table-auto">
|
<table class="w-full min-w-max table">
|
||||||
<thead
|
<thead
|
||||||
class="border-x border-b border-t border-border bg-card/50 text-sm text-muted-foreground"
|
class="border-x border-b border-t border-border bg-card/50 text-sm text-muted-foreground"
|
||||||
>
|
>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col" class="text-left">#</th>
|
<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-left">Joueurs</th>
|
||||||
<th scope="col" class="text-right">Score</th>
|
<th scope="col" class="text-right">Score</th>
|
||||||
<th scope="col" class="text-right">Essais</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">
|
<tbody class="border-x border-b border-border bg-card align-middle">
|
||||||
{#if !data.leaderboard.groups.length}
|
{#if !data.leaderboard.groups.length}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="4" class="text-center text-muted-foreground"
|
<td colspan="5" class="text-center text-muted-foreground">
|
||||||
>Aucun groupe n'a encore de score</td
|
Aucun groupe n'a encore de score
|
||||||
>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{:else}
|
{:else}
|
||||||
{#each data.leaderboard.groups.filter( (g) => g.players.reduce((a, b) => a + b.score, 0) ) as group (group)}
|
{#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])}>
|
<tr class={cn(SCORE_COLORS[group.rank - 1])}>
|
||||||
<td>{group.rank}</td>
|
<td>{group.rank}</td>
|
||||||
|
<td>{group.name}</td>
|
||||||
<td class="text-lg">
|
<td class="text-lg">
|
||||||
{#if group.players?.length}
|
{#if group.players?.length}
|
||||||
<span>{group.players.map((player) => player?.pseudo).join(', ')} </span>
|
<span>{group.players.map((player) => player?.pseudo).join(', ')} </span>
|
|
@ -19,7 +19,7 @@ export const load = (async ({ locals: { user }, fetch, cookies }) => {
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
return {
|
return {
|
||||||
leaderboard: [] as Leaderboard[]
|
leaderboard: []
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,10 +15,12 @@
|
||||||
<p class="text-muted-foreground">Suivez la progression des élèves en direct</p>
|
<p class="text-muted-foreground">Suivez la progression des élèves en direct</p>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<main class="flex flex-col justify-between gap-4 pb-4">
|
<main class="pb-4">
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
<table class="w-full min-w-max table-auto">
|
<table class="table w-full min-w-max">
|
||||||
<thead class="border-x border-b text-muted-foreground border-t border-border bg-card/50 text-sm">
|
<thead
|
||||||
|
class="border-x border-b border-t border-border bg-card/50 text-sm text-muted-foreground"
|
||||||
|
>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col" class="text-left">#</th>
|
<th scope="col" class="text-left">#</th>
|
||||||
<th scope="col" class="text-left">Nom</th>
|
<th scope="col" class="text-left">Nom</th>
|
||||||
|
|
|
@ -2,17 +2,11 @@ import { API_URL } from '$env/static/private';
|
||||||
import { fail, redirect, type Actions } from '@sveltejs/kit';
|
import { fail, redirect, type Actions } from '@sveltejs/kit';
|
||||||
import type { PageServerLoad } from './$types';
|
import type { PageServerLoad } from './$types';
|
||||||
|
|
||||||
|
import { settingSchema } from '$lib/validations/auth';
|
||||||
import { zod } from 'sveltekit-superforms/adapters';
|
import { zod } from 'sveltekit-superforms/adapters';
|
||||||
import { superValidate } from 'sveltekit-superforms/server';
|
import { setError, superValidate } from 'sveltekit-superforms/server';
|
||||||
import { z } from 'zod';
|
|
||||||
|
|
||||||
const settingSchema = z.object({
|
export const load: PageServerLoad = async ({ locals: { user } }) => {
|
||||||
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 } }) => {
|
|
||||||
if (!user) redirect(302, '/login');
|
if (!user) redirect(302, '/login');
|
||||||
|
|
||||||
const form = await superValidate(user, zod(settingSchema));
|
const form = await superValidate(user, zod(settingSchema));
|
||||||
|
@ -21,10 +15,13 @@ export const load = (async ({ locals: { user } }) => {
|
||||||
title: 'Paramètres',
|
title: 'Paramètres',
|
||||||
form
|
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 session = cookies.get('session');
|
||||||
|
|
||||||
const form = await superValidate(request, zod(settingSchema));
|
const form = await superValidate(request, zod(settingSchema));
|
||||||
|
@ -38,21 +35,20 @@ export const actions = {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${session}`
|
Authorization: `Bearer ${session}`
|
||||||
},
|
},
|
||||||
body: JSON.stringify(form.data)
|
body: JSON.stringify({
|
||||||
|
...form.data
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.ok) {
|
if (!res.ok) {
|
||||||
return {
|
if (res.status === 400) {
|
||||||
success: true
|
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) {
|
return {
|
||||||
form.errors.pseudo = ['Le pseudo est déjà utilisé'];
|
form
|
||||||
|
};
|
||||||
return fail(400, { form });
|
|
||||||
}
|
|
||||||
|
|
||||||
return fail(500, { form });
|
|
||||||
}
|
}
|
||||||
} satisfies Actions;
|
};
|
||||||
|
|
|
@ -1,106 +1,76 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { page } from '$app/stores';
|
||||||
import type { PageData } 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 { toast } from 'svelte-sonner';
|
||||||
|
import { zodClient } from 'sveltekit-superforms/adapters';
|
||||||
import { superForm } from 'sveltekit-superforms/client';
|
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 Input from '$lib/components/ui/input/input.svelte';
|
||||||
|
import Label from '$lib/components/ui/label/label.svelte';
|
||||||
import plausible from '$lib/stores/plausible';
|
|
||||||
|
import { settingSchema } from '$lib/validations/auth';
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
|
|
||||||
let submitting = false;
|
const form = superForm(data.form, {
|
||||||
|
validators: zodClient(settingSchema),
|
||||||
const { form, errors, enhance } = superForm(data.form, {
|
delayMs: 500,
|
||||||
onSubmit() {
|
multipleSubmits: 'prevent',
|
||||||
submitting = true;
|
|
||||||
},
|
|
||||||
onResult({ result }) {
|
onResult({ result }) {
|
||||||
if (result.type === 'success') {
|
if (result.type === 'success') {
|
||||||
toast.message('Succès', { description: 'Vos informations ont été mises à jour.' });
|
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
|
// TODO: Handle overflow X on small screens
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section>
|
<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">
|
<div class="flex items-center justify-between">
|
||||||
<h2 class="text-xl font-bold">Paramètres généraux</h2>
|
<h2 class="text-xl font-bold">Paramètres généraux</h2>
|
||||||
<Button type="submit" variant="default" disabled={submitting}>
|
<Form.Button disabled={$delayed}>
|
||||||
{#if submitting}
|
{#if $delayed}
|
||||||
<Loader2 class="mr-2 h-4 w-4 animate-spin" />
|
<Loader class="mr-2 h-4 w-4 animate-spin" />
|
||||||
{/if}
|
{/if}
|
||||||
Modifier
|
Modifier
|
||||||
</Button>
|
</Form.Button>
|
||||||
</div>
|
</div>
|
||||||
|
<Label>Email</Label>
|
||||||
<label for="email">Email</label>
|
|
||||||
<Input
|
<Input
|
||||||
name="email"
|
|
||||||
type="email"
|
type="email"
|
||||||
placeholder="philipzcwbarlow@peerat.dev"
|
value={$page.data.user?.email}
|
||||||
value={data.user?.email}
|
|
||||||
disabled
|
disabled
|
||||||
|
placeholder="philiphzcwbarlow@peerat.dev"
|
||||||
/>
|
/>
|
||||||
|
<p class="text-sm text-muted-foreground">Votre email ne peut pas être modifié.</p>
|
||||||
<label for="firstname">Prénom</label>
|
<Form.Field {form} name="firstname">
|
||||||
<Input
|
<Form.Control let:attrs>
|
||||||
name="firstname"
|
<Form.Label>Prénom</Form.Label>
|
||||||
type="text"
|
<Input {...attrs} bind:value={$formData.firstname} placeholder="Philip" />
|
||||||
placeholder="Philip"
|
</Form.Control>
|
||||||
aria-invalid={$errors.firstname ? 'true' : undefined}
|
<Form.FieldErrors />
|
||||||
bind:value={$form.firstname}
|
</Form.Field>
|
||||||
/>
|
<Form.Field {form} name="lastname">
|
||||||
{#if $errors.firstname}
|
<Form.Control let:attrs>
|
||||||
<span class="text-sm text-red-500">{$errors.firstname}</span>
|
<Form.Label>Nom de famille</Form.Label>
|
||||||
{/if}
|
<Input {...attrs} bind:value={$formData.lastname} placeholder="Barlow" />
|
||||||
|
</Form.Control>
|
||||||
<label for="lastname">Nom</label>
|
<Form.FieldErrors />
|
||||||
<Input
|
</Form.Field>
|
||||||
name="lastname"
|
<Form.Field {form} name="pseudo">
|
||||||
type="text"
|
<Form.Control let:attrs>
|
||||||
placeholder="Barlow"
|
<Form.Label>Nom d'utilisateur</Form.Label>
|
||||||
aria-invalid={$errors.lastname ? 'true' : undefined}
|
<Input {...attrs} bind:value={$formData.pseudo} placeholder="Cypherwolf" />
|
||||||
bind:value={$form.lastname}
|
</Form.Control>
|
||||||
/>
|
<Form.Description>Ce nom sera visible par les autres utilisateurs.</Form.Description>
|
||||||
{#if $errors.lastname}
|
<Form.FieldErrors />
|
||||||
<span class="text-sm text-red-500">{$errors.lastname}</span>
|
</Form.Field>
|
||||||
{/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>
|
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { fail, redirect, type Actions } from '@sveltejs/kit';
|
||||||
import type { PageServerLoad } from './$types';
|
import type { PageServerLoad } from './$types';
|
||||||
|
|
||||||
import { zod } from 'sveltekit-superforms/adapters';
|
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';
|
import { loginSchema } from '$lib/validations/auth';
|
||||||
|
|
||||||
|
@ -37,19 +37,13 @@ export const actions: Actions = {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
|
return setError(form, 'passwd', "Nom d'utilisateur ou mot de passe incorrect");
|
||||||
form.errors.passwd = ["Nom d'utilisateur ou mot de passe incorrect"];
|
|
||||||
|
|
||||||
return fail(400, { form });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = res.headers.get('Authorization')?.split(' ').pop();
|
const token = res.headers.get('Authorization')?.split('Bearer ').pop();
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
|
return setError(form, 'passwd', "Une erreur est survenue, veuillez réessayer plus tard");
|
||||||
form.errors.passwd = ["Une erreur est survenue, veuillez réessayer plus tard"];
|
|
||||||
|
|
||||||
return fail(500, { form });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cookies.set('session', token, {
|
cookies.set('session', token, {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { PageData } from './$types';
|
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 { zodClient } from 'sveltekit-superforms/adapters';
|
||||||
import { superForm } from 'sveltekit-superforms/client';
|
import { superForm } from 'sveltekit-superforms/client';
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@
|
||||||
<div class="container flex h-screen">
|
<div class="container flex h-screen">
|
||||||
<div class="flex w-full flex-col items-center justify-center">
|
<div class="flex w-full flex-col items-center justify-center">
|
||||||
<div class="flex w-full max-w-xs flex-col gap-4">
|
<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 class="flex flex-col justify-center gap-2" method="POST" use:enhance>
|
||||||
<Form.Field {form} name="pseudo">
|
<Form.Field {form} name="pseudo">
|
||||||
<Form.Control let:attrs>
|
<Form.Control let:attrs>
|
||||||
|
@ -54,7 +54,7 @@
|
||||||
</Form.Field>
|
</Form.Field>
|
||||||
<Form.Button disabled={$delayed}>
|
<Form.Button disabled={$delayed}>
|
||||||
{#if $delayed}
|
{#if $delayed}
|
||||||
<Loader2 class="mr-2 h-4 w-4 animate-spin" />
|
<Loader class="mr-2 h-4 w-4 animate-spin" />
|
||||||
{/if}
|
{/if}
|
||||||
Se connecter
|
Se connecter
|
||||||
</Form.Button>
|
</Form.Button>
|
||||||
|
|
|
@ -6,12 +6,11 @@ import { fail, redirect, type Actions } from '@sveltejs/kit';
|
||||||
import type { PageServerLoad } from './$types';
|
import type { PageServerLoad } from './$types';
|
||||||
|
|
||||||
import { zod } from 'sveltekit-superforms/adapters';
|
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';
|
import { registerConfirmationSchema, registerSchema } from '$lib/validations/auth';
|
||||||
|
|
||||||
|
export const load: PageServerLoad = async ({ locals: { user } }) => {
|
||||||
export const load = (async ({ locals: { user } }) => {
|
|
||||||
if (user) redirect(302, '/');
|
if (user) redirect(302, '/');
|
||||||
|
|
||||||
const registerForm = await superValidate(zod(registerSchema));
|
const registerForm = await superValidate(zod(registerSchema));
|
||||||
|
@ -22,10 +21,11 @@ export const load = (async ({ locals: { user } }) => {
|
||||||
registerForm,
|
registerForm,
|
||||||
registerConfirmationForm
|
registerConfirmationForm
|
||||||
};
|
};
|
||||||
}) satisfies PageServerLoad;
|
}
|
||||||
|
|
||||||
export const actions = {
|
export const actions: Actions = {
|
||||||
register: async ({ request }) => {
|
register: async ({ request }) => {
|
||||||
|
|
||||||
const form = await superValidate(request, zod(registerSchema));
|
const form = await superValidate(request, zod(registerSchema));
|
||||||
|
|
||||||
if (!form.valid) {
|
if (!form.valid) {
|
||||||
|
@ -39,26 +39,19 @@ export const actions = {
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.ok) {
|
if (!res.ok) {
|
||||||
return {
|
if (res.status === 400) {
|
||||||
success: true
|
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) {
|
return {
|
||||||
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, {
|
|
||||||
form
|
form
|
||||||
});
|
};
|
||||||
},
|
},
|
||||||
confirmation: async ({ request, cookies }) => {
|
confirmation: async ({ request, cookies }) => {
|
||||||
const form = await superValidate(request, zod(registerConfirmationSchema));
|
const form = await superValidate(request, zod(registerConfirmationSchema));
|
||||||
|
@ -79,46 +72,27 @@ export const actions = {
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.ok) {
|
if (!res.ok) {
|
||||||
const token = res.headers.get('Authorization')?.split('Bearer ')[1];
|
if (res.status === 400) {
|
||||||
|
|
||||||
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 {
|
|
||||||
const { email_valid, username_valid } = await res.json();
|
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 (email_valid) form.errors.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é");
|
||||||
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 });
|
|
||||||
}
|
}
|
||||||
|
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, {
|
if (!token) {
|
||||||
form
|
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, '/');
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { fade } from 'svelte/transition';
|
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 Loader from 'lucide-svelte/icons/loader-circle';
|
||||||
import { toast } from 'svelte-sonner';
|
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 { superForm } from 'sveltekit-superforms/client';
|
||||||
|
|
||||||
import * as Form from '$lib/components/ui/form';
|
import * as Form from '$lib/components/ui/form';
|
||||||
import Input from '$lib/components/ui/input/input.svelte';
|
import Input from '$lib/components/ui/input/input.svelte';
|
||||||
|
|
||||||
import { registerConfirmationSchema, registerSchema } from '$lib/validations/auth';
|
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;
|
export let data: PageData;
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@
|
||||||
validators: zodClient(registerSchema),
|
validators: zodClient(registerSchema),
|
||||||
delayMs: 500,
|
delayMs: 500,
|
||||||
multipleSubmits: 'prevent',
|
multipleSubmits: 'prevent',
|
||||||
|
invalidateAll: false,
|
||||||
onResult({ result }) {
|
onResult({ result }) {
|
||||||
switch (result.type) {
|
switch (result.type) {
|
||||||
case 'success':
|
case 'success':
|
||||||
|
@ -30,6 +31,7 @@
|
||||||
registerConfirmationFormData.set({
|
registerConfirmationFormData.set({
|
||||||
...$registerFormData,
|
...$registerFormData,
|
||||||
passwd: '',
|
passwd: '',
|
||||||
|
confirm: '',
|
||||||
code: ''
|
code: ''
|
||||||
});
|
});
|
||||||
confirmation = true;
|
confirmation = true;
|
||||||
|
@ -38,9 +40,6 @@
|
||||||
(field as HTMLInputElement)?.focus();
|
(field as HTMLInputElement)?.focus();
|
||||||
}, 100);
|
}, 100);
|
||||||
break;
|
break;
|
||||||
case 'error':
|
|
||||||
confirmation = false;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -52,7 +51,7 @@
|
||||||
} = registerForm;
|
} = registerForm;
|
||||||
|
|
||||||
const registerConfirmationForm = superForm(data.registerConfirmationForm, {
|
const registerConfirmationForm = superForm(data.registerConfirmationForm, {
|
||||||
validators: zod(registerConfirmationSchema),
|
validators: zodClient(registerConfirmationSchema),
|
||||||
delayMs: 500,
|
delayMs: 500,
|
||||||
multipleSubmits: 'prevent'
|
multipleSubmits: 'prevent'
|
||||||
});
|
});
|
||||||
|
@ -62,18 +61,13 @@
|
||||||
enhance: registerConfirmationEnhance,
|
enhance: registerConfirmationEnhance,
|
||||||
delayed: registerConfirmationDelayed
|
delayed: registerConfirmationDelayed
|
||||||
} = registerConfirmationForm;
|
} = registerConfirmationForm;
|
||||||
|
|
||||||
export const snapshot: Snapshot = {
|
|
||||||
capture: () => confirmation,
|
|
||||||
restore: (value) => (confirmation = value)
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="container flex h-screen">
|
<div class="container flex h-screen">
|
||||||
<div class="flex w-full flex-col items-center justify-center">
|
<div class="flex w-full flex-col items-center justify-center">
|
||||||
<div class="flex w-full max-w-xs flex-col gap-4">
|
<div class="flex w-full max-w-xs flex-col gap-4">
|
||||||
{#if confirmation}
|
{#if confirmation}
|
||||||
<h1 class="mx-auto text-xl font-bold">Confirmation</h1>
|
<h2 class="mx-auto text-xl font-bold">Confirmation</h2>
|
||||||
<form
|
<form
|
||||||
class="flex flex-col justify-center gap-2"
|
class="flex flex-col justify-center gap-2"
|
||||||
method="POST"
|
method="POST"
|
||||||
|
@ -143,6 +137,18 @@
|
||||||
</Form.Control>
|
</Form.Control>
|
||||||
<Form.FieldErrors />
|
<Form.FieldErrors />
|
||||||
</Form.Field>
|
</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.Field form={registerConfirmationForm} name="code">
|
||||||
<Form.Control let:attrs>
|
<Form.Control let:attrs>
|
||||||
<Form.Label>Code</Form.Label>
|
<Form.Label>Code</Form.Label>
|
||||||
|
@ -156,16 +162,12 @@
|
||||||
</Form.Field>
|
</Form.Field>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Form.Button type="submit" disabled={$registerConfirmationDelayed}>
|
<Form.Button disabled={$registerConfirmationDelayed}>
|
||||||
{#if $registerConfirmationDelayed}
|
{#if $registerConfirmationDelayed}
|
||||||
<Loader class="mr-2 h-4 w-4 animate-spin" />
|
<Loader class="mr-2 h-4 w-4 animate-spin" />
|
||||||
{/if}
|
{/if}
|
||||||
Continuer
|
Continuer
|
||||||
</Form.Button>
|
</Form.Button>
|
||||||
|
|
||||||
<!-- <Button variant="link" formaction="?/register" on:click={() => toast('Code renvoyé')}>
|
|
||||||
Renvoyer le code
|
|
||||||
</Button> -->
|
|
||||||
</form>
|
</form>
|
||||||
{:else}
|
{:else}
|
||||||
<h1 class="mx-auto text-xl font-bold">Inscription</h1>
|
<h1 class="mx-auto text-xl font-bold">Inscription</h1>
|
||||||
|
@ -208,14 +210,12 @@
|
||||||
</Form.Control>
|
</Form.Control>
|
||||||
<Form.FieldErrors />
|
<Form.FieldErrors />
|
||||||
</Form.Field>
|
</Form.Field>
|
||||||
<div class="flex flex-col gap-2">
|
<Form.Button disabled={$registerDelayed}>
|
||||||
<Form.Button type="submit" disabled={$registerDelayed}>
|
{#if $registerDelayed}
|
||||||
{#if $registerDelayed}
|
<Loader className="mr-2 h-4 w-4 animate-spin" />
|
||||||
<Loader className="mr-2 h-4 w-4 animate-spin" />
|
{/if}
|
||||||
{/if}
|
S'inscrire
|
||||||
S'inscrire
|
</Form.Button>
|
||||||
</Form.Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
{/if}
|
{/if}
|
||||||
<ul class="flex justify-between">
|
<ul class="flex justify-between">
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { fail, redirect, type Actions } from '@sveltejs/kit';
|
||||||
import type { PageServerLoad } from './$types';
|
import type { PageServerLoad } from './$types';
|
||||||
|
|
||||||
import { zod } from 'sveltekit-superforms/adapters';
|
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';
|
import { requestPasswordResetSchema, resetPasswordSchema } from '$lib/validations/auth';
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ export const load = (async ({ locals: { user } }) => {
|
||||||
};
|
};
|
||||||
}) satisfies PageServerLoad;
|
}) satisfies PageServerLoad;
|
||||||
|
|
||||||
export const actions = {
|
export const actions: Actions = {
|
||||||
request: async ({ request, fetch }) => {
|
request: async ({ request, fetch }) => {
|
||||||
const form = await superValidate(request, zod(requestPasswordResetSchema));
|
const form = await superValidate(request, zod(requestPasswordResetSchema));
|
||||||
|
|
||||||
|
@ -33,16 +33,17 @@ export const actions = {
|
||||||
|
|
||||||
const res = await fetch(`${API_URL}/user/fpw`, {
|
const res = await fetch(`${API_URL}/user/fpw`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify(form.data)
|
body: JSON.stringify({
|
||||||
|
...form.data
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
form.errors.email = ["Une erreur s'est produite ou l'email n'existe pas"];
|
return setError(form, 'email', "Une erreur s'est produite ou l'email n'existe pas");
|
||||||
return fail(400, { form });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true
|
form
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
confirmation: async ({ request, cookies, fetch }) => {
|
confirmation: async ({ request, cookies, fetch }) => {
|
||||||
|
@ -62,11 +63,10 @@ export const actions = {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const token = res.headers.get('Authorization')?.split('Bearer ')[1];
|
const token = res.headers.get('Authorization')?.split('Bearer ').pop();
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
form.errors.code = ["Une erreur s'est produite"];
|
return setError(form, 'code', "Une erreur est survenue, veuillez réessayer plus tard");
|
||||||
return fail(400, { form });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cookies.set('session', token, {
|
cookies.set('session', token, {
|
||||||
|
@ -79,13 +79,9 @@ export const actions = {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (res.status === 400) {
|
if (res.status === 400) {
|
||||||
form.errors.code = ['Code invalide'];
|
return setError(form, 'code', "Le code de confirmation est incorrect");
|
||||||
} else {
|
|
||||||
form.errors.code = [`Une erreur s'est produite`];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return fail(400, {
|
return setError(form, 'code', "Une erreur est survenue, veuillez réessayer plus tard");
|
||||||
form
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} satisfies Actions;
|
}
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { fade } from 'svelte/transition';
|
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 { toast } from 'svelte-sonner';
|
||||||
import { zodClient } from 'sveltekit-superforms/adapters';
|
import { zodClient } from 'sveltekit-superforms/adapters';
|
||||||
import { superForm } from 'sveltekit-superforms/client';
|
import { superForm } from 'sveltekit-superforms/client';
|
||||||
|
|
||||||
import * as Form from '$lib/components/ui/form';
|
import * as Form from '$lib/components/ui/form';
|
||||||
import Input from '$lib/components/ui/input/input.svelte';
|
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;
|
export let data: PageData;
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@
|
||||||
validators: zodClient(requestPasswordResetSchema),
|
validators: zodClient(requestPasswordResetSchema),
|
||||||
delayMs: 500,
|
delayMs: 500,
|
||||||
multipleSubmits: 'prevent',
|
multipleSubmits: 'prevent',
|
||||||
|
invalidateAll: false,
|
||||||
onResult({ result }) {
|
onResult({ result }) {
|
||||||
switch (result.type) {
|
switch (result.type) {
|
||||||
case 'success':
|
case 'success':
|
||||||
|
@ -28,6 +29,7 @@
|
||||||
resetPasswordFormData.set({
|
resetPasswordFormData.set({
|
||||||
...$requestPasswordResetFormData,
|
...$requestPasswordResetFormData,
|
||||||
password: '',
|
password: '',
|
||||||
|
confirm: '',
|
||||||
code: ''
|
code: ''
|
||||||
});
|
});
|
||||||
confirmation = true;
|
confirmation = true;
|
||||||
|
@ -50,6 +52,7 @@
|
||||||
} = requestPasswordResetForm;
|
} = requestPasswordResetForm;
|
||||||
|
|
||||||
const resetPasswordForm = superForm(data.resetPasswordForm, {
|
const resetPasswordForm = superForm(data.resetPasswordForm, {
|
||||||
|
validators: zodClient(resetPasswordSchema),
|
||||||
delayMs: 500,
|
delayMs: 500,
|
||||||
multipleSubmits: 'prevent'
|
multipleSubmits: 'prevent'
|
||||||
});
|
});
|
||||||
|
@ -59,18 +62,13 @@
|
||||||
enhance: resetPasswordEnhance,
|
enhance: resetPasswordEnhance,
|
||||||
delayed: resetPasswordDelayed
|
delayed: resetPasswordDelayed
|
||||||
} = resetPasswordForm;
|
} = resetPasswordForm;
|
||||||
|
|
||||||
export const snapshot: Snapshot = {
|
|
||||||
capture: () => confirmation,
|
|
||||||
restore: (value) => (confirmation = value)
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="container flex h-screen">
|
<div class="container flex h-screen">
|
||||||
<div class="flex w-full flex-col items-center justify-center">
|
<div class="flex w-full flex-col items-center justify-center">
|
||||||
<div class="flex w-full max-w-xs flex-col gap-4">
|
<div class="flex w-full max-w-xs flex-col gap-4">
|
||||||
{#if confirmation}
|
{#if confirmation}
|
||||||
<h1 class="mx-auto text-xl font-bold">Confirmation</h1>
|
<h2 class="mx-auto text-xl font-bold">Confirmation</h2>
|
||||||
<form
|
<form
|
||||||
class="flex flex-col justify-center gap-2"
|
class="flex flex-col justify-center gap-2"
|
||||||
method="POST"
|
method="POST"
|
||||||
|
@ -107,6 +105,18 @@
|
||||||
</Form.Control>
|
</Form.Control>
|
||||||
<Form.FieldErrors />
|
<Form.FieldErrors />
|
||||||
</Form.Field>
|
</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.Field form={resetPasswordForm} name="code">
|
||||||
<Form.Control let:attrs>
|
<Form.Control let:attrs>
|
||||||
<Form.Label>Code</Form.Label>
|
<Form.Label>Code</Form.Label>
|
||||||
|
@ -118,7 +128,7 @@
|
||||||
|
|
||||||
<Form.Button disabled={$resetPasswordDelayed}>
|
<Form.Button disabled={$resetPasswordDelayed}>
|
||||||
{#if $resetPasswordDelayed}
|
{#if $resetPasswordDelayed}
|
||||||
<Loader2 class="mr-2 h-4 w-4 animate-spin" />
|
<Loader class="mr-2 h-4 w-4 animate-spin" />
|
||||||
{/if}
|
{/if}
|
||||||
Changer le mot de passe
|
Changer le mot de passe
|
||||||
</Form.Button>
|
</Form.Button>
|
||||||
|
@ -157,7 +167,7 @@
|
||||||
</Form.Field>
|
</Form.Field>
|
||||||
<Form.Button disabled={$requestPasswordResetDelayed}>
|
<Form.Button disabled={$requestPasswordResetDelayed}>
|
||||||
{#if $requestPasswordResetDelayed}
|
{#if $requestPasswordResetDelayed}
|
||||||
<Loader2 class="mr-2 h-4 w-4 animate-spin" />
|
<Loader class="mr-2 h-4 w-4 animate-spin" />
|
||||||
{/if}
|
{/if}
|
||||||
Continuer
|
Continuer
|
||||||
</Form.Button>
|
</Form.Button>
|
||||||
|
|
Loading…
Add table
Reference in a new issue