Setup global toaster and finalize login/registration page
This commit is contained in:
parent
1af78e5ab7
commit
b3fae0b5db
27
app.vue
27
app.vue
|
|
@ -1,14 +1,19 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="text-light-100 dark:text-dark-100 flex bg-light-0 dark:bg-dark-0 h-screen overflow-hidden">
|
<div class="text-light-100 dark:text-dark-100 flex bg-light-0 dark:bg-dark-0 h-screen overflow-hidden">
|
||||||
<NuxtRouteAnnouncer/>
|
<NuxtRouteAnnouncer/>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<ToastProvider>
|
<NuxtLayout>
|
||||||
<NuxtLayout>
|
<div class="px-12 flex flex-1 justify-center">
|
||||||
<div class="px-12 flex flex-1 justify-center">
|
<NuxtPage></NuxtPage>
|
||||||
<NuxtPage></NuxtPage>
|
</div>
|
||||||
</div>
|
</NuxtLayout>
|
||||||
</NuxtLayout>
|
<Toaster v-model="list" />
|
||||||
</ToastProvider>
|
</TooltipProvider>
|
||||||
</TooltipProvider>
|
</div>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
provideToaster();
|
||||||
|
|
||||||
|
const { list } = useToast();
|
||||||
|
</script>
|
||||||
|
|
@ -1,84 +0,0 @@
|
||||||
<template>
|
|
||||||
<ToastRoot :default-open="open" :duration="duration" class="ToastRoot bg-light-10 dark:bg-dark-10 border border-light-30 dark:border-dark-30" v-model:open="model">
|
|
||||||
<div class="grid grid-cols-8 px-3 pt-2 pb-2">
|
|
||||||
<ToastTitle v-if="title" class="font-semibold text-xl col-span-7 text-light-70 dark:text-dark-70" asChild><h4>{{ title }}</h4></ToastTitle>
|
|
||||||
<ToastClose v-if="closeable" aria-label="Close" class="text-xl -translate-y-2 translate-x-4 cursor-pointer"><span aria-hidden>×</span></ToastClose>
|
|
||||||
<ToastDescription v-if="content" class="text-sm col-span-8 text-light-70 dark:text-dark-70" asChild><span>{{ content }}</span></ToastDescription>
|
|
||||||
</div>
|
|
||||||
<TimerProgress v-if="timer" shape="thin" :delay="duration" class="mb-0 mt-0 w-full" />
|
|
||||||
</ToastRoot>
|
|
||||||
|
|
||||||
<ToastViewport class="fixed bottom-0 right-0 flex flex-col p-6 gap-2 max-w-[512px] z-50 outline-none min-w-72" />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
const model = defineModel<boolean>();
|
|
||||||
|
|
||||||
const { closeable = true, duration, title, content, timer = true } = defineProps<{
|
|
||||||
closeable?: boolean
|
|
||||||
duration?: number
|
|
||||||
title?: string
|
|
||||||
content?: string
|
|
||||||
timer?: boolean
|
|
||||||
open?: boolean
|
|
||||||
}>();
|
|
||||||
const timeout = ref<NodeJS.Timeout>();
|
|
||||||
|
|
||||||
watch(model, (value) => {
|
|
||||||
if(duration)
|
|
||||||
{
|
|
||||||
if(value === true)
|
|
||||||
{
|
|
||||||
timeout.value = setTimeout(() => model.value = false, duration);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
clearTimeout(timeout.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.ToastRoot[data-state='open'] {
|
|
||||||
animation: slideIn .15s cubic-bezier(0.16, 1, 0.3, 1);
|
|
||||||
}
|
|
||||||
.ToastRoot[data-state='closed'] {
|
|
||||||
animation: hide .1s ease-in;
|
|
||||||
}
|
|
||||||
.ToastRoot[data-swipe='move'] {
|
|
||||||
transform: translateX(var(--radix-toast-swipe-move-x));
|
|
||||||
}
|
|
||||||
.ToastRoot[data-swipe='cancel'] {
|
|
||||||
transform: translateX(0);
|
|
||||||
transition: transform .2s ease-out;
|
|
||||||
}
|
|
||||||
.ToastRoot[data-swipe='end'] {
|
|
||||||
animation: swipeRight .1s ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes hide {
|
|
||||||
from {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@keyframes slideIn {
|
|
||||||
from {
|
|
||||||
transform: translateX(calc(100% + var(--viewport-padding)));
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
transform: translateX(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@keyframes swipeRight {
|
|
||||||
from {
|
|
||||||
transform: translateX(var(--radix-toast-swipe-end-x));
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
transform: translateX(100%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -0,0 +1,92 @@
|
||||||
|
<template>
|
||||||
|
<ToastProvider>
|
||||||
|
<ToastRoot v-for="toast in model" :key="toast.id" :duration="toast.duration" class="ToastRoot bg-light-10 dark:bg-dark-10 border border-light-30 dark:border-dark-30 group" :open="toast.state ?? true" @update:open="(state: boolean) => tryClose(toast, state)" :data-type="toast.type ?? 'info'">
|
||||||
|
<div class="grid grid-cols-8 px-3 pt-2 pb-2">
|
||||||
|
<ToastTitle v-if="toast.title" class="font-semibold text-xl col-span-7 text-light-70 dark:text-dark-70" asChild><h4>{{ toast.title }}</h4></ToastTitle>
|
||||||
|
<ToastClose v-if="toast.closeable" aria-label="Close" class="text-xl -translate-y-2 translate-x-4 cursor-pointer"><span aria-hidden>×</span></ToastClose>
|
||||||
|
<ToastDescription v-if="toast.content" class="text-sm col-span-8 text-light-70 dark:text-dark-70" asChild><span>{{ toast.content }}</span></ToastDescription>
|
||||||
|
</div>
|
||||||
|
<TimerProgress v-if="toast.timer" shape="thin" :delay="toast.duration" class="mb-0 mt-0 w-full group-data-[type=error]:bg-light-red dark:group-data-[type=error]:bg-dark-red group-data-[type=error]:bg-opacity-50 dark:group-data-[type=error]:bg-opacity-50 group-data-[type=error]:*:bg-light-red dark:group-data-[type=error]:*:bg-dark-red
|
||||||
|
group-data-[type=success]:bg-light-green dark:group-data-[type=success]:bg-dark-green group-data-[type=success]:bg-opacity-50 dark:group-data-[type=success]:bg-opacity-50 group-data-[type=success]:*:bg-light-green dark:group-data-[type=success]:*:bg-dark-green" @finish="() => tryClose(toast, false)" />
|
||||||
|
</ToastRoot>
|
||||||
|
|
||||||
|
<ToastViewport class="fixed bottom-0 right-0 flex flex-col p-6 gap-2 max-w-[512px] z-50 outline-none min-w-72" />
|
||||||
|
</ToastProvider>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const model = defineModel<ExtraToastConfig[]>();
|
||||||
|
|
||||||
|
function tryClose(config: ExtraToastConfig, state: boolean)
|
||||||
|
{
|
||||||
|
if(!state)
|
||||||
|
{
|
||||||
|
const m = model.value;
|
||||||
|
if(m)
|
||||||
|
{
|
||||||
|
const idx = m?.findIndex(e => e.id === config.id);
|
||||||
|
m[idx].state = false;
|
||||||
|
model.value = m;
|
||||||
|
}
|
||||||
|
setTimeout(() => model.value?.splice(model.value?.findIndex(e => e.id === config.id), 1), 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.ToastRoot[data-type='error'] {
|
||||||
|
@apply border-light-red;
|
||||||
|
@apply dark:border-dark-red;
|
||||||
|
@apply bg-light-red;
|
||||||
|
@apply dark:bg-dark-red;
|
||||||
|
@apply bg-opacity-15;
|
||||||
|
}
|
||||||
|
.ToastRoot[data-type='success'] {
|
||||||
|
@apply border-light-green;
|
||||||
|
@apply dark:border-dark-green;
|
||||||
|
@apply bg-light-green;
|
||||||
|
@apply dark:bg-dark-green;
|
||||||
|
@apply bg-opacity-15;
|
||||||
|
}
|
||||||
|
.ToastRoot[data-state='open'] {
|
||||||
|
animation: slideIn .15s cubic-bezier(0.16, 1, 0.3, 1);
|
||||||
|
}
|
||||||
|
.ToastRoot[data-state='closed'] {
|
||||||
|
animation: hide .1s ease-in;
|
||||||
|
}
|
||||||
|
.ToastRoot[data-swipe='move'] {
|
||||||
|
transform: translateX(var(--radix-toast-swipe-move-x));
|
||||||
|
}
|
||||||
|
.ToastRoot[data-swipe='cancel'] {
|
||||||
|
transform: translateX(0);
|
||||||
|
transition: transform .2s ease-out;
|
||||||
|
}
|
||||||
|
.ToastRoot[data-swipe='end'] {
|
||||||
|
animation: swipeRight .1s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes hide {
|
||||||
|
from {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes slideIn {
|
||||||
|
from {
|
||||||
|
transform: translateX(calc(100% + var(--viewport-padding)));
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes swipeRight {
|
||||||
|
from {
|
||||||
|
transform: translateX(var(--radix-toast-swipe-end-x));
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateX(100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -1,4 +1,40 @@
|
||||||
export default function useToast()
|
export interface ToastConfig
|
||||||
{
|
{
|
||||||
|
closeable?: boolean
|
||||||
|
duration: number
|
||||||
|
title?: string
|
||||||
|
content?: string
|
||||||
|
timer?: boolean
|
||||||
|
type?: ToastType
|
||||||
|
}
|
||||||
|
export type ToastType = 'info' | 'success' | 'error';
|
||||||
|
export type ExtraToastConfig = ToastConfig & { id: string, state: boolean };
|
||||||
|
|
||||||
|
let id = 0;
|
||||||
|
|
||||||
|
const [provideToaster, useToast] = createInjectionState(() => {
|
||||||
|
const list = ref<ExtraToastConfig[]>([]);
|
||||||
|
|
||||||
|
function add(config: ToastConfig)
|
||||||
|
{
|
||||||
|
list.value.push({ ...config, id: (++id).toString(), state: true, });
|
||||||
|
}
|
||||||
|
function clear(type?: ToastType)
|
||||||
|
{
|
||||||
|
list.value.forEach(e => { if(e.type !== type) { e.state = false; } });
|
||||||
|
}
|
||||||
|
|
||||||
|
return { list, add, clear }
|
||||||
|
}, { injectionKey: Symbol('toaster') });
|
||||||
|
|
||||||
|
export { provideToaster, useToastWithDefault as useToast };
|
||||||
|
|
||||||
|
function useToastWithDefault()
|
||||||
|
{
|
||||||
|
const toasts = useToast();
|
||||||
|
if(!toasts)
|
||||||
|
{
|
||||||
|
return { list: ref<ExtraToastConfig[]>([]), add: () => {}, clear: () => {} };
|
||||||
|
}
|
||||||
|
return toasts;
|
||||||
}
|
}
|
||||||
BIN
db.sqlite-shm
BIN
db.sqlite-shm
Binary file not shown.
|
|
@ -44,7 +44,5 @@ async function fetch()
|
||||||
<Button class="self-center" @click="() => !!job && fetch()" :loading="status === 'pending'">
|
<Button class="self-center" @click="() => !!job && fetch()" :loading="status === 'pending'">
|
||||||
<span>Executer</span>
|
<span>Executer</span>
|
||||||
</Button>
|
</Button>
|
||||||
<Toast :duration="10000" title="Erreur" :content="error?.message" v-model="err" />
|
|
||||||
<Toast :duration="10000" content="Traitement terminé" v-model="success" :closeable="false" />
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -31,7 +31,6 @@ watch(loading, (value) => {
|
||||||
<Button :loading="loading" @click="() => loading = true">
|
<Button :loading="loading" @click="() => loading = true">
|
||||||
<span v-if="!loading">Load data</span>
|
<span v-if="!loading">Load data</span>
|
||||||
</Button>
|
</Button>
|
||||||
<Toast v-model="open" :duration="7500" content="Je suis un long texte pour tester comment ça se comporte aaaaaaaa aaaaaaaa aaaaaaaaa aaaaaaaa" :closeable="false" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -3,25 +3,30 @@
|
||||||
<Title>Connexion</Title>
|
<Title>Connexion</Title>
|
||||||
</Head>
|
</Head>
|
||||||
<div class="flex flex-1 flex-col justify-center items-center">
|
<div class="flex flex-1 flex-col justify-center items-center">
|
||||||
<ProseH4>Connexion</ProseH4>
|
<div class="flex gap-8 items-center">
|
||||||
|
<span class="border border-transparent hover:border-light-35 dark:hover:border-dark-35 p-1 cursor-pointer" @click="() => $router.go(-1)"><Icon icon="radix-icons:arrow-left" class="text-light-50 dark:text-dark-50 w-6 h-6"/></span>
|
||||||
|
<ProseH4>Connexion</ProseH4>
|
||||||
|
</div>
|
||||||
<form @submit.prevent="() => submit()" class="flex flex-1 flex-col justify-center items-stretch">
|
<form @submit.prevent="() => submit()" class="flex flex-1 flex-col justify-center items-stretch">
|
||||||
<TextInput type="text" label="Utilisateur ou email" autocomplete="username" v-model="state.usernameOrEmail"/>
|
<TextInput type="text" label="Utilisateur ou email" autocomplete="username" v-model="state.usernameOrEmail"/>
|
||||||
<TextInput type="password" label="Mot de passe" autocomplete="current-password" v-model="state.password"/>
|
<TextInput type="password" label="Mot de passe" autocomplete="current-password" v-model="state.password"/>
|
||||||
<Button class="border border-light-35 dark:border-dark-35 self-center" :loading="status === 'pending'">Se connecter</Button>
|
<Button class="border border-light-35 dark:border-dark-35 self-center" :loading="status === 'pending'">Se connecter</Button>
|
||||||
<NuxtLink class="mt-4 text-center block text-sm font-semibold tracking-wide hover:text-accent-blue" :to="{ path: `/user/register`, force: true }">Pas de compte ?</NuxtLink>
|
<NuxtLink class="mt-4 text-center block text-sm font-semibold tracking-wide hover:text-accent-blue" :to="{ path: `/user/register`, force: true }">Pas de compte ?</NuxtLink>
|
||||||
</form>
|
</form>
|
||||||
<Toast :closeable="false" :content="toastMessage" :duration="10000" timer />
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { ZodError } from 'zod';
|
import type { ZodError } from 'zod';
|
||||||
import { schema, type Login } from '~/schemas/login';
|
import { schema, type Login } from '~/schemas/login';
|
||||||
|
import { Icon } from '@iconify/vue/dist/iconify.js';
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
layout: 'login',
|
layout: 'login',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { add: addToast, clear: clearToasts } = useToast();
|
||||||
|
|
||||||
const state = reactive<Login>({
|
const state = reactive<Login>({
|
||||||
usernameOrEmail: '',
|
usernameOrEmail: '',
|
||||||
password: ''
|
password: ''
|
||||||
|
|
@ -39,15 +44,16 @@ const toastMessage = ref('');
|
||||||
|
|
||||||
async function submit()
|
async function submit()
|
||||||
{
|
{
|
||||||
|
if(state.usernameOrEmail === "")
|
||||||
|
return addToast({ content: 'Veuillez saisir un nom d\'utilisateur ou un email', timer: true, duration: 10000 });
|
||||||
if(state.password === "")
|
if(state.password === "")
|
||||||
return;
|
return addToast({ content: 'Veuillez saisir un mot de passe', timer: true, duration: 10000 });
|
||||||
|
|
||||||
const data = schema.safeParse(state);
|
const data = schema.safeParse(state);
|
||||||
|
|
||||||
if(data.success)
|
if(data.success)
|
||||||
{
|
{
|
||||||
await refresh();
|
await refresh();
|
||||||
debugger;
|
|
||||||
|
|
||||||
const login = result.value;
|
const login = result.value;
|
||||||
if(!login || !login.success)
|
if(!login || !login.success)
|
||||||
|
|
@ -56,6 +62,8 @@ async function submit()
|
||||||
}
|
}
|
||||||
else if(status.value === 'success' && login.success)
|
else if(status.value === 'success' && login.success)
|
||||||
{
|
{
|
||||||
|
clearToasts();
|
||||||
|
addToast({ duration: 10000, content: 'Vous êtes maintenant connecté', timer: true, type: 'success' });
|
||||||
await navigateTo('/user/profile');
|
await navigateTo('/user/profile');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -75,13 +83,12 @@ function handleErrors(error: Error | ZodError)
|
||||||
{
|
{
|
||||||
for(const err of (error as ZodError).issues)
|
for(const err of (error as ZodError).issues)
|
||||||
{
|
{
|
||||||
toastMessage.value = err.message;
|
return addToast({ content: err.message, timer: true, duration: 10000, type: 'error' });
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
toastMessage.value = error?.message ?? 'Erreur inconnue.';
|
return addToast({ content: error?.message ?? 'Une erreur est survenue', timer: true, duration: 10000, type: 'error' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -3,28 +3,31 @@
|
||||||
<Title>Inscription</Title>
|
<Title>Inscription</Title>
|
||||||
</Head>
|
</Head>
|
||||||
<div class="flex flex-1 flex-col justify-center items-center">
|
<div class="flex flex-1 flex-col justify-center items-center">
|
||||||
<ProseH4>Inscription</ProseH4>
|
<div class="flex gap-8 items-center">
|
||||||
<form @submit.prevent="() => submit()" class="flex flex-1 flex-col justify-center items-stretch p-4">
|
<span class="border border-transparent hover:border-light-35 dark:hover:border-dark-35 p-1 cursor-pointer" @click="() => $router.go(-1)"><Icon icon="radix-icons:arrow-left" class="text-light-50 dark:text-dark-50 w-6 h-6"/></span>
|
||||||
<TextInput type="text" label="Nom d'utilisateur" autocomplete="username" v-model="state.username"/>
|
<ProseH4>Inscription</ProseH4>
|
||||||
<TextInput type="email" label="Email" autocomplete="email" v-model="state.email"/>
|
</div>
|
||||||
<TextInput type="password" label="Mot de passe" autocomplete="new-password" v-model="state.password"/>
|
<form @submit.prevent="() => submit()" class="grid flex-1 p-4 grid-cols-2 md:grid-cols-1 gap-4 md:gap-0">
|
||||||
<div class="flex flex-col font-light border border-light-35 dark:border-dark-35 px-4 py-2 me-4">
|
<TextInput type="text" label="Nom d'utilisateur" autocomplete="username" v-model="state.username" class="w-full md:w-auto"/>
|
||||||
|
<TextInput type="email" label="Email" autocomplete="email" v-model="state.email" class="w-full md:w-auto"/>
|
||||||
|
<TextInput type="password" label="Mot de passe" autocomplete="new-password" v-model="state.password" class="w-full md:w-auto"/>
|
||||||
|
<div class="flex flex-col font-light border border-light-35 dark:border-dark-35 px-4 py-2 m-4 ms-0 text-sm leading-[18px] lg:text-base order-8 col-span-2 md:col-span-1 md:order-none">
|
||||||
<span class="">Votre mot de passe doit respecter les critères de sécurité suivants
|
<span class="">Votre mot de passe doit respecter les critères de sécurité suivants
|
||||||
:</span>
|
:</span>
|
||||||
<span class="px-4" :class="{'text-light-red dark:text-dark-red': !checkedLength}">Entre 8 et 128
|
<span class="ps-4" :class="{'text-light-red dark:text-dark-red': !checkedLength}">Entre 8 et 128
|
||||||
caractères</span>
|
caractères</span>
|
||||||
<span class="px-4" :class="{'text-light-red dark:text-dark-red': !checkedLowerUpper}">Au moins
|
<span class="ps-4" :class="{'text-light-red dark:text-dark-red': !checkedLowerUpper}">Au moins
|
||||||
une minuscule et une majuscule</span>
|
une minuscule et une majuscule</span>
|
||||||
<span class="px-4" :class="{'text-light-red dark:text-dark-red': !checkedDigit}">Au moins un
|
<span class="ps-4" :class="{'text-light-red dark:text-dark-red': !checkedDigit}">Au moins un
|
||||||
chiffre</span>
|
chiffre</span>
|
||||||
<span class="px-4" :class="{'text-light-red dark:text-dark-red': !checkedSymbol}">Au moins un
|
<span class="ps-4" :class="{'text-light-red dark:text-dark-red': !checkedSymbol}">Au moins un
|
||||||
caractère spécial parmis la liste suivante:
|
caractère spécial parmi la liste suivante:
|
||||||
<pre class="text-wrap">! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ ] ^ _ ` { | } ~</pre>
|
<pre class="text-wrap">! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ ] ^ _ ` { | } ~</pre>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<TextInput type="password" label="Confirmation du mot de passe" autocomplete="new-password" v-model="confirmPassword"/>
|
<TextInput type="password" label="Confirmation du mot de passe" autocomplete="new-password" v-model="confirmPassword" class="w-full md:w-auto"/>
|
||||||
<Button class="border border-light-35 dark:border-dark-35 self-center" :loading="status === 'pending'">Se connecter</Button>
|
<Button class="border border-light-35 dark:border-dark-35 max-w-48 w-full order-9 col-span-2 md:col-span-1 m-auto" :loading="status === 'pending'">S'inscrire</Button>
|
||||||
<NuxtLink class="mt-4 text-center block text-sm font-semibold tracking-wide hover:text-accent-blue" :to="{ path: `/user/register`, force: true }">Pas de compte ?</NuxtLink>
|
<span class="mt-4 order-10 flex justify-center items-center gap-4 col-span-2 md:col-span-1 m-auto">Vous avez déjà un compte ?<NuxtLink class="text-center block text-sm font-semibold tracking-wide hover:text-accent-blue" :to="{ path: `/user/login`, force: true }">Se connecter</NuxtLink></span>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -32,6 +35,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ZodError } from 'zod';
|
import { ZodError } from 'zod';
|
||||||
import { schema, type Registration } from '~/schemas/registration';
|
import { schema, type Registration } from '~/schemas/registration';
|
||||||
|
import { Icon } from '@iconify/vue/dist/iconify.js';
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
layout: 'login',
|
layout: 'login',
|
||||||
|
|
@ -43,6 +47,7 @@ const state = reactive<Registration>({
|
||||||
password: ''
|
password: ''
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { add: addToast, clear: clearToasts } = useToast();
|
||||||
const confirmPassword = ref("");
|
const confirmPassword = ref("");
|
||||||
|
|
||||||
const checkedLength = computed(() => state.password.length >= 8 && state.password.length <= 128);
|
const checkedLength = computed(() => state.password.length >= 8 && state.password.length <= 128);
|
||||||
|
|
@ -50,11 +55,7 @@ const checkedLowerUpper = computed(() => state.password.toLowerCase() !== state.
|
||||||
const checkedDigit = computed(() => /[0-9]/.test(state.password));
|
const checkedDigit = computed(() => /[0-9]/.test(state.password));
|
||||||
const checkedSymbol = computed(() => " !\"#$%&'()*+,-./:;<=>?@[]^_`{|}~".split("").some(e => state.password.includes(e)));
|
const checkedSymbol = computed(() => " !\"#$%&'()*+,-./:;<=>?@[]^_`{|}~".split("").some(e => state.password.includes(e)));
|
||||||
|
|
||||||
const usernameError = ref("");
|
const { data: result, status, error, refresh } = await useFetch('/api/auth/register', {
|
||||||
const emailError = ref("");
|
|
||||||
const generalError = ref("");
|
|
||||||
|
|
||||||
const { data: result, status, error, execute } = await useFetch('/api/auth/register', {
|
|
||||||
body: state,
|
body: state,
|
||||||
immediate: false,
|
immediate: false,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|
@ -64,15 +65,20 @@ const { data: result, status, error, execute } = await useFetch('/api/auth/regis
|
||||||
|
|
||||||
async function submit()
|
async function submit()
|
||||||
{
|
{
|
||||||
if(state.password === "" || state.password !== confirmPassword.value)
|
if(state.username === '')
|
||||||
return;
|
return addToast({ content: 'Veuillez saisir un nom d\'utilisateur', timer: true, duration: 10000 });
|
||||||
|
if(state.email === '')
|
||||||
|
return addToast({ content: 'Veuillez saisir une adresse mail', timer: true, duration: 10000 });
|
||||||
|
if(state.password === "")
|
||||||
|
return addToast({ content: 'Veuillez saisir un mot de passe', timer: true, duration: 10000 });
|
||||||
|
if(state.password !== confirmPassword.value)
|
||||||
|
return addToast({ content: 'Les deux mots de passe saisis ne correspondent pas', timer: true, duration: 10000 });
|
||||||
|
|
||||||
const data = schema.safeParse(state);
|
const data = schema.safeParse(state);
|
||||||
|
|
||||||
if(data.success)
|
if(data.success)
|
||||||
{
|
{
|
||||||
await execute();
|
await refresh();
|
||||||
debugger;
|
|
||||||
|
|
||||||
const login = result.value;
|
const login = result.value;
|
||||||
if(!login || !login.success)
|
if(!login || !login.success)
|
||||||
|
|
@ -81,6 +87,8 @@ async function submit()
|
||||||
}
|
}
|
||||||
else if(status.value === 'success' && login.success)
|
else if(status.value === 'success' && login.success)
|
||||||
{
|
{
|
||||||
|
clearToasts();
|
||||||
|
addToast({ duration: 10000, content: 'Vous avez été enregistré. Pensez à valider votre adresse mail.', timer: true, type: 'success' });
|
||||||
await navigateTo('/user/profile');
|
await navigateTo('/user/profile');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -91,23 +99,21 @@ async function submit()
|
||||||
}
|
}
|
||||||
function handleErrors(error: Error | ZodError)
|
function handleErrors(error: Error | ZodError)
|
||||||
{
|
{
|
||||||
|
if(!error)
|
||||||
|
return;
|
||||||
|
|
||||||
|
status.value = 'error';
|
||||||
|
|
||||||
if(error.hasOwnProperty('issues'))
|
if(error.hasOwnProperty('issues'))
|
||||||
{
|
{
|
||||||
for(const err of (error as ZodError).issues)
|
for(const err of (error as ZodError).issues)
|
||||||
{
|
{
|
||||||
if(err.path.includes('username'))
|
return addToast({ content: err.message, timer: true, duration: 10000, type: 'error' });
|
||||||
{
|
|
||||||
usernameError.value = err.message;
|
|
||||||
}
|
|
||||||
if(err.path.includes('email'))
|
|
||||||
{
|
|
||||||
emailError.value = err.message;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
generalError.value = error?.message ?? 'Erreur inconnue.';
|
return addToast({ content: error?.message ?? 'Une erreur est survenue', timer: true, duration: 10000, type: 'error' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
Loading…
Reference in New Issue