From 0afa045449caefc8df4f7c098606d75e4e8df8c8 Mon Sep 17 00:00:00 2001 From: Sofiane Lasri-Trienpont <alasri250@gmail.com> Date: Sat, 9 Nov 2024 17:12:21 +0100 Subject: [PATCH] feat: add authentication flows and basic page structure - Implement login and registration pages with validation - Add middleware to verify user existence for route protection - Establish admin dashboard with middleware authentication - Update app.vue for layout and loading improvements - Create auth layout with navigation button - Add initial index page with welcome component --- app.vue | 7 +- layouts/auth.vue | 21 ++++++ middleware/verifyUserDoesntExists.ts | 16 +++++ middleware/verifyUserExists.ts | 16 +++++ pages/admin.vue | 15 +++++ pages/index.vue | 11 ++++ pages/login.vue | 87 +++++++++++++++++++++++++ pages/register.vue | 95 ++++++++++++++++++++++++++++ 8 files changed, 266 insertions(+), 2 deletions(-) create mode 100644 layouts/auth.vue create mode 100644 middleware/verifyUserDoesntExists.ts create mode 100644 middleware/verifyUserExists.ts create mode 100644 pages/admin.vue create mode 100644 pages/index.vue create mode 100644 pages/login.vue create mode 100644 pages/register.vue diff --git a/app.vue b/app.vue index 09f935b..8f57c25 100644 --- a/app.vue +++ b/app.vue @@ -1,6 +1,9 @@ <template> <div> - <NuxtRouteAnnouncer /> - <NuxtWelcome /> + <NuxtLoadingIndicator /> + + <NuxtLayout> + <NuxtPage /> + </NuxtLayout> </div> </template> diff --git a/layouts/auth.vue b/layouts/auth.vue new file mode 100644 index 0000000..921b9e7 --- /dev/null +++ b/layouts/auth.vue @@ -0,0 +1,21 @@ +<script setup lang="ts"> + +</script> + +<template> + <div class="h-screen flex items-center justify-center overlay"> + <UButton + icon="i-heroicons-home" + label="Accueil" + to="/" + color="black" + class="absolute top-4" + /> + + <slot/> + </div> +</template> + +<style scoped> + +</style> \ No newline at end of file diff --git a/middleware/verifyUserDoesntExists.ts b/middleware/verifyUserDoesntExists.ts new file mode 100644 index 0000000..a4c30af --- /dev/null +++ b/middleware/verifyUserDoesntExists.ts @@ -0,0 +1,16 @@ +export default defineNuxtRouteMiddleware(async (to) => { + let userExists = false; + try { + await useFetch('/api/auth/userExists', { + onResponse: ({response}) => { + userExists = response._data.userExists; + }, + }); + } catch (error) { + console.error('Error checking if user exists', error); + return abortNavigation(); + } + if (userExists) { + return navigateTo('/login'); + } +}); \ No newline at end of file diff --git a/middleware/verifyUserExists.ts b/middleware/verifyUserExists.ts new file mode 100644 index 0000000..3bff935 --- /dev/null +++ b/middleware/verifyUserExists.ts @@ -0,0 +1,16 @@ +export default defineNuxtRouteMiddleware(async (to) => { + let userExists = false; + try { + await useFetch('/api/auth/userExists', { + onResponse: ({response}) => { + userExists = response._data.userExists; + }, + }); + } catch (error) { + console.error('Error checking if user exists', error); + return abortNavigation(); + } + if (!userExists) { + return navigateTo('/register'); + } +}); \ No newline at end of file diff --git a/pages/admin.vue b/pages/admin.vue new file mode 100644 index 0000000..67818b9 --- /dev/null +++ b/pages/admin.vue @@ -0,0 +1,15 @@ +<script setup lang="ts"> +definePageMeta({ + middleware: 'auth' +}) +</script> + +<template> + <div> + <h1>Admin Dashboard</h1> + </div> +</template> + +<style scoped> + +</style> \ No newline at end of file diff --git a/pages/index.vue b/pages/index.vue new file mode 100644 index 0000000..0bfc5e2 --- /dev/null +++ b/pages/index.vue @@ -0,0 +1,11 @@ +<script setup lang="ts"> + +</script> + +<template> + <NuxtWelcome /> +</template> + +<style scoped> + +</style> \ No newline at end of file diff --git a/pages/login.vue b/pages/login.vue new file mode 100644 index 0000000..48c84f5 --- /dev/null +++ b/pages/login.vue @@ -0,0 +1,87 @@ +<script setup lang="ts"> +import type {FormError} from "#ui/types"; + +definePageMeta({ + layout: 'auth', + middleware: 'verify-user-exists' +}); + +useSeoMeta({ + title: 'Connexion', +}) + +const uiOptions = { + default: { + submitButton: { + label: 'Se connecter' + } + } +}; + +const formFields = [{ + type: 'email', + label: 'Adresse email', + name: 'email', + placeholder: 'Entrez votre adresse email', + color: 'gray', + required: true +}, { + type: 'password', + label: 'Mot de passe', + name: 'password', + placeholder: 'Entrez votre mot de passe', + color: 'gray', + required: true +}]; + +const validate = (values: any) => { + const errors: FormError[] = []; + if(!values.email) { + errors.push({ path: 'email', message: 'Veuillez entrer votre adresse email' }); + } + if(!values.password) { + errors.push({ path: 'password', message: 'Veuillez entrer votre mot de passe' }); + } + return errors; +}; + +const authStore = useAuthStore(); +const loginErrorMessage = ref<string | null>(null); +const isLoggingIn = ref(false); + +async function onSubmit(data: any) { + isLoggingIn.value = true; + try { + await authStore.login(data.email, data.password); + } catch (error) { + console.error('Login failed', error); + loginErrorMessage.value = 'Identifiants incorrects'; + } finally { + isLoggingIn.value = false; + } +} +</script> + +<template> + <UCard class="max-w-sm w-full"> + <UAuthForm + title="Connexion" + description="Connectez-vous pour accéder à l'administration." + align="top" + icon="i-heroicons-user-circle" + :validate="validate" + :fields="formFields" + :loading="isLoggingIn" + :ui="uiOptions" + @submit="onSubmit" + > + <template #password-hint> + <NuxtLink to="/" class="text-primary font-medium">Mot de passe oublié ?</NuxtLink> + </template> + <template #validation v-if="loginErrorMessage !== null"> + <UAlert color="red" icon="i-heroicons-information-circle-20-solid" + :title="loginErrorMessage" /> + </template> + </UAuthForm> + </UCard> +</template> \ No newline at end of file diff --git a/pages/register.vue b/pages/register.vue new file mode 100644 index 0000000..6cb5974 --- /dev/null +++ b/pages/register.vue @@ -0,0 +1,95 @@ +<script setup lang="ts"> +import type {FormError} from "#ui/types"; +definePageMeta({ + layout: 'auth', + middleware: 'verify-user-doesnt-exists' +}) + +useSeoMeta({ + title: 'Inscription', +}) + +const uiOptions = { + default: { + submitButton: { + label: 'Créer un compte' + } + } +}; + +const formFields = [{ + name: 'name', + type: 'text', + label: 'Nom d\'utilisateur', + placeholder: 'Entrez votre nom d\'utilisateur', + required: true, + description: "Il ne s'agit là que d'un nom d'affichage, vous pourrez le modifier plus tard.", +}, { + type: 'email', + label: 'Adresse email', + name: 'email', + placeholder: 'Entrez votre adresse email', + color: 'gray', + required: true, + description: "Soyez sûr d'entrer une adresse valide, vous serez le seul maître à bord. 👀" +}, { + type: 'password', + label: 'Mot de passe', + name: 'password', + placeholder: 'Entrez votre mot de passe', + color: 'gray', + required: true +}]; + +const validate = (values: any) => { + const errors: FormError[] = []; + if (!values.email) { + errors.push({path: 'email', message: 'Veuillez entrer votre adresse email'}); + } + if (!values.password) { + errors.push({path: 'password', message: 'Veuillez entrer votre mot de passe'}); + } else { + if (values.password.length < 6 || values.password.length > 32) { + errors.push({path: 'password', message: 'Votre mot de passe doit contenir entre 6 et 32 caractères'}); + } + if (!/[A-Z]/.test(values.password) || !/[a-z]/.test(values.password) || !/[0-9]/.test(values.password) || !/[^A-Za-z0-9]/.test(values.password)) { + errors.push({path: 'password', message: 'Votre mot de passe doit contenir au moins une majuscule, une minuscule, un chiffre et un caractère spécial'}); + } + } + if (!values.name) { + errors.push({path: 'name', message: 'Veuillez entrer votre nom d\'utilisateur'}); + } else { + if (values.name.length < 3 || values.name.length > 32) { + errors.push({path: 'name', message: 'Votre nom d\'utilisateur doit contenir entre 3 et 32 caractères'}); + } + } + return errors; +}; + +const isRegisteringIn = ref(false); + +async function onSubmit(data: any) { + console.log('Registering in with data', data); +} +</script> + +<template> + <UCard class="max-w-sm w-full"> + <UAuthForm + title="Inscription" + description="Bienvenue chez vous ! Créez un compte pour finaliser l'installation de votre site internet." + align="top" + icon="i-heroicons-user-circle" + :validate="validate" + :fields="formFields" + :loading="isRegisteringIn" + :ui="uiOptions" + @submit="onSubmit" + > + </UAuthForm> + </UCard> +</template> + +<style scoped> + +</style> \ No newline at end of file -- GitLab