296 lines
9.6 KiB
Vue
296 lines
9.6 KiB
Vue
<template>
|
|
<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>
|
|
|
|
<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="mdiFacebook"
|
|
class="mr-2"
|
|
/>
|
|
{{ t('continueWithFacebook') }}
|
|
</button>
|
|
</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>
|
|
</section>
|
|
</div>
|
|
|
|
<!-- Error notification -->
|
|
<v-snackbar
|
|
v-model="errorSnackBar"
|
|
color="error"
|
|
>
|
|
{{ t('loginFailed') }}
|
|
</v-snackbar>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
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, mdiFacebook, mdiGoogle } from '@mdi/js';
|
|
|
|
const { t } = useI18n();
|
|
const router = useRouter();
|
|
const authStore = useAuthStore();
|
|
const { loginWithFacebook } = useFacebookLogin();
|
|
|
|
const email = ref('');
|
|
const password = ref('');
|
|
const errorSnackBar = ref(false);
|
|
const showPassword = ref(false);
|
|
|
|
const props = defineProps({
|
|
returnUrl: {
|
|
type: String,
|
|
default: '/app/dashboard',
|
|
},
|
|
});
|
|
|
|
function getPostLoginUrl() {
|
|
return props.returnUrl?.startsWith('/app')
|
|
? props.returnUrl
|
|
: '/app/dashboard';
|
|
}
|
|
|
|
async function handleLocalLogin() {
|
|
try {
|
|
await authStore.login(email.value, password.value);
|
|
await router.push(getPostLoginUrl());
|
|
} catch (error) {
|
|
console.error('Login failed:', error);
|
|
errorSnackBar.value = true;
|
|
}
|
|
}
|
|
|
|
async function googleCallback(token) {
|
|
try {
|
|
const response = await authStore.loginWithGoogle(JSON.stringify(token));
|
|
if (response === true) {
|
|
await router.push(getPostLoginUrl());
|
|
} else {
|
|
errorSnackBar.value = true;
|
|
}
|
|
} catch (error) {
|
|
console.error('Login failed:', error);
|
|
errorSnackBar.value = true;
|
|
}
|
|
}
|
|
|
|
async function handleFacebookLogin() {
|
|
try {
|
|
const response = await loginWithFacebook();
|
|
if (response === true) {
|
|
await router.push(getPostLoginUrl());
|
|
} else {
|
|
errorSnackBar.value = true;
|
|
}
|
|
} catch (error) {
|
|
console.error('Facebook login failed:', error);
|
|
errorSnackBar.value = true;
|
|
}
|
|
}
|
|
|
|
function forgotPassword() {
|
|
router.push('/forgot-password');
|
|
}
|
|
|
|
function resendVerification() {
|
|
router.push('/verify-email');
|
|
}
|
|
</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;
|
|
@apply opacity-60 hover:opacity-100;
|
|
@apply z-10;
|
|
}
|
|
|
|
/* Override Vuetify's default padding to accommodate our icon */
|
|
:deep(.v-field__append-inner) {
|
|
padding-inline-start: 0;
|
|
}
|
|
|
|
/* Dark mode support if needed */
|
|
@media (prefers-color-scheme: dark) {
|
|
.custom-divider {
|
|
background-color: rgb(75, 85, 99);
|
|
/* Equivalent to gray-600 */
|
|
}
|
|
}
|
|
</style>
|
|
|
|
<i18n>
|
|
{
|
|
"en": {
|
|
"title": "Sign in",
|
|
"alt": "Login",
|
|
"email": "Email",
|
|
"password": "Password",
|
|
"signIn": "Connect",
|
|
"forgotPassword": "Forgot password?",
|
|
"resendVerification": "Resend verification email",
|
|
"orContinueWith": "Or",
|
|
"noAccount": "Don't have an account?",
|
|
"register": "Register",
|
|
"loginFailed": "Login failed. Please check your credentials.",
|
|
"continueWithGoogle": "Continue with Google",
|
|
"continueWithFacebook": "Continue with Facebook"
|
|
},
|
|
"fr": {
|
|
"title": "Se connecter",
|
|
"alt": "Connexion",
|
|
"email": "Email",
|
|
"password": "Mot de passe",
|
|
"signIn": "Connexion",
|
|
"forgotPassword": "Mot de passe oublié?",
|
|
"resendVerification": "Renvoyer l'email de vérification",
|
|
"orContinueWith": "Ou",
|
|
"noAccount": "Vous n'avez pas de compte?",
|
|
"register": "S'inscrire",
|
|
"loginFailed": "Échec de la connexion. Veuillez vérifier vos identifiants.",
|
|
"continueWithGoogle": "Continuer avec Google",
|
|
"continueWithFacebook": "Continuer avec Facebook"
|
|
}
|
|
}
|
|
</i18n>
|