monacousa-portal/pages/login.vue

282 lines
7.9 KiB
Vue

<template>
<div class="login-container">
<v-container fluid class="fill-height">
<v-row class="fill-height" justify="center" align="center">
<v-col cols="12" sm="8" md="6" lg="4" xl="3">
<v-card class="login-card" elevation="24">
<v-card-text class="pa-8">
<!-- Logo and Welcome -->
<div class="text-center mb-8">
<v-img
src="/MONACOUSA-Flags_376x376.png"
width="120"
height="120"
class="mx-auto mb-4"
alt="MonacoUSA Logo"
/>
<h1 class="text-h4 font-weight-bold mb-2" style="color: #a31515;">
Welcome Back
</h1>
<p class="text-body-1 text-medium-emphasis">
Sign in to your MonacoUSA Portal
</p>
</div>
<!-- Login Form -->
<v-form @submit.prevent="handleLogin" ref="loginForm">
<v-text-field
v-model="credentials.username"
label="Username or Email"
prepend-inner-icon="mdi-account"
variant="outlined"
:error-messages="errors.username"
:disabled="loading"
class="mb-3"
required
/>
<v-text-field
v-model="credentials.password"
:type="showPassword ? 'text' : 'password'"
label="Password"
prepend-inner-icon="mdi-lock"
:append-inner-icon="showPassword ? 'mdi-eye' : 'mdi-eye-off'"
@click:append-inner="showPassword = !showPassword"
variant="outlined"
:error-messages="errors.password"
:disabled="loading"
class="mb-3"
required
/>
<div class="d-flex justify-space-between align-center mb-6">
<v-checkbox
v-model="credentials.rememberMe"
label="Remember me"
density="compact"
:disabled="loading"
/>
<v-btn
variant="text"
size="small"
@click="showForgotPassword = true"
:disabled="loading"
style="color: #a31515;"
>
Forgot Password?
</v-btn>
</div>
<!-- Error Alert -->
<v-alert
v-if="loginError"
type="error"
variant="tonal"
class="mb-4"
closable
@click:close="loginError = ''"
>
{{ loginError }}
</v-alert>
<!-- Login Button -->
<v-btn
type="submit"
color="primary"
size="large"
block
:loading="loading"
:disabled="!isFormValid"
class="mb-4"
style="background-color: #a31515 !important; color: white !important;"
>
<v-icon left>mdi-login</v-icon>
Sign In
</v-btn>
</v-form>
</v-card-text>
</v-card>
</v-col>
</v-row>
</v-container>
<!-- Forgot Password Dialog -->
<ForgotPasswordDialog
v-model="showForgotPassword"
@success="handlePasswordResetSuccess"
/>
<!-- PWA Install Banner -->
<PWAInstallBanner />
</div>
</template>
<script setup lang="ts">
definePageMeta({
layout: false,
middleware: 'guest' // This prevents authenticated users from accessing login
});
// Use the auth composable
const { user, login, loading: authLoading, error: authError, checkAuth } = useAuth();
// Reactive data
const credentials = ref({
username: '',
password: '',
rememberMe: false
});
const showPassword = ref(false);
const showForgotPassword = ref(false);
const loginError = ref('');
const errors = ref({
username: '',
password: ''
});
const loginForm = ref();
// Computed
const loading = computed(() => authLoading.value);
const isFormValid = computed(() => {
return credentials.value.username.length > 0 &&
credentials.value.password.length > 0 &&
!loading.value;
});
// Methods
const validateForm = () => {
errors.value = { username: '', password: '' };
let isValid = true;
if (!credentials.value.username || credentials.value.username.length < 2) {
errors.value.username = 'Username must be at least 2 characters';
isValid = false;
}
if (!credentials.value.password || credentials.value.password.length < 6) {
errors.value.password = 'Password must be at least 6 characters';
isValid = false;
}
return isValid;
};
const handleLogin = async () => {
if (!validateForm()) return;
loginError.value = '';
try {
const result = await login({
username: credentials.value.username,
password: credentials.value.password,
rememberMe: credentials.value.rememberMe
});
if (result.success) {
// Handle redirect using standard Nuxt navigation
console.log('🔄 Login successful, redirecting to:', result.redirectTo);
const redirectUrl = result.redirectTo || '/dashboard';
// Use standard navigation
await navigateTo(redirectUrl);
} else {
loginError.value = result.error || 'Login failed. Please check your credentials and try again.';
}
} catch (error: any) {
console.error('Login error:', error);
loginError.value = authError.value || 'Login failed. Please check your credentials and try again.';
}
};
const handlePasswordResetSuccess = (message: string) => {
showForgotPassword.value = false;
// Could show a success message here
console.log('Password reset:', message);
};
// Auto-focus on mount - guest middleware handles auth redirects
onMounted(() => {
// Only auto-focus, no auth check needed - guest middleware handles it
nextTick(() => {
const usernameField = document.querySelector('input[type="text"]') as HTMLInputElement;
if (usernameField) {
usernameField.focus();
}
});
});
</script>
<style scoped>
.login-container {
min-height: 100vh;
background: linear-gradient(rgba(163, 21, 21, 0.7), rgba(0, 0, 0, 0.5)),
url('/monaco_high_res.jpg');
background-size: cover;
background-position: center;
background-attachment: fixed;
display: flex;
align-items: center;
justify-content: center;
}
.login-card {
backdrop-filter: blur(15px);
background: rgba(255, 255, 255, 0.95) !important;
border-radius: 20px !important;
border: 1px solid rgba(255, 255, 255, 0.3);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3) !important;
transition: all 0.3s ease;
max-width: 400px;
width: 100%;
}
.login-card:hover {
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.4) !important;
}
/* Custom scrollbar for mobile */
::-webkit-scrollbar {
width: 4px;
}
::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.1);
}
::-webkit-scrollbar-thumb {
background: rgba(163, 21, 21, 0.5);
border-radius: 2px;
}
/* Responsive adjustments */
@media (max-width: 600px) {
.login-container {
background-attachment: scroll;
padding: 16px;
}
.login-card {
margin: 0;
}
}
/* Loading state styles */
.v-btn--loading {
pointer-events: none;
}
/* Form field focus styles */
.v-field--focused {
border-color: #a31515 !important;
}
.v-field--focused .v-field__outline {
border-color: #a31515 !important;
}
</style>