feat: add public site pages and social login

This commit is contained in:
2026-05-04 16:13:57 -04:00
parent cd6f402d9e
commit 802668fb0b
9 changed files with 617 additions and 131 deletions

View File

@@ -45,23 +45,32 @@ export function useFacebookLogin() {
const loginWithFacebook = () => {
if (!isSdkLoaded.value) {
console.error("Facebook SDK non encore chargé !");
return;
return Promise.reject(new Error("Facebook SDK is not loaded"));
}
window.FB.login(
(response) => {
if (response.authResponse) {
console.log("Utilisateur connecté :", response);
const authStore = useAuthStore();
authStore.loginWithFacebook(response.authResponse);
} else {
console.log("Connexion annulée ou échouée.");
return new Promise((resolve, reject) => {
window.FB.login(
async (response) => {
if (!response.authResponse) {
console.log("Connexion annulée ou échouée.");
reject(new Error("Facebook login was cancelled or failed"));
return;
}
try {
console.log("Utilisateur connecté :", response);
const authStore = useAuthStore();
const result = await authStore.loginWithFacebook(response.authResponse);
resolve(result);
} catch (error) {
reject(error);
}
},
{
scope: "public_profile,email"
}
},
{
scope: "public_profile,email"
}
);
);
});
};
onMounted(() => {

View File

@@ -1,94 +1,116 @@
<template>
<div class="flex min-h-full w-full items-center justify-center p-4">
<div class="flex w-full max-w-[512px] flex-col gap-10">
<h1 class="login-text text-center text-2xl font-bold">
{{ t('title') }}
</h1>
<div class="login-page">
<div class="login-wrap">
<router-link
class="login-brand"
to="/"
>
<span class="login-brand-mark">S</span>
<span class="login-brand-text">Socialize</span>
</router-link>
<div class="flex flex-col gap-4">
<google-login
:callback="googleCallback"
popup-type="TOKEN"
>
<button class="secondary">
<section class="login-card">
<h1 class="login-text text-center text-2xl font-bold">
{{ t('title') }}
</h1>
<div class="flex flex-col gap-4">
<google-login
:callback="googleCallback"
popup-type="TOKEN"
>
<button class="secondary">
<v-icon
:icon="mdiGoogle"
class="mr-2"
/>
{{ t('continueWithGoogle') }}
</button>
</google-login>
<button
class="secondary"
type="button"
@click="handleFacebookLogin"
>
<v-icon
:icon="mdiGoogle"
:icon="mdiFacebook"
class="mr-2"
/>
{{ t('continueWithGoogle') }}
{{ t('continueWithFacebook') }}
</button>
</google-login>
</div>
<div class="my-4 flex items-center">
<div class="h-px grow bg-gray-200"></div>
<span class="px-3 text-sm font-semibold uppercase text-gray-300">{{ t('orContinueWith') }}</span>
<div class="h-px grow bg-gray-200"></div>
</div>
<!-- Add email/password form -->
<v-form @submit.prevent="handleLocalLogin">
<div class="flex flex-col gap-4">
<v-text-field
v-model="email"
:label="t('email')"
required
type="email"
></v-text-field>
<v-text-field
v-model="password"
:label="t('password')"
:type="showPassword ? 'text' : 'password'"
required
>
<template v-slot:append-inner>
<v-icon
:icon="showPassword ? mdiEyeOff : mdiEye"
class="visibility-toggle"
size="small"
@click="showPassword = !showPassword"
/>
</template>
</v-text-field>
<v-btn
block
color="primary"
type="submit"
>
{{ t('signIn') }}
</v-btn>
<div class="text-center">
<a
class="cursor-pointer text-sm text-blue-500"
@click="forgotPassword"
>
{{ t('forgotPassword') }}
</a>
</div>
<div class="mt-2 text-center">
<a
class="cursor-pointer text-sm text-blue-500"
@click="resendVerification"
>
{{ t('resendVerification') }}
</a>
</div>
<div class="mt-4 text-center">
{{ t('noAccount') }}
<router-link
class="text-blue-500"
to="/register"
>
{{ t('register') }}
</router-link>
</div>
</div>
</v-form>
<div class="my-4 flex items-center">
<div class="h-px grow bg-gray-200"></div>
<span class="px-3 text-sm font-semibold uppercase text-gray-300">{{ t('orContinueWith') }}</span>
<div class="h-px grow bg-gray-200"></div>
</div>
<!-- Add email/password form -->
<v-form @submit.prevent="handleLocalLogin">
<div class="flex flex-col gap-4">
<v-text-field
v-model="email"
:label="t('email')"
required
type="email"
></v-text-field>
<v-text-field
v-model="password"
:label="t('password')"
:type="showPassword ? 'text' : 'password'"
required
>
<template v-slot:append-inner>
<v-icon
:icon="showPassword ? mdiEyeOff : mdiEye"
class="visibility-toggle"
size="small"
@click="showPassword = !showPassword"
/>
</template>
</v-text-field>
<v-btn
block
color="primary"
type="submit"
>
{{ t('signIn') }}
</v-btn>
<div class="text-center">
<a
class="cursor-pointer text-sm text-blue-500"
@click="forgotPassword"
>
{{ t('forgotPassword') }}
</a>
</div>
<div class="mt-2 text-center">
<a
class="cursor-pointer text-sm text-blue-500"
@click="resendVerification"
>
{{ t('resendVerification') }}
</a>
</div>
<div class="mt-4 text-center">
{{ t('noAccount') }}
<router-link
class="text-blue-500"
to="/register"
>
{{ t('register') }}
</router-link>
</div>
</div>
</v-form>
</section>
</div>
<!-- Error notification -->
@@ -105,13 +127,15 @@
import { ref } from 'vue';
import { GoogleLogin } from 'vue3-google-login';
import { useAuthStore } from '@/features/auth/stores/authStore.js';
import { useFacebookLogin } from '@/features/auth/composables/useFacebookLogin.js';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import { mdiEye, mdiEyeOff, mdiGoogle } from '@mdi/js';
import { mdiEye, mdiEyeOff, mdiFacebook, mdiGoogle } from '@mdi/js';
const { t } = useI18n();
const router = useRouter();
const authStore = useAuthStore();
const { loginWithFacebook } = useFacebookLogin();
const email = ref('');
const password = ref('');
@@ -149,6 +173,20 @@
}
}
async function handleFacebookLogin() {
try {
const response = await loginWithFacebook();
if (response === true) {
await router.push(props.returnUrl);
} else {
errorSnackBar.value = true;
}
} catch (error) {
console.error('Facebook login failed:', error);
errorSnackBar.value = true;
}
}
function forgotPassword() {
router.push('/forgot-password');
}
@@ -159,6 +197,41 @@
</script>
<style scoped>
.login-page {
@apply flex min-h-screen w-full items-stretch justify-center sm:items-center sm:p-4;
}
.login-wrap {
@apply flex min-h-screen w-full max-w-[512px] flex-col gap-6 sm:min-h-0;
}
.login-brand {
@apply mx-auto flex items-center gap-3 px-4 pt-6 no-underline sm:px-0 sm:pt-0;
color: #172033;
}
.login-brand-mark {
@apply flex h-11 w-11 items-center justify-center rounded-2xl text-lg font-black;
background: linear-gradient(135deg, #ff8a3d 0%, #ef4444 100%);
color: #fffaf2;
}
.login-brand-text {
@apply text-lg font-black uppercase tracking-[0.18em];
}
.login-card {
@apply flex min-h-0 w-full flex-1 flex-col justify-center gap-10 bg-white/80 px-5 py-8 sm:flex-none sm:rounded-[1.5rem] sm:border sm:p-8;
border-color: rgba(23, 32, 51, 0.08);
box-shadow: none;
}
@media (min-width: 640px) {
.login-card {
box-shadow: 0 18px 40px rgba(23, 32, 51, 0.08);
}
}
.visibility-toggle {
@apply cursor-pointer;
@apply transition-opacity duration-300;
@@ -194,7 +267,8 @@
"noAccount": "Don't have an account?",
"register": "Register",
"loginFailed": "Login failed. Please check your credentials.",
"continueWithGoogle": "Continue with Google"
"continueWithGoogle": "Continue with Google",
"continueWithFacebook": "Continue with Facebook"
},
"fr": {
"title": "Se connecter",
@@ -208,7 +282,8 @@
"noAccount": "Vous n'avez pas de compte?",
"register": "S'inscrire",
"loginFailed": "Échec de la connexion. Veuillez vérifier vos identifiants.",
"continueWithGoogle": "Continuer avec Google"
"continueWithGoogle": "Continuer avec Google",
"continueWithFacebook": "Continuer avec Facebook"
}
}
</i18n>