feat: changed form behavior

This commit is contained in:
Théo 2023-09-17 23:04:00 +02:00
parent f1dea1016a
commit 7321d48266
4 changed files with 156 additions and 148 deletions

View file

@ -1,36 +1,39 @@
import { redirect, type Actions, fail } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
import { API_URL } from '$env/static/private'; import { API_URL } from '$env/static/private';
import { z } from 'zod'; import { redirect, type Actions, fail } from '@sveltejs/kit';
export const load = (async ({ locals: { user } }) => { import type { PageServerLoad } from './$types';
if (user) throw redirect(303, '/dashboard');
}) satisfies PageServerLoad; import { superValidate } from 'sveltekit-superforms/server';
import { z } from 'zod';
const schema = z.object({ const schema = z.object({
pseudo: z.string().trim(), pseudo: z.string().trim(),
passwd: z.string() passwd: z.string()
}); });
export const load = (async ({ locals: { user } }) => {
if (user) throw redirect(303, '/dashboard');
const form = await superValidate(schema);
return {
form
};
}) satisfies PageServerLoad;
export const actions = { export const actions = {
default: async (event) => { default: async ({ request, cookies }) => {
const data = await event.request.formData(); const form = await superValidate(request, schema);
const parse = schema.safeParse(Object.fromEntries(data.entries())); if (!form.valid) {
return fail(400, { form });
if (!parse.success) {
const errors = parse.error.errors.map((error) => {
const { path, message } = error;
return { field: path[0], message };
});
return fail(400, { errors });
} }
const res = await fetch(`${API_URL}/login`, { const res = await fetch(`${API_URL}/login`, {
method: 'POST', method: 'POST',
body: JSON.stringify({ body: JSON.stringify({
...parse.data ...form.data
}) })
}); });
@ -39,15 +42,17 @@ export const actions = {
if (!token) throw new Error('No token found'); if (!token) throw new Error('No token found');
event.cookies.set('session', token, { cookies.set('session', token, {
path: '/' path: '/'
}); });
throw redirect(303, '/dashboard'); throw redirect(303, '/dashboard');
} }
form.errors.passwd = ["Nom d'utilisateur ou mot de passe incorrect"];
return fail(400, { return fail(400, {
errors: [{ field: 'passwd', message: "Nom d'utilisateur ou mot de passe incorrect" }] form
}); });
} }
} satisfies Actions; } satisfies Actions;

View file

@ -1,12 +1,26 @@
<script lang="ts"> <script lang="ts">
import { enhance } from '$app/forms'; import type { PageData } from './$types';
import type { ActionData } from './$types'; import { goto } from '$app/navigation';
import { superForm } from 'sveltekit-superforms/client';
import Button from '$lib/components/ui/Button.svelte'; import Button from '$lib/components/ui/Button.svelte';
import Input from '$lib/components/ui/Input.svelte'; import Input from '$lib/components/ui/Input.svelte';
export let form: ActionData; export let data: PageData;
const { form, errors, enhance } = superForm(data.form, {
onResult({ result }) {
switch (result.type) {
case 'redirect':
goto(result.location, {
replaceState: true
});
break;
}
}
});
</script> </script>
<div class="flex h-screen w-full"> <div class="flex h-screen w-full">
@ -15,20 +29,18 @@
<h1 class="mx-auto text-xl font-bold">Connexion</h1> <h1 class="mx-auto text-xl font-bold">Connexion</h1>
<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>
<label for="pseudo"> Nom d'utilisateur </label> <label for="pseudo"> Nom d'utilisateur </label>
<Input name="pseudo" placeholder="Barlow" type="text" required /> <Input name="pseudo" placeholder="Barlow" type="text" required bind:value={$form.pseudo} />
{#if form?.errors.find((error) => error.field === 'pseudo')} {#if $errors.pseudo}<span class="text-sm text-red-500">{$errors.pseudo}</span>{/if}
<p class="text-sm text-red-500">
{form?.errors.find((error) => error.field === 'pseudo')?.message}
</p>
{/if}
<label for="passwd"> Mot de passe </label> <label for="passwd"> Mot de passe </label>
<Input name="passwd" placeholder="************" type="password" required /> <Input
{#if form?.errors.find((error) => error.field === 'passwd')} name="passwd"
<p class="text-sm text-red-500"> placeholder="************"
{form?.errors.find((error) => error.field === 'passwd')?.message} type="password"
</p> required
{/if} bind:value={$form.passwd}
/>
{#if $errors.passwd}<span class="text-sm text-red-500">{$errors.passwd}</span>{/if}
<Button class="mt-2" variant="brand"> <Button class="mt-2" variant="brand">
<!-- {isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />} --> <!-- {isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />} -->
@ -39,11 +51,11 @@
<li> <li>
<a class="text-highlight-secondary hover:text-brand" href="/sign-up">S'inscrire</a> <a class="text-highlight-secondary hover:text-brand" href="/sign-up">S'inscrire</a>
</li> </li>
<!-- <li> <li>
<a class="text-highlight-secondary hover:text-brand" href="/forgot-password" <a class="text-highlight-secondary hover:text-brand" href="/forgot-password"
>Mot de passe oublié</a >Mot de passe oublié</a
> >
</li> --> </li>
</ul> </ul>
</form> </form>
</div> </div>

View file

@ -4,12 +4,9 @@ import { fail, redirect, type Actions } from '@sveltejs/kit';
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
import { superValidate } from 'sveltekit-superforms/server';
import { z } from 'zod'; import { z } from 'zod';
export const load = (async ({ locals: { user } }) => {
if (user) throw redirect(303, '/dashboard');
}) satisfies PageServerLoad;
const registerSchema = z.object({ const registerSchema = z.object({
email: z email: z
.string() .string()
@ -19,7 +16,17 @@ const registerSchema = z.object({
.trim(), .trim(),
firstname: z.string().trim(), firstname: z.string().trim(),
lastname: z.string().trim(), lastname: z.string().trim(),
pseudo: z.string().trim() pseudo: z.string().trim(),
code: z
.string({
required_error: 'Code manquant'
})
.length(4, {
message: 'Code invalide, il doit contenir 4 chiffres'
})
.regex(/^[0-9]+$/)
.optional(),
passwd: z.string().optional()
}); });
const confirmationSchema = z.object({ const confirmationSchema = z.object({
@ -32,80 +39,80 @@ const confirmationSchema = z.object({
firstname: z.string().trim(), firstname: z.string().trim(),
lastname: z.string().trim(), lastname: z.string().trim(),
pseudo: z.string().trim(), pseudo: z.string().trim(),
code: z.string(), code: z
.string({
required_error: 'Code manquant'
})
.regex(/^[0-9]{4}$/, {
message: 'Code invalide, il doit contenir 4 chiffres'
}),
passwd: z.string() passwd: z.string()
}); });
export const load = (async ({ locals: { user } }) => {
if (user) throw redirect(303, '/dashboard');
const form = await superValidate(registerSchema);
return {
form
};
}) satisfies PageServerLoad;
export const actions = { export const actions = {
register: async (event) => { register: async ({ request }) => {
const data = await event.request.formData(); const form = await superValidate(request, registerSchema);
const parse = registerSchema.safeParse(Object.fromEntries(data.entries())); if (!form.valid) {
return fail(400, { form });
if (!parse.success) {
const errors = parse.error.errors.map((error) => {
const { path, message } = error;
return { field: path[0], message };
});
return fail(400, { errors });
} }
const res = await fetch(`${API_URL}/register`, { const res = await fetch(`${API_URL}/register`, {
method: 'POST', method: 'POST',
body: JSON.stringify({ body: JSON.stringify({
pseudo: parse.data.pseudo, pseudo: form.data.pseudo,
firstname: parse.data.firstname, firstname: form.data.firstname,
lastname: parse.data.lastname, lastname: form.data.lastname,
email: parse.data.email email: form.data.email
}) })
}); });
if (res.ok) { if (res.ok) {
return { return {
success: true form
}; };
} }
if (res.status === 400) { if (res.status === 400) {
const { email_valid } = await res.json(); const { email_valid } = await res.json();
const errors = []; if (!email_valid) form.errors.email = ['Un compte avec cette adresse email existe déjà'];
if (!email_valid) return fail(400, { form });
errors.push({
field: 'email',
message: 'Cet email est déjà utilisé'
});
return fail(400, { errors });
} }
form.errors.passwd = ["Une erreur s'est produite"];
return fail(400, { return fail(400, {
errors: [{ field: 'passwd', message: "Une erreur s'est produite" }] form
}); });
}, },
confirmation: async (event) => { confirmation: async ({ request, cookies }) => {
const data = await event.request.formData(); const form = await superValidate(request, confirmationSchema);
const parse = confirmationSchema.safeParse(Object.fromEntries(data.entries())); if (!form.valid) {
return fail(400, { form });
if (!parse.success) {
const errors = parse.error.errors.map((error) => {
const { path, message } = error;
return { field: path[0], message };
});
return fail(400, { errors });
} }
const res = await fetch(`${API_URL}/confirmation`, { const res = await fetch(`${API_URL}/confirmation`, {
method: 'POST', method: 'POST',
body: JSON.stringify({ body: JSON.stringify({
firstname: parse.data.firstname, firstname: form.data.firstname,
lastname: parse.data.lastname, lastname: form.data.lastname,
pseudo: parse.data.pseudo, pseudo: form.data.pseudo,
email: parse.data.email, email: form.data.email,
code: parseInt(parse.data.code), code: parseInt(form.data.code),
passwd: parse.data.passwd passwd: form.data.passwd
}) })
}); });
@ -114,15 +121,17 @@ export const actions = {
if (!token) throw new Error('No token'); if (!token) throw new Error('No token');
event.cookies.set('token', token, { cookies.set('session', token, {
path: '/' path: '/'
}); });
throw redirect(303, '/dashboard'); throw redirect(303, '/dashboard');
} }
form.errors.code = [`Une erreur s'est produite (${res.status} ${res.statusText})`];
return fail(400, { return fail(400, {
errors: [{ field: 'passwd', message: "Une erreur s'est produite" }] form
}); });
} }
} satisfies Actions; } satisfies Actions;

View file

@ -1,25 +1,39 @@
<script lang="ts"> <script lang="ts">
import { enhance } from '$app/forms'; import { goto } from '$app/navigation';
import type { ActionData, Snapshot } from './$types'; import { fade } from 'svelte/transition';
import { superForm } from 'sveltekit-superforms/client';
import type { PageData, Snapshot } from './$types';
import Button from '$lib/components/ui/Button.svelte'; import Button from '$lib/components/ui/Button.svelte';
import Input from '$lib/components/ui/Input.svelte'; import Input from '$lib/components/ui/Input.svelte';
import { fade } from 'svelte/transition';
export let form: ActionData; export let data: PageData;
let data = { const { form, errors, enhance } = superForm(data.form, {
email: '', onResult({ result }) {
firstname: '', switch (result.type) {
lastname: '', case 'success':
pseudo: '', confirmation = true;
confirmation: false break;
}; case 'error':
confirmation = false;
break;
case 'redirect':
goto(result.location, {
replaceState: true
});
break;
}
}
});
let confirmation = false;
export const snapshot: Snapshot = { export const snapshot: Snapshot = {
capture: () => data, capture: () => confirmation,
restore: (value) => (data = value) restore: (value) => (confirmation = value)
}; };
</script> </script>
@ -27,83 +41,59 @@
<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">
<h2 class="mx-auto text-xl font-bold"> <h2 class="mx-auto text-xl font-bold">
{data.confirmation ? 'Confirmation' : 'Inscription'} {confirmation ? 'Confirmation' : 'Inscription'}
</h2> </h2>
<form <form
class="flex flex-col justify-center gap-2" class="flex flex-col justify-center gap-2"
method="POST" method="POST"
use:enhance={({}) => { action={confirmation ? '?/confirmation' : '?/register'}
return async ({ result }) => { use:enhance
switch (result.type) {
case 'success':
data.confirmation = true;
break;
}
};
}}
action={data.confirmation ? '/sign-up?/confirmation' : '/sign-up?/register'}
> >
<label for="email">Email</label> <label for="email">Email</label>
<Input <Input
bind:value={data.email} bind:value={$form.email}
name="email" name="email"
type="email" type="email"
placeholder="philipzcwbarlow@peerat.dev" placeholder="philipzcwbarlow@peerat.dev"
autocomplete="off" autocomplete="off"
required required
/> />
{#if form?.errors?.find((error) => error.field === 'email')} {#if $errors.email}<span class="text-sm text-red-500">{$errors.email}</span>{/if}
<p class="text-sm text-red-500">
{form?.errors.find((error) => error.field === 'email')?.message}
</p>
{/if}
<label for="firstname">Prénom</label> <label for="firstname">Prénom</label>
<Input <Input
bind:value={data.firstname} bind:value={$form.firstname}
name="firstname" name="firstname"
type="text" type="text"
placeholder="Philip" placeholder="Philip"
autocomplete="off" autocomplete="off"
required required
/> />
{#if form?.errors?.find((error) => error.field === 'firstname')} {#if $errors.firstname}<span class="text-sm text-red-500">{$errors.firstname}</span>{/if}
<p class="text-sm text-red-500">
{form?.errors?.find((error) => error.field === 'firstname')?.message}
</p>
{/if}
<label for="lastname">Nom</label> <label for="lastname">Nom</label>
<Input <Input
bind:value={data.lastname} bind:value={$form.lastname}
name="lastname" name="lastname"
type="text" type="text"
placeholder="Barlow" placeholder="Barlow"
autocomplete="off" autocomplete="off"
required required
/> />
{#if form?.errors?.find((error) => error.field === 'lastname')} {#if $errors.lastname}<span class="text-sm text-red-500">{$errors.lastname}</span>{/if}
<p class="text-sm text-red-500">
{form?.errors?.find((error) => error.field === 'lastname')?.message}
</p>
{/if}
<label for="pseudo"> Nom d'utilisateur </label> <label for="pseudo"> Nom d'utilisateur </label>
<Input <Input
bind:value={data.pseudo} bind:value={$form.pseudo}
name="pseudo" name="pseudo"
type="text" type="text"
placeholder="Cypher Wolf" placeholder="Cypher Wolf"
autocomplete="off" autocomplete="off"
required required
/> />
{#if form?.errors?.find((error) => error.field === 'pseudo')} {#if $errors.pseudo}<span class="text-sm text-red-500">{$errors.pseudo}</span>{/if}
<p class="text-sm text-red-500">
{form?.errors?.find((error) => error.field === 'pseudo')?.message}
</p>
{/if}
{#if data.confirmation} {#if confirmation}
<div <div
class="flex flex-col gap-2" class="flex flex-col gap-2"
transition:fade={{ transition:fade={{
@ -112,35 +102,27 @@
> >
<label for="passwd"> Mot de passe </label> <label for="passwd"> Mot de passe </label>
<Input name="passwd" placeholder="************" type="password" /> <Input name="passwd" placeholder="************" type="password" />
{#if form?.errors?.find((error) => error.field === 'passwd')} {#if $errors.passwd}<span class="text-sm text-red-500">{$errors.passwd}</span>{/if}
<p class="text-sm text-red-500">
{form?.errors?.find((error) => error.field === 'passwd')?.message}
</p>
{/if}
<label for="code"> Code </label> <label for="code"> Code </label>
<Input name="code" placeholder="1234" type="text" /> <Input name="code" placeholder="1234" type="text" />
{#if form?.errors?.find((error) => error.field === 'code')} {#if $errors.code}<span class="text-sm text-red-500">{$errors.code}</span>{/if}
<p class="text-sm text-red-500">
{form?.errors?.find((error) => error.field === 'code')?.message}
</p>
{/if}
</div> </div>
{/if} {/if}
<Button class="mt-2" variant="brand"> <Button class="mt-2" variant="brand">
<!-- {isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />} --> <!-- {isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />} -->
{data.confirmation ? "S'inscrire" : 'Continuer'} {confirmation ? "S'inscrire" : 'Continuer'}
</Button> </Button>
<ul class="flex justify-between"> <ul class="flex justify-between">
<li> <li>
<a class="text-highlight-secondary hover:text-brand" href="/sign-in">Se connecter</a> <a class="text-highlight-secondary hover:text-brand" href="/sign-in">Se connecter</a>
</li> </li>
{#if data.confirmation} {#if confirmation}
<li> <li>
<button <button formaction="?/register" class="text-highlight-secondary hover:text-brand"
formaction="/sign-up?/confirmation" >Pas reçu ?</button
class="text-highlight-secondary hover:text-brand">Pas reçu ?</button
> >
</li> </li>
{/if} {/if}