diff --git a/app.vue b/app.vue index 09f935bbb637b61773bafe6def88f5231d51f700..8f57c2517d2174ba3b55ac3147361036ab5f31ba 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 0000000000000000000000000000000000000000..921b9e7f82ab4c92e6044f150d21e806108d1992 --- /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 0000000000000000000000000000000000000000..a4c30af4417ef622ac2c521cdecb990bd73669fa --- /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 0000000000000000000000000000000000000000..3bff9353d040abc5d045f04fc8545e18d9a4836c --- /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 0000000000000000000000000000000000000000..67818b9fcf8915ecaeb4ed2fafc95893dd354460 --- /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 0000000000000000000000000000000000000000..0bfc5e2d66d3d5e35c13673affcca47d606b9312 --- /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 0000000000000000000000000000000000000000..48c84f5a2c6ee18885004b922e12d7237c92e37f --- /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 0000000000000000000000000000000000000000..6cb59744802660547345abfdc7954d2531d6308c --- /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