279 lines
7.6 KiB
Vue
279 lines
7.6 KiB
Vue
|
|
<template>
|
||
|
|
<div class="verification-expired">
|
||
|
|
<v-container class="fill-height" fluid>
|
||
|
|
<v-row align="center" justify="center">
|
||
|
|
<v-col cols="12" sm="8" md="6" lg="4">
|
||
|
|
<v-card class="elevation-12 rounded-lg">
|
||
|
|
<v-card-text class="text-center pa-8">
|
||
|
|
<div class="mb-6">
|
||
|
|
<v-icon
|
||
|
|
color="warning"
|
||
|
|
size="80"
|
||
|
|
class="mb-4"
|
||
|
|
>
|
||
|
|
mdi-clock-alert
|
||
|
|
</v-icon>
|
||
|
|
|
||
|
|
<h1 class="text-h4 font-weight-bold text-warning mb-3">
|
||
|
|
{{ pageTitle }}
|
||
|
|
</h1>
|
||
|
|
|
||
|
|
<p class="text-body-1 text-medium-emphasis mb-6">
|
||
|
|
{{ pageDescription }}
|
||
|
|
</p>
|
||
|
|
|
||
|
|
<!-- Information alert -->
|
||
|
|
<v-alert
|
||
|
|
type="info"
|
||
|
|
variant="tonal"
|
||
|
|
class="mb-6 text-start"
|
||
|
|
icon="mdi-information"
|
||
|
|
>
|
||
|
|
<div class="text-body-2">
|
||
|
|
<strong>What to do next:</strong>
|
||
|
|
<ul class="mt-2 pl-4">
|
||
|
|
<li>Request a new verification email below</li>
|
||
|
|
<li>Check your spam/junk folder for emails from MonacoUSA</li>
|
||
|
|
<li>Make sure you're checking the correct email address</li>
|
||
|
|
</ul>
|
||
|
|
</div>
|
||
|
|
</v-alert>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Resend verification form -->
|
||
|
|
<v-form @submit.prevent="resendVerification" :disabled="loading">
|
||
|
|
<div class="mb-4">
|
||
|
|
<v-text-field
|
||
|
|
v-model="email"
|
||
|
|
label="Email Address"
|
||
|
|
type="email"
|
||
|
|
variant="outlined"
|
||
|
|
prepend-inner-icon="mdi-email"
|
||
|
|
:rules="emailRules"
|
||
|
|
:error-messages="emailError"
|
||
|
|
required
|
||
|
|
class="mb-3"
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="d-flex flex-column gap-3 mb-6">
|
||
|
|
<v-btn
|
||
|
|
type="submit"
|
||
|
|
color="primary"
|
||
|
|
size="large"
|
||
|
|
variant="elevated"
|
||
|
|
block
|
||
|
|
:loading="loading"
|
||
|
|
:disabled="!email || !isValidEmail(email)"
|
||
|
|
class="text-none"
|
||
|
|
>
|
||
|
|
<v-icon start>mdi-email-send</v-icon>
|
||
|
|
{{ loading ? 'Sending...' : 'Send New Verification Email' }}
|
||
|
|
</v-btn>
|
||
|
|
|
||
|
|
<v-btn
|
||
|
|
color="secondary"
|
||
|
|
size="large"
|
||
|
|
variant="outlined"
|
||
|
|
block
|
||
|
|
to="/login"
|
||
|
|
class="text-none"
|
||
|
|
>
|
||
|
|
<v-icon start>mdi-login</v-icon>
|
||
|
|
Back to Login
|
||
|
|
</v-btn>
|
||
|
|
</div>
|
||
|
|
</v-form>
|
||
|
|
|
||
|
|
<!-- Success message -->
|
||
|
|
<v-alert
|
||
|
|
v-if="successMessage"
|
||
|
|
type="success"
|
||
|
|
variant="tonal"
|
||
|
|
class="mb-4"
|
||
|
|
icon="mdi-check"
|
||
|
|
>
|
||
|
|
{{ successMessage }}
|
||
|
|
</v-alert>
|
||
|
|
|
||
|
|
<!-- Error message -->
|
||
|
|
<v-alert
|
||
|
|
v-if="errorMessage"
|
||
|
|
type="error"
|
||
|
|
variant="tonal"
|
||
|
|
class="mb-4"
|
||
|
|
icon="mdi-alert"
|
||
|
|
>
|
||
|
|
{{ errorMessage }}
|
||
|
|
</v-alert>
|
||
|
|
|
||
|
|
<!-- Additional help -->
|
||
|
|
<div class="mt-6 pt-4 border-t">
|
||
|
|
<p class="text-caption text-medium-emphasis mb-2">
|
||
|
|
Still having trouble? Contact support:
|
||
|
|
</p>
|
||
|
|
<v-chip
|
||
|
|
size="small"
|
||
|
|
variant="outlined"
|
||
|
|
prepend-icon="mdi-email"
|
||
|
|
>
|
||
|
|
support@monacousa.org
|
||
|
|
</v-chip>
|
||
|
|
</div>
|
||
|
|
</v-card-text>
|
||
|
|
</v-card>
|
||
|
|
</v-col>
|
||
|
|
</v-row>
|
||
|
|
</v-container>
|
||
|
|
</div>
|
||
|
|
</template>
|
||
|
|
|
||
|
|
<script setup lang="ts">
|
||
|
|
definePageMeta({
|
||
|
|
layout: false,
|
||
|
|
middleware: 'guest'
|
||
|
|
});
|
||
|
|
|
||
|
|
// Get query parameters
|
||
|
|
const route = useRoute();
|
||
|
|
const reason = computed(() => route.query.reason as string || 'expired');
|
||
|
|
|
||
|
|
// Reactive data
|
||
|
|
const email = ref('');
|
||
|
|
const loading = ref(false);
|
||
|
|
const successMessage = ref('');
|
||
|
|
const errorMessage = ref('');
|
||
|
|
const emailError = ref('');
|
||
|
|
|
||
|
|
// Computed properties
|
||
|
|
const pageTitle = computed(() => {
|
||
|
|
switch (reason.value) {
|
||
|
|
case 'used':
|
||
|
|
return 'Verification Link Already Used';
|
||
|
|
case 'invalid':
|
||
|
|
return 'Invalid Verification Link';
|
||
|
|
default:
|
||
|
|
return 'Verification Link Expired';
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
const pageDescription = computed(() => {
|
||
|
|
switch (reason.value) {
|
||
|
|
case 'used':
|
||
|
|
return 'This verification link has already been used. If you need to verify your email again, please request a new verification link below.';
|
||
|
|
case 'invalid':
|
||
|
|
return 'The verification link you clicked is invalid or malformed. Please request a new verification link below.';
|
||
|
|
default:
|
||
|
|
return 'Your verification link has expired. Verification links are valid for 24 hours. Please request a new verification link below.';
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// Validation rules
|
||
|
|
const emailRules = [
|
||
|
|
(v: string) => !!v || 'Email is required',
|
||
|
|
(v: string) => isValidEmail(v) || 'Please enter a valid email address'
|
||
|
|
];
|
||
|
|
|
||
|
|
// Helper function
|
||
|
|
function isValidEmail(email: string): boolean {
|
||
|
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||
|
|
return emailRegex.test(email);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Resend verification email
|
||
|
|
async function resendVerification() {
|
||
|
|
if (!email.value || !isValidEmail(email.value)) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
loading.value = true;
|
||
|
|
successMessage.value = '';
|
||
|
|
errorMessage.value = '';
|
||
|
|
emailError.value = '';
|
||
|
|
|
||
|
|
try {
|
||
|
|
const response = await $fetch('/api/auth/send-verification-email', {
|
||
|
|
method: 'POST',
|
||
|
|
body: { email: email.value }
|
||
|
|
});
|
||
|
|
|
||
|
|
successMessage.value = 'A new verification email has been sent! Please check your inbox and spam folder.';
|
||
|
|
email.value = ''; // Clear the form
|
||
|
|
|
||
|
|
} catch (error: any) {
|
||
|
|
console.error('[verify-expired] Failed to resend verification:', error);
|
||
|
|
|
||
|
|
if (error.status === 404) {
|
||
|
|
emailError.value = 'No account found with this email address.';
|
||
|
|
} else if (error.status === 429) {
|
||
|
|
errorMessage.value = 'Please wait a few minutes before requesting another verification email.';
|
||
|
|
} else {
|
||
|
|
errorMessage.value = error.data?.message || 'Failed to send verification email. Please try again.';
|
||
|
|
}
|
||
|
|
} finally {
|
||
|
|
loading.value = false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Set page title
|
||
|
|
useHead({
|
||
|
|
title: `${pageTitle.value} - MonacoUSA Portal`,
|
||
|
|
meta: [
|
||
|
|
{
|
||
|
|
name: 'description',
|
||
|
|
content: 'Request a new email verification link for your MonacoUSA Portal account.'
|
||
|
|
}
|
||
|
|
]
|
||
|
|
});
|
||
|
|
|
||
|
|
// Track page view
|
||
|
|
onMounted(() => {
|
||
|
|
console.log('[verify-expired] Page accessed', { reason: reason.value });
|
||
|
|
});
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<style scoped>
|
||
|
|
.verification-expired {
|
||
|
|
min-height: 100vh;
|
||
|
|
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
||
|
|
}
|
||
|
|
|
||
|
|
.fill-height {
|
||
|
|
min-height: 100vh;
|
||
|
|
}
|
||
|
|
|
||
|
|
.border-t {
|
||
|
|
border-top: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
|
||
|
|
}
|
||
|
|
|
||
|
|
.gap-3 {
|
||
|
|
gap: 12px;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Animation for the warning icon */
|
||
|
|
.v-icon {
|
||
|
|
animation: pulse 2s ease-in-out infinite;
|
||
|
|
}
|
||
|
|
|
||
|
|
@keyframes pulse {
|
||
|
|
0% {
|
||
|
|
transform: scale(1);
|
||
|
|
}
|
||
|
|
50% {
|
||
|
|
transform: scale(1.05);
|
||
|
|
}
|
||
|
|
100% {
|
||
|
|
transform: scale(1);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* List styling */
|
||
|
|
ul {
|
||
|
|
list-style-type: disc;
|
||
|
|
}
|
||
|
|
|
||
|
|
li {
|
||
|
|
margin-bottom: 4px;
|
||
|
|
}
|
||
|
|
</style>
|