feat: Initial auth components

This commit is contained in:
2026-03-26 14:40:10 -04:00
parent bd108f69a4
commit eb204c003f
11 changed files with 172 additions and 6 deletions

View File

@@ -0,0 +1,31 @@
<script setup lang="ts">
const { login } = useAuthConnexion();
const fields = [
{
name: "username",
type: "text" as const,
label: "Courriel",
placeholder: "Entrez votre courriel",
required: true,
},
{
name: "password",
label: "Mot de passe",
type: "password" as const,
placeholder: "Entrez votre mot de passe",
required: true,
},
];
</script>
<template>
<UAuthForm
:schema="authLoginFormSchema"
:fields="fields"
title="Connexion"
description="Veuillez vous identifier."
loading-auto
@submit="login"
/>
</template>

View File

@@ -0,0 +1,20 @@
<script setup lang="ts">
const { logout } = useAuthConnexion();
</script>
<template>
<div class="w-full space-y-6">
<div class="flex flex-col text-center">
<div class="text-xl font-semibold text-pretty text-highlighted">Déconnexion</div>
<div class="mt-1 text-base text-pretty text-muted">Veuillez confirmer la déconnexion.</div>
</div>
<UButton
icon="i-lucide-log-out"
block
loading-auto
to="#"
label="Déconnexion"
@click="logout()"
/>
</div>
</template>

View File

@@ -0,0 +1,8 @@
<template>
<div class="w-full space-y-6">
<div class="flex flex-col text-center">
<div class="text-xl font-semibold text-pretty text-highlighted">Redirection en cours</div>
<div class="mt-1 text-base text-pretty text-muted">Veuillez patienter...</div>
</div>
</div>
</template>

View File

@@ -0,0 +1,7 @@
export function useAuth() {
const { loggedIn: isLoggedIn, session } = useUserSession();
const hasRole = (role: string) => session.value?.user?.roles?.includes(role) || false;
const isAdmin = computed(() => hasRole("administrator"));
return { isLoggedIn, hasRole, isAdmin };
}

View File

@@ -0,0 +1,80 @@
import { delay } from "es-toolkit/promise";
import type { FormSubmitEvent } from "@nuxt/ui";
export function useAuthConnexion() {
const toast = useToast();
const defaultRedirect = (useRoute().query.redirect as string) || undefined;
const isRedirecting = useState("auth.isRedirecting", () => false);
const { fetch: refreshUserSession } = useUserSession();
// Helper: Redirect after login / logout
async function redirectTo(to: string | undefined) {
isRedirecting.value = true;
await delay(1000);
await refreshUserSession();
await navigateTo(to || defaultRedirect || "/");
}
/**
* Attempt login with the provided credentials.
*
* @param event The form submit event containing the login credentials.
* @param redirect Optional URL to redirect to after successful login.
* @returns A promise that resolves when the login process is complete.
*/
async function login({ data: body }: FormSubmitEvent<AuthLoginForm>, redirect?: string) {
try {
const { success, message } = await $fetch("/api/login", { method: "POST", body });
if (!success) throw new Error(message);
toast.add({
color: "success",
title: "Connexion réussie",
description: message,
});
await redirectTo(redirect);
} catch (error) {
toast.add({
color: "error",
title: "Erreur de connexion",
description:
error instanceof Error ? error.message : "Une erreur est survenue lors de la connexion.",
});
}
}
/**
* Logout the current user.
*
* @param redirect Optional URL to redirect to after successful logout.
* @returns A promise that resolves when the logout process is complete.
*/
async function logout(redirect?: string) {
try {
const { success, message } = await $fetch("/api/logout", { method: "POST" });
if (!success) throw new Error(message);
toast.add({
color: "success",
title: "Déconnexion réussie",
description: message,
});
await redirectTo(redirect);
} catch (error) {
toast.add({
color: "error",
title: "Erreur de déconnexion",
description:
error instanceof Error
? error.message
: "Une erreur est survenue lors de la déconnexion.",
});
}
}
return { isRedirecting, login, logout };
}

View File

@@ -0,0 +1,17 @@
<script setup lang="ts">
const { isLoggedIn } = useAuth();
const { isRedirecting } = useAuthConnexion();
onBeforeMount(() => (isRedirecting.value = false));
</script>
<template>
<div id="page-connexion" class="container-sm py-12">
<AuthState>
<AuthRedirecting v-if="isRedirecting" />
<template v-else>
<AuthLoginForm v-if="!isLoggedIn" />
<AuthLogoutForm v-else />
</template>
</AuthState>
</div>
</template>