Files
social-media/frontend/src/features/auth/views/LoginView.vue
Jonathan Bourdon b7379cf823
Some checks failed
Backend CI/CD / build_and_deploy (push) Has been cancelled
Frontend CI/CD / build_and_deploy (push) Has been cancelled
feat: just getting better and better
2026-05-04 21:34:38 -04:00

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>