282 lines
7.9 KiB
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>
|