feat: register & confirmation

This commit is contained in:
Théo 2023-09-09 20:15:12 +02:00
parent 4246b25030
commit 3482c1f46c
6 changed files with 243 additions and 81 deletions

View file

@ -1,6 +1,7 @@
{
"name": "peer-at-code-sveltekit",
"name": "peer-at-code",
"version": "0.0.1",
"type": "module",
"private": true,
"scripts": {
"dev": "vite dev",
@ -14,6 +15,14 @@
"test:integration": "playwright test",
"test:unit": "vitest"
},
"dependencies": {
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"marked": "^7.0.1",
"svelte-boring-avatars": "^1.2.4",
"tailwind-merge": "^1.14.0",
"zod": "^3.21.4"
},
"devDependencies": {
"@playwright/test": "^1.28.1",
"@sveltejs/adapter-auto": "^2.0.0",
@ -37,14 +46,5 @@
"typescript": "^5.0.0",
"vite": "^4.4.2",
"vitest": "^0.32.2"
},
"type": "module",
"dependencies": {
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"marked": "^7.0.1",
"svelte-boring-avatars": "^1.2.4",
"tailwind-merge": "^1.14.0",
"zod": "^3.21.4"
}
}

View file

@ -3,9 +3,30 @@
import { cn } from '$lib';
let className: string | undefined | null = undefined;
type FormInputEvent<T extends Event = Event> = T & {
currentTarget: EventTarget & HTMLInputElement;
};
export let value: HTMLInputAttributes['value'] = undefined;
type InputEvents = {
blur: FormInputEvent<FocusEvent>;
change: FormInputEvent<Event>;
click: FormInputEvent<MouseEvent>;
focus: FormInputEvent<FocusEvent>;
keydown: FormInputEvent<KeyboardEvent>;
keypress: FormInputEvent<KeyboardEvent>;
keyup: FormInputEvent<KeyboardEvent>;
mouseover: FormInputEvent<MouseEvent>;
mouseenter: FormInputEvent<MouseEvent>;
mouseleave: FormInputEvent<MouseEvent>;
paste: FormInputEvent<ClipboardEvent>;
input: FormInputEvent<InputEvent>;
};
type $$Props = HTMLInputAttributes;
type $$Events = InputEvents;
let className: $$Props['class'] = undefined;
export let value: $$Props['value'] = undefined;
export { className as class };
</script>

View file

@ -0,0 +1,19 @@
import { browser } from '$app/environment';
import { writable } from 'svelte/store';
const defaultValue = false;
const initialValue = browser ? window.localStorage.getItem('register') === 'true' : defaultValue;
const register = writable<boolean>(initialValue);
register.subscribe((value) => {
if (browser) {
window.localStorage.setItem('register', value ? 'true' : 'false');
}
});
export function resetRegister() {
register.set(defaultValue);
}
export default register;

View file

@ -34,7 +34,7 @@
<!-- {isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />} -->
Se connecter
</Button>
</form>
<ul class="flex justify-between">
<li>
<a class="text-highlight-secondary hover:text-brand" href="/sign-up">S'inscrire</a>
@ -45,6 +45,7 @@
>
</li> -->
</ul>
</form>
</div>
</div>
</div>

View file

@ -1,14 +1,28 @@
import { redirect, type Actions, fail } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
import { API_URL } from '$env/static/private';
import { fail, redirect, type Actions } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
import { z } from 'zod';
export const load = (async ({ locals: { user } }) => {
if (user) throw redirect(303, '/dashboard');
}) satisfies PageServerLoad;
const schema = z.object({
const registerSchema = z.object({
email: z
.string()
.email({
message: 'Email invalide'
})
.trim(),
firstname: z.string().trim(),
lastname: z.string().trim(),
pseudo: z.string().trim()
});
const confirmationSchema = z.object({
email: z
.string()
.email({
@ -18,17 +32,15 @@ const schema = z.object({
firstname: z.string().trim(),
lastname: z.string().trim(),
pseudo: z.string().trim(),
passwd: z.string(),
description: z.string().nullable(),
sgroup: z.string().nullable(),
avatar: z.string().nullable()
code: z.string(),
passwd: z.string()
});
export const actions = {
default: async (event) => {
register: async (event) => {
const data = await event.request.formData();
const parse = schema.safeParse(Object.fromEntries(data.entries()));
const parse = registerSchema.safeParse(Object.fromEntries(data.entries()));
if (!parse.success) {
const errors = parse.error.errors.map((error) => {
@ -41,44 +53,74 @@ export const actions = {
const res = await fetch(`${API_URL}/register`, {
method: 'POST',
body: JSON.stringify({
...parse.data
pseudo: parse.data.pseudo,
firstname: parse.data.firstname,
lastname: parse.data.lastname,
email: parse.data.email
})
});
if (res.ok) {
const token = res.headers.get('Authorization')?.split(' ')[1];
if (!token) throw new Error('No token found');
event.cookies.set('session', token, {
path: '/'
});
throw redirect(303, '/dashboard');
return {
success: true
};
}
if (res.status === 400) {
const { username_valid, email_valid } = await res.json();
const { email_valid } = await res.json();
const errors = [];
if (!username_valid) {
errors.push({
field: 'pseudo',
message: 'Ce pseudo est déjà utilisé'
});
}
if (!email_valid) {
if (!email_valid)
errors.push({
field: 'email',
message: 'Cet email est déjà utilisé'
});
}
return fail(400, { errors });
}
return fail(400, {
errors: [{ field: 'passwd', message: "Une erreur s'est produite" }]
});
},
confirmation: async (event) => {
const data = await event.request.formData();
const parse = confirmationSchema.safeParse(Object.fromEntries(data.entries()));
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`, {
method: 'POST',
body: JSON.stringify({
firstname: parse.data.firstname,
lastname: parse.data.lastname,
pseudo: parse.data.pseudo,
email: parse.data.email,
code: parseInt(parse.data.code),
passwd: parse.data.passwd
})
});
if (res.ok) {
const token = res.headers.get('Authorization')?.split('Bearer ')[1];
if (!token) throw new Error('No token');
event.cookies.set('token', token, {
path: '/'
});
throw redirect(303, '/dashboard');
}
return fail(400, {
errors: [{ field: 'passwd', message: "Une erreur s'est produite" }]
});

View file

@ -1,73 +1,152 @@
<script lang="ts">
import { enhance } from '$app/forms';
import type { ActionData } from './$types';
import type { ActionData, Snapshot } from './$types';
import register from '$lib/stores/Register';
import Button from '$lib/components/ui/Button.svelte';
import Input from '$lib/components/ui/Input.svelte';
import { fade } from 'svelte/transition';
export let form: ActionData;
let data = {
email: '',
firstname: '',
lastname: '',
pseudo: ''
};
export const snapshot: Snapshot = {
capture: () => data,
restore: (value) => (data = value)
};
$: confirmation = $register;
</script>
<div class="flex h-screen w-full">
<div class="flex w-full flex-col items-center justify-center">
<div class="flex w-full max-w-xs flex-col gap-4">
<h2 class="mx-auto text-xl font-bold">Inscription</h2>
<form class="flex flex-col justify-center gap-2" method="POST" use:enhance>
<h2 class="mx-auto text-xl font-bold">{confirmation ? 'Confirmation' : 'Inscription'}</h2>
<form
class="flex flex-col justify-center gap-2"
method="POST"
use:enhance={({}) => {
return async ({ result }) => {
switch (result.type) {
case 'success':
register.set(true);
break;
}
};
}}
action={confirmation ? '/sign-up?/confirmation' : '/sign-up?/register'}
>
<label for="email">Email</label>
<Input name="email" type="email" placeholder="philipzcwbarlow@peerat.dev" />
{#if form?.errors.find((error) => error.field === 'email')}
<Input
bind:value={data.email}
name="email"
type="email"
placeholder="philipzcwbarlow@peerat.dev"
autocomplete="off"
required
/>
{#if form?.errors?.find((error) => error.field === 'email')}
<p class="text-sm text-red-500">
{form?.errors.find((error) => error.field === 'email')?.message}
</p>
{/if}
<label for="firstname">Prénom</label>
<Input name="firstname" type="text" placeholder="Philip" required />
{#if form?.errors.find((error) => error.field === 'firstname')}
<Input
bind:value={data.firstname}
name="firstname"
type="text"
placeholder="Philip"
autocomplete="off"
required
/>
{#if form?.errors?.find((error) => error.field === 'firstname')}
<p class="text-sm text-red-500">
{form?.errors.find((error) => error.field === 'firstname')?.message}
{form?.errors?.find((error) => error.field === 'firstname')?.message}
</p>
{/if}
<label for="lastname">Nom</label>
<Input name="lastname" type="text" placeholder="Barlow" required />
{#if form?.errors.find((error) => error.field === 'lastname')}
<Input
bind:value={data.lastname}
name="lastname"
type="text"
placeholder="Barlow"
autocomplete="off"
required
/>
{#if form?.errors?.find((error) => error.field === 'lastname')}
<p class="text-sm text-red-500">
{form?.errors.find((error) => error.field === 'lastname')?.message}
{form?.errors?.find((error) => error.field === 'lastname')?.message}
</p>
{/if}
<label for="pseudo"> Nom d'utilisateur </label>
<Input name="pseudo" type="text" placeholder="Cypher Wolf" required />
{#if form?.errors.find((error) => error.field === 'pseudo')}
<Input
bind:value={data.pseudo}
name="pseudo"
type="text"
placeholder="Cypher Wolf"
autocomplete="off"
required
/>
{#if form?.errors?.find((error) => error.field === 'pseudo')}
<p class="text-sm text-red-500">
{form?.errors.find((error) => error.field === 'pseudo')?.message}
{form?.errors?.find((error) => error.field === 'pseudo')?.message}
</p>
{/if}
{#if confirmation}
<div
class="flex flex-col gap-2"
transition:fade={{
duration: 300
}}
>
<label for="passwd"> Mot de passe </label>
<Input name="passwd" placeholder="************" type="password" required />
{#if form?.errors.find((error) => error.field === 'passwd')}
<Input name="passwd" placeholder="************" type="password" />
{#if form?.errors?.find((error) => error.field === 'passwd')}
<p class="text-sm text-red-500">
{form?.errors.find((error) => error.field === 'passwd')?.message}
{form?.errors?.find((error) => error.field === 'passwd')?.message}
</p>
{/if}
<Input class="hidden" type="text" name="sgroup" />
<Input class="hidden" type="text" name="description" />
<Input class="hidden" type="text" name="avatar" />
<label for="code"> Code </label>
<Input name="code" placeholder="1234" type="text" />
{#if form?.errors?.find((error) => error.field === 'code')}
<p class="text-sm text-red-500">
{form?.errors?.find((error) => error.field === 'code')?.message}
</p>
{/if}
</div>
{/if}
<Button class="mt-2" variant="brand">
<!-- {isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />} -->
S'inscrire
{confirmation ? "S'inscrire" : 'Continuer'}
</Button>
</form>
<ul class="flex justify-between">
<li>
<a class="text-highlight-secondary hover:text-brand" href="/sign-in">Se connecter</a>
</li>
{#if confirmation}
<li>
<button
formaction="/sign-up?/register"
class="text-highlight-secondary hover:text-brand">Pas reçu ?</button
>
</li>
{/if}
</ul>
</form>
</div>
</div>
</div>