diff --git a/wp-content/themes/headless/app/components/auth/AuthLoginForm.vue b/wp-content/themes/headless/app/components/auth/AuthLoginForm.vue new file mode 100644 index 0000000..7ba55e7 --- /dev/null +++ b/wp-content/themes/headless/app/components/auth/AuthLoginForm.vue @@ -0,0 +1,31 @@ + + + + + diff --git a/wp-content/themes/headless/app/components/auth/AuthLogoutForm.vue b/wp-content/themes/headless/app/components/auth/AuthLogoutForm.vue new file mode 100644 index 0000000..eb2bb94 --- /dev/null +++ b/wp-content/themes/headless/app/components/auth/AuthLogoutForm.vue @@ -0,0 +1,20 @@ + + + + + + Déconnexion + Veuillez confirmer la déconnexion. + + + + diff --git a/wp-content/themes/headless/app/components/auth/AuthRedirecting.vue b/wp-content/themes/headless/app/components/auth/AuthRedirecting.vue new file mode 100644 index 0000000..2512c50 --- /dev/null +++ b/wp-content/themes/headless/app/components/auth/AuthRedirecting.vue @@ -0,0 +1,8 @@ + + + + Redirection en cours + Veuillez patienter... + + + diff --git a/wp-content/themes/headless/app/composables/useAuth.ts b/wp-content/themes/headless/app/composables/useAuth.ts new file mode 100644 index 0000000..aed5532 --- /dev/null +++ b/wp-content/themes/headless/app/composables/useAuth.ts @@ -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 }; +} diff --git a/wp-content/themes/headless/app/composables/useAuthConnexion.ts b/wp-content/themes/headless/app/composables/useAuthConnexion.ts new file mode 100644 index 0000000..0283274 --- /dev/null +++ b/wp-content/themes/headless/app/composables/useAuthConnexion.ts @@ -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, 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 }; +} diff --git a/wp-content/themes/headless/app/pages/connexion.vue b/wp-content/themes/headless/app/pages/connexion.vue new file mode 100644 index 0000000..65fad43 --- /dev/null +++ b/wp-content/themes/headless/app/pages/connexion.vue @@ -0,0 +1,17 @@ + + + + + + + + + + + + + diff --git a/wp-content/themes/headless/nuxt.config.ts b/wp-content/themes/headless/nuxt.config.ts index f3f79ab..ff73c8b 100644 --- a/wp-content/themes/headless/nuxt.config.ts +++ b/wp-content/themes/headless/nuxt.config.ts @@ -48,7 +48,7 @@ export default defineNuxtConfig({ vite: { optimizeDeps: { - include: ["@vue/devtools-core", "@vue/devtools-kit", "zod"], + include: ["@vue/devtools-core", "@vue/devtools-kit", "zod", "es-toolkit/promise"], }, }, }); diff --git a/wp-content/themes/headless/package.json b/wp-content/themes/headless/package.json index 0719b20..9c5f69e 100644 --- a/wp-content/themes/headless/package.json +++ b/wp-content/themes/headless/package.json @@ -22,6 +22,7 @@ "@lewebsimple/nuxt-graphql": "^0.7.5", "@nuxt/ui": "^4.6.0", "@nuxtjs/seo": "^4.0.2", + "es-toolkit": "^1.45.1", "nuxt": "^4.4.2", "nuxt-auth-utils": "^0.5.29", "nuxt-svgo": "^4.2.6", diff --git a/wp-content/themes/headless/pnpm-lock.yaml b/wp-content/themes/headless/pnpm-lock.yaml index 1fdabac..cfdc712 100644 --- a/wp-content/themes/headless/pnpm-lock.yaml +++ b/wp-content/themes/headless/pnpm-lock.yaml @@ -23,6 +23,9 @@ importers: '@nuxtjs/seo': specifier: ^4.0.2 version: 4.0.2(489f84e1ce5b91b262b98d380824761d) + es-toolkit: + specifier: ^1.45.1 + version: 1.45.1 nuxt: specifier: ^4.4.2 version: 4.4.2(@babel/core@7.29.0)(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@parcel/watcher@2.5.6)(@types/node@25.5.0)(@vue/compiler-sfc@3.5.31)(cac@6.7.14)(db0@0.3.4)(ioredis@5.10.1)(lightningcss@1.32.0)(magicast@0.5.2)(oxlint@1.57.0)(rollup-plugin-visualizer@7.0.1(rollup@4.60.0))(rollup@4.60.0)(terser@5.46.1)(typescript@5.9.3)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(yaml@2.8.3))(vue-tsc@3.2.6(typescript@5.9.3))(yaml@2.8.3) diff --git a/wp-content/themes/headless/server/api/login.post.ts b/wp-content/themes/headless/server/api/login.post.ts index 40c3b83..9c31d52 100644 --- a/wp-content/themes/headless/server/api/login.post.ts +++ b/wp-content/themes/headless/server/api/login.post.ts @@ -4,13 +4,12 @@ export default defineEventHandler(async (event) => { const variables = authLoginFormSchema.parse(await readBody(event)); // Execute the GraphQL operation to authenticate the user - const { data, error } = await executeSchemaOperation(event, { + const { data } = await executeSchemaOperation(event, { operationName: "AuthLogin", variables, }); - // Handle errors and validate the response data - if (error) throw error; + // Validate the response data if (!data?.login) throw new Error("Identifiants invalides. Veuillez réessayer."); // Handle the login process by setting the session data @@ -18,7 +17,7 @@ export default defineEventHandler(async (event) => { throw new Error("Une erreur est survenue lors de la connexion."); } - return { success: true, message: "Connexion réussie" }; + return { success: true, message: "Vous avez été connecté avec succès." }; } catch (error) { const message = error instanceof Error ? error.message : "Une erreur est survenue lors de la connexion."; diff --git a/wp-content/themes/headless/server/api/logout.post.ts b/wp-content/themes/headless/server/api/logout.post.ts index a76c2cd..b850265 100644 --- a/wp-content/themes/headless/server/api/logout.post.ts +++ b/wp-content/themes/headless/server/api/logout.post.ts @@ -4,7 +4,7 @@ export default defineEventHandler(async (event) => { try { await handleLogout(event); - return { success: true, message: "Déconnexion réussie" }; + return { success: true, message: "Vous avez été déconnecté avec succès." }; } catch (error) { const message = error instanceof Error ? error.message : "Une erreur est survenue lors de la déconnexion.";