2025-08-08 19:40:13 +02:00
|
|
|
<template>
|
2025-08-08 19:55:32 +02:00
|
|
|
<div class="signup-container">
|
|
|
|
|
<v-container fluid class="fill-height">
|
|
|
|
|
<v-row class="fill-height" justify="center" align="center">
|
|
|
|
|
<v-col cols="12" sm="10" md="8" lg="6" xl="5">
|
|
|
|
|
<v-card class="signup-card" elevation="24" :loading="loading">
|
|
|
|
|
<v-card-text class="pa-8">
|
|
|
|
|
<!-- Logo and Welcome -->
|
|
|
|
|
<div class="text-center mb-6">
|
|
|
|
|
<v-img
|
|
|
|
|
src="/MONACOUSA-Flags_376x376.png"
|
|
|
|
|
width="80"
|
|
|
|
|
height="80"
|
|
|
|
|
class="mx-auto mb-3"
|
|
|
|
|
alt="MonacoUSA Logo"
|
|
|
|
|
/>
|
|
|
|
|
<h1 class="text-h4 font-weight-bold mb-2" style="color: #a31515;">
|
|
|
|
|
Join MonacoUSA
|
|
|
|
|
</h1>
|
|
|
|
|
<p class="text-body-1 text-medium-emphasis">
|
|
|
|
|
Register as a member of MonacoUSA Association
|
|
|
|
|
</p>
|
2025-08-08 19:40:13 +02:00
|
|
|
</div>
|
|
|
|
|
|
2025-08-08 19:55:32 +02:00
|
|
|
<!-- Registration Form -->
|
2025-08-08 19:40:13 +02:00
|
|
|
<v-form ref="form" v-model="valid" @submit.prevent="submitRegistration">
|
|
|
|
|
<v-row>
|
|
|
|
|
<v-col cols="12" sm="6">
|
|
|
|
|
<v-text-field
|
2025-08-08 20:59:06 +02:00
|
|
|
v-model="firstName"
|
|
|
|
|
:key="'first-name-field'"
|
2025-08-08 20:27:54 +02:00
|
|
|
name="firstName"
|
|
|
|
|
autocomplete="given-name"
|
2025-08-08 19:40:13 +02:00
|
|
|
label="First Name"
|
|
|
|
|
:rules="nameRules"
|
2025-08-08 19:55:32 +02:00
|
|
|
prepend-inner-icon="mdi-account"
|
2025-08-08 19:40:13 +02:00
|
|
|
variant="outlined"
|
2025-08-08 19:55:32 +02:00
|
|
|
:disabled="loading"
|
2025-08-08 19:40:13 +02:00
|
|
|
required
|
|
|
|
|
/>
|
|
|
|
|
</v-col>
|
|
|
|
|
<v-col cols="12" sm="6">
|
|
|
|
|
<v-text-field
|
2025-08-08 20:59:06 +02:00
|
|
|
v-model="lastName"
|
|
|
|
|
:key="'last-name-field'"
|
2025-08-08 20:27:54 +02:00
|
|
|
name="lastName"
|
|
|
|
|
autocomplete="family-name"
|
2025-08-08 19:40:13 +02:00
|
|
|
label="Last Name"
|
|
|
|
|
:rules="nameRules"
|
|
|
|
|
variant="outlined"
|
2025-08-08 19:55:32 +02:00
|
|
|
:disabled="loading"
|
2025-08-08 19:40:13 +02:00
|
|
|
required
|
|
|
|
|
/>
|
|
|
|
|
</v-col>
|
|
|
|
|
</v-row>
|
|
|
|
|
|
|
|
|
|
<v-text-field
|
2025-08-08 20:59:06 +02:00
|
|
|
v-model="email"
|
|
|
|
|
:key="'email-field'"
|
2025-08-08 20:27:54 +02:00
|
|
|
name="email"
|
|
|
|
|
autocomplete="email"
|
2025-08-08 19:40:13 +02:00
|
|
|
label="Email Address"
|
|
|
|
|
type="email"
|
|
|
|
|
:rules="emailRules"
|
2025-08-08 19:55:32 +02:00
|
|
|
prepend-inner-icon="mdi-email"
|
2025-08-08 19:40:13 +02:00
|
|
|
variant="outlined"
|
2025-08-08 19:55:32 +02:00
|
|
|
:disabled="loading"
|
2025-08-08 19:40:13 +02:00
|
|
|
required
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<PhoneInputWrapper
|
2025-08-08 20:59:06 +02:00
|
|
|
v-model="phone"
|
|
|
|
|
:key="'phone-field'"
|
2025-08-08 19:40:13 +02:00
|
|
|
label="Phone Number"
|
|
|
|
|
:rules="phoneRules"
|
2025-08-08 19:55:32 +02:00
|
|
|
:disabled="loading"
|
2025-08-08 19:40:13 +02:00
|
|
|
required
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<v-text-field
|
2025-08-08 20:59:06 +02:00
|
|
|
v-model="dateOfBirth"
|
|
|
|
|
:key="'dob-field'"
|
2025-08-08 19:40:13 +02:00
|
|
|
label="Date of Birth"
|
|
|
|
|
type="date"
|
|
|
|
|
:rules="dobRules"
|
2025-08-08 19:55:32 +02:00
|
|
|
prepend-inner-icon="mdi-calendar"
|
2025-08-08 19:40:13 +02:00
|
|
|
variant="outlined"
|
2025-08-08 19:55:32 +02:00
|
|
|
:disabled="loading"
|
2025-08-08 19:40:13 +02:00
|
|
|
required
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<v-textarea
|
2025-08-08 20:59:06 +02:00
|
|
|
v-model="address"
|
|
|
|
|
:key="'address-field'"
|
2025-08-08 20:27:54 +02:00
|
|
|
name="address"
|
|
|
|
|
autocomplete="street-address"
|
2025-08-08 19:40:13 +02:00
|
|
|
label="Address"
|
|
|
|
|
:rules="addressRules"
|
2025-08-08 19:55:32 +02:00
|
|
|
prepend-inner-icon="mdi-map-marker"
|
2025-08-08 19:40:13 +02:00
|
|
|
variant="outlined"
|
|
|
|
|
rows="3"
|
2025-08-08 19:55:32 +02:00
|
|
|
:disabled="loading"
|
2025-08-08 19:40:13 +02:00
|
|
|
required
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<MultipleNationalityInput
|
2025-08-08 20:59:06 +02:00
|
|
|
v-model="nationality"
|
|
|
|
|
:key="'nationality-field'"
|
2025-08-08 19:40:13 +02:00
|
|
|
label="Nationality"
|
|
|
|
|
:rules="nationalityRules"
|
2025-08-08 19:55:32 +02:00
|
|
|
:disabled="loading"
|
2025-08-08 19:40:13 +02:00
|
|
|
required
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
|
2025-08-08 19:55:32 +02:00
|
|
|
<!-- Error Alert -->
|
|
|
|
|
<v-alert
|
|
|
|
|
v-if="errorMessage"
|
|
|
|
|
type="error"
|
|
|
|
|
variant="tonal"
|
|
|
|
|
class="mb-4"
|
|
|
|
|
closable
|
|
|
|
|
@click:close="errorMessage = ''"
|
|
|
|
|
>
|
|
|
|
|
<v-alert-title>Registration Failed</v-alert-title>
|
|
|
|
|
{{ errorMessage }}
|
|
|
|
|
</v-alert>
|
|
|
|
|
|
|
|
|
|
|
2025-08-08 19:40:13 +02:00
|
|
|
<v-btn
|
|
|
|
|
type="submit"
|
|
|
|
|
color="primary"
|
|
|
|
|
size="large"
|
|
|
|
|
block
|
2025-08-08 22:04:53 +02:00
|
|
|
:disabled="!valid || loading"
|
2025-08-08 19:40:13 +02:00
|
|
|
:loading="loading"
|
2025-08-08 19:55:32 +02:00
|
|
|
class="mb-4"
|
|
|
|
|
style="background-color: #a31515 !important; color: white !important;"
|
2025-08-08 19:40:13 +02:00
|
|
|
>
|
|
|
|
|
<v-icon left>mdi-account-plus</v-icon>
|
|
|
|
|
Register
|
|
|
|
|
</v-btn>
|
|
|
|
|
</v-form>
|
|
|
|
|
|
2025-08-08 19:55:32 +02:00
|
|
|
<!-- Payment Information -->
|
|
|
|
|
<v-divider class="my-6" />
|
|
|
|
|
<div class="payment-info">
|
|
|
|
|
<div class="d-flex align-center mb-4">
|
|
|
|
|
<v-icon color="#a31515" class="mr-2">mdi-bank</v-icon>
|
|
|
|
|
<h3 class="text-h6" style="color: #a31515;">Membership Dues Payment</h3>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="payment-details" style="color: #000 !important;">
|
|
|
|
|
<p class="text-body-1 mb-3" style="color: #000 !important;">
|
2025-08-08 19:40:13 +02:00
|
|
|
After registration, please transfer your annual membership dues:
|
2025-08-08 19:55:32 +02:00
|
|
|
</p>
|
2025-08-08 19:40:13 +02:00
|
|
|
|
2025-08-08 19:55:32 +02:00
|
|
|
<v-row dense class="mb-2">
|
|
|
|
|
<v-col cols="4">
|
|
|
|
|
<span class="text-body-2 font-weight-bold" style="color: #000 !important;">Amount:</span>
|
|
|
|
|
</v-col>
|
|
|
|
|
<v-col cols="8">
|
|
|
|
|
<span class="text-body-2" style="color: #000 !important;">€{{ registrationConfig.membershipFee }}/year</span>
|
|
|
|
|
</v-col>
|
2025-08-08 19:40:13 +02:00
|
|
|
</v-row>
|
|
|
|
|
|
2025-08-08 19:55:32 +02:00
|
|
|
<v-row dense class="mb-2" v-if="registrationConfig.iban">
|
|
|
|
|
<v-col cols="4">
|
|
|
|
|
<span class="text-body-2 font-weight-bold" style="color: #000 !important;">IBAN:</span>
|
|
|
|
|
</v-col>
|
|
|
|
|
<v-col cols="8">
|
|
|
|
|
<span class="text-body-2 font-family-monospace" style="color: #000 !important;">{{ registrationConfig.iban }}</span>
|
|
|
|
|
</v-col>
|
2025-08-08 19:40:13 +02:00
|
|
|
</v-row>
|
|
|
|
|
|
2025-08-08 19:55:32 +02:00
|
|
|
<v-row dense class="mb-2" v-if="registrationConfig.accountHolder">
|
|
|
|
|
<v-col cols="4">
|
|
|
|
|
<span class="text-body-2 font-weight-bold" style="color: #000 !important;">Account:</span>
|
|
|
|
|
</v-col>
|
|
|
|
|
<v-col cols="8">
|
|
|
|
|
<span class="text-body-2" style="color: #000 !important;">{{ registrationConfig.accountHolder }}</span>
|
|
|
|
|
</v-col>
|
2025-08-08 19:40:13 +02:00
|
|
|
</v-row>
|
|
|
|
|
|
|
|
|
|
<v-divider class="my-3" />
|
|
|
|
|
|
2025-08-08 19:55:32 +02:00
|
|
|
<div class="text-body-2 d-flex align-center" style="color: #666 !important;">
|
|
|
|
|
<v-icon size="small" class="mr-2" color="#666">mdi-information</v-icon>
|
2025-08-08 19:40:13 +02:00
|
|
|
Your account will be activated once payment is verified by our administrators.
|
|
|
|
|
</div>
|
2025-08-08 19:55:32 +02:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2025-08-08 19:40:13 +02:00
|
|
|
</v-card-text>
|
|
|
|
|
</v-card>
|
|
|
|
|
</v-col>
|
|
|
|
|
</v-row>
|
|
|
|
|
</v-container>
|
2025-08-08 21:52:21 +02:00
|
|
|
|
|
|
|
|
<!-- Registration Success Dialog -->
|
|
|
|
|
<RegistrationSuccessDialog
|
|
|
|
|
v-model="showSuccessDialog"
|
|
|
|
|
:member-data="registrationResult"
|
|
|
|
|
:payment-info="registrationConfig"
|
|
|
|
|
@go-to-login="goToLogin"
|
|
|
|
|
/>
|
2025-08-08 19:40:13 +02:00
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
import type { RegistrationFormData, RecaptchaConfig, RegistrationConfig } from '~/utils/types';
|
|
|
|
|
|
2025-08-08 22:04:53 +02:00
|
|
|
// Declare global grecaptcha interface for TypeScript
|
|
|
|
|
declare global {
|
|
|
|
|
interface Window {
|
|
|
|
|
grecaptcha: {
|
|
|
|
|
ready: (callback: () => void) => void;
|
|
|
|
|
execute: (siteKey: string, options: { action: string }) => Promise<string>;
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-08 19:40:13 +02:00
|
|
|
// Page metadata
|
|
|
|
|
definePageMeta({
|
2025-08-08 21:52:21 +02:00
|
|
|
layout: false
|
2025-08-08 19:40:13 +02:00
|
|
|
});
|
|
|
|
|
|
2025-08-08 22:04:53 +02:00
|
|
|
// Configs - need to be declared first
|
|
|
|
|
const recaptchaConfig = ref<RecaptchaConfig>({ siteKey: '', secretKey: '' });
|
|
|
|
|
const registrationConfig = ref<RegistrationConfig>({
|
|
|
|
|
membershipFee: 50,
|
|
|
|
|
iban: '',
|
|
|
|
|
accountHolder: ''
|
2025-08-08 19:40:13 +02:00
|
|
|
});
|
|
|
|
|
|
2025-08-08 20:59:06 +02:00
|
|
|
// Reactive data - Using individual refs to prevent Vue reactivity corruption
|
|
|
|
|
const firstName = ref('');
|
|
|
|
|
const lastName = ref('');
|
|
|
|
|
const email = ref('');
|
|
|
|
|
const phone = ref('');
|
|
|
|
|
const dateOfBirth = ref('');
|
|
|
|
|
const address = ref('');
|
|
|
|
|
const nationality = ref('');
|
|
|
|
|
|
|
|
|
|
// Computed property to create form object for submission
|
|
|
|
|
const form = computed(() => ({
|
|
|
|
|
first_name: firstName.value,
|
|
|
|
|
last_name: lastName.value,
|
|
|
|
|
email: email.value,
|
|
|
|
|
phone: phone.value,
|
|
|
|
|
date_of_birth: dateOfBirth.value,
|
|
|
|
|
address: address.value,
|
|
|
|
|
nationality: nationality.value
|
|
|
|
|
}));
|
2025-08-08 19:40:13 +02:00
|
|
|
|
|
|
|
|
const valid = ref(false);
|
|
|
|
|
const loading = ref(false);
|
|
|
|
|
const recaptchaToken = ref('');
|
|
|
|
|
const successMessage = ref('');
|
|
|
|
|
const errorMessage = ref('');
|
|
|
|
|
|
2025-08-08 21:52:21 +02:00
|
|
|
// Success dialog state
|
|
|
|
|
const showSuccessDialog = ref(false);
|
|
|
|
|
const registrationResult = ref<{ memberId: string; email: string } | null>(null);
|
|
|
|
|
|
2025-08-08 22:04:53 +02:00
|
|
|
// Head configuration
|
|
|
|
|
useHead({
|
|
|
|
|
title: 'Register - MonacoUSA Portal',
|
|
|
|
|
meta: [
|
|
|
|
|
{ name: 'description', content: 'Register to become a member of MonacoUSA Association' },
|
|
|
|
|
{ name: 'robots', content: 'noindex, nofollow' }
|
|
|
|
|
]
|
2025-08-08 19:40:13 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Form validation rules
|
|
|
|
|
const nameRules = [
|
|
|
|
|
(v: string) => !!v || 'Name is required',
|
|
|
|
|
(v: string) => v.length >= 2 || 'Name must be at least 2 characters'
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const emailRules = [
|
|
|
|
|
(v: string) => !!v || 'Email is required',
|
|
|
|
|
(v: string) => /.+@.+\..+/.test(v) || 'Email must be valid'
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const phoneRules = [
|
|
|
|
|
(v: string) => !!v || 'Phone number is required'
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const dobRules = [
|
|
|
|
|
(v: string) => !!v || 'Date of birth is required',
|
|
|
|
|
(v: string) => {
|
|
|
|
|
if (!v) return true;
|
|
|
|
|
const birthDate = new Date(v);
|
|
|
|
|
const today = new Date();
|
|
|
|
|
const age = today.getFullYear() - birthDate.getFullYear();
|
|
|
|
|
const monthDiff = today.getMonth() - birthDate.getMonth();
|
|
|
|
|
|
|
|
|
|
let actualAge = age;
|
|
|
|
|
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) {
|
|
|
|
|
actualAge = age - 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return actualAge >= 18 || 'You must be at least 18 years old';
|
|
|
|
|
}
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const addressRules = [
|
|
|
|
|
(v: string) => !!v || 'Address is required',
|
|
|
|
|
(v: string) => v.length >= 10 || 'Please provide a complete address'
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const nationalityRules = [
|
|
|
|
|
(v: string) => !!v || 'Nationality is required'
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
// reCAPTCHA handling
|
|
|
|
|
function onRecaptchaVerified(token: string) {
|
|
|
|
|
recaptchaToken.value = token;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function onRecaptchaExpired() {
|
|
|
|
|
recaptchaToken.value = '';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Template refs
|
|
|
|
|
const recaptcha = ref<any>(null);
|
|
|
|
|
|
2025-08-08 20:49:50 +02:00
|
|
|
|
2025-08-08 20:27:54 +02:00
|
|
|
|
2025-08-08 22:04:53 +02:00
|
|
|
// reCAPTCHA v3 token generation
|
|
|
|
|
async function generateRecaptchaToken(): Promise<string> {
|
|
|
|
|
if (!recaptchaConfig.value.siteKey || typeof window === 'undefined') {
|
|
|
|
|
return '';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return new Promise((resolve) => {
|
|
|
|
|
if (window.grecaptcha && window.grecaptcha.ready) {
|
|
|
|
|
window.grecaptcha.ready(() => {
|
|
|
|
|
window.grecaptcha.execute(recaptchaConfig.value.siteKey, { action: 'registration' }).then((token: string) => {
|
|
|
|
|
resolve(token);
|
|
|
|
|
}).catch(() => {
|
|
|
|
|
resolve('');
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
resolve('');
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-08 19:40:13 +02:00
|
|
|
// Form submission
|
|
|
|
|
async function submitRegistration() {
|
2025-08-08 22:04:53 +02:00
|
|
|
if (!valid.value) {
|
2025-08-08 19:40:13 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
loading.value = true;
|
|
|
|
|
errorMessage.value = '';
|
|
|
|
|
successMessage.value = '';
|
|
|
|
|
|
|
|
|
|
try {
|
2025-08-08 22:04:53 +02:00
|
|
|
// Generate reCAPTCHA v3 token if configured
|
|
|
|
|
let token = '';
|
|
|
|
|
if (recaptchaConfig.value.siteKey) {
|
|
|
|
|
token = await generateRecaptchaToken();
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-08 19:40:13 +02:00
|
|
|
const registrationData: RegistrationFormData = {
|
|
|
|
|
...form.value,
|
2025-08-08 22:04:53 +02:00
|
|
|
recaptcha_token: token
|
2025-08-08 19:40:13 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const response = await $fetch('/api/registration', {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
body: registrationData
|
|
|
|
|
}) as any;
|
|
|
|
|
|
|
|
|
|
if (response?.success) {
|
2025-08-08 21:52:21 +02:00
|
|
|
// Set registration result data for dialog
|
|
|
|
|
registrationResult.value = {
|
|
|
|
|
memberId: response.data?.memberId || 'N/A',
|
|
|
|
|
email: form.value.email
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Show success dialog instead of just alert
|
|
|
|
|
showSuccessDialog.value = true;
|
|
|
|
|
|
2025-08-08 20:59:06 +02:00
|
|
|
// Reset form by resetting individual refs
|
|
|
|
|
firstName.value = '';
|
|
|
|
|
lastName.value = '';
|
|
|
|
|
email.value = '';
|
|
|
|
|
phone.value = '';
|
|
|
|
|
dateOfBirth.value = '';
|
|
|
|
|
address.value = '';
|
|
|
|
|
nationality.value = '';
|
2025-08-08 19:40:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
console.error('Registration failed:', error);
|
|
|
|
|
errorMessage.value = error.data?.message || error.message || 'Registration failed. Please try again.';
|
|
|
|
|
} finally {
|
|
|
|
|
loading.value = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-08 21:52:21 +02:00
|
|
|
// Navigation methods
|
|
|
|
|
const goToLogin = () => {
|
|
|
|
|
navigateTo('/login');
|
|
|
|
|
};
|
|
|
|
|
|
2025-08-08 22:04:53 +02:00
|
|
|
// Load reCAPTCHA script dynamically
|
|
|
|
|
function loadRecaptchaScript(siteKey: string) {
|
|
|
|
|
if (typeof window === 'undefined' || document.querySelector(`script[src*="recaptcha/api.js"]`)) {
|
|
|
|
|
return; // Already loaded or not in browser
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const script = document.createElement('script');
|
|
|
|
|
script.src = `https://www.google.com/recaptcha/api.js?render=${siteKey}`;
|
|
|
|
|
script.async = true;
|
|
|
|
|
script.defer = true;
|
|
|
|
|
document.head.appendChild(script);
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-08 22:51:14 +02:00
|
|
|
// Add page initialization state
|
|
|
|
|
const pageReady = ref(false);
|
|
|
|
|
|
2025-08-08 19:40:13 +02:00
|
|
|
// Load configurations on mount
|
|
|
|
|
onMounted(async () => {
|
2025-08-08 22:51:14 +02:00
|
|
|
console.log('🚀 Initializing signup page...');
|
|
|
|
|
|
|
|
|
|
// Set a timeout to ensure page shows even if API calls fail
|
|
|
|
|
const initTimeout = setTimeout(() => {
|
|
|
|
|
if (!pageReady.value) {
|
|
|
|
|
console.warn('⚠️ API calls taking too long, showing page with defaults');
|
|
|
|
|
pageReady.value = true;
|
|
|
|
|
}
|
|
|
|
|
}, 3000);
|
|
|
|
|
|
2025-08-08 19:40:13 +02:00
|
|
|
try {
|
2025-08-08 22:51:14 +02:00
|
|
|
// Load reCAPTCHA config with timeout
|
|
|
|
|
try {
|
|
|
|
|
const recaptchaResponse = await Promise.race([
|
|
|
|
|
$fetch('/api/recaptcha-config'),
|
|
|
|
|
new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), 2000))
|
|
|
|
|
]) as any;
|
2025-08-08 22:04:53 +02:00
|
|
|
|
2025-08-08 22:51:14 +02:00
|
|
|
if (recaptchaResponse?.success && recaptchaResponse?.data?.siteKey) {
|
|
|
|
|
recaptchaConfig.value.siteKey = recaptchaResponse.data.siteKey;
|
|
|
|
|
loadRecaptchaScript(recaptchaConfig.value.siteKey);
|
|
|
|
|
console.log('✅ reCAPTCHA site key loaded successfully');
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.warn('❌ reCAPTCHA config failed to load:', error);
|
2025-08-08 19:40:13 +02:00
|
|
|
}
|
|
|
|
|
|
2025-08-08 22:51:14 +02:00
|
|
|
// Load registration config with timeout
|
|
|
|
|
try {
|
|
|
|
|
const registrationResponse = await Promise.race([
|
|
|
|
|
$fetch('/api/registration-config'),
|
|
|
|
|
new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), 2000))
|
|
|
|
|
]) as any;
|
|
|
|
|
|
|
|
|
|
if (registrationResponse?.success) {
|
|
|
|
|
registrationConfig.value = registrationResponse.data;
|
|
|
|
|
console.log('✅ Registration config loaded successfully');
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.warn('❌ Registration config failed to load:', error);
|
2025-08-08 19:40:13 +02:00
|
|
|
}
|
2025-08-08 22:51:14 +02:00
|
|
|
|
2025-08-08 19:40:13 +02:00
|
|
|
} catch (error) {
|
2025-08-08 21:10:00 +02:00
|
|
|
console.error('Failed to load configuration:', error);
|
2025-08-08 22:51:14 +02:00
|
|
|
} finally {
|
|
|
|
|
clearTimeout(initTimeout);
|
|
|
|
|
pageReady.value = true;
|
|
|
|
|
console.log('✅ Signup page ready');
|
2025-08-08 19:40:13 +02:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
2025-08-08 19:55:32 +02:00
|
|
|
.signup-container {
|
2025-08-08 19:40:13 +02:00
|
|
|
min-height: 100vh;
|
2025-08-08 22:51:14 +02:00
|
|
|
min-height: 100dvh; /* Dynamic viewport height for mobile */
|
2025-08-08 19:55:32 +02:00
|
|
|
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;
|
2025-08-08 22:51:14 +02:00
|
|
|
background-repeat: no-repeat;
|
|
|
|
|
background-attachment: scroll;
|
|
|
|
|
padding: 20px 0;
|
|
|
|
|
position: relative;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Ensure background covers full content */
|
|
|
|
|
.signup-container::before {
|
|
|
|
|
content: '';
|
|
|
|
|
position: absolute;
|
2025-08-08 20:41:39 +02:00
|
|
|
top: 0;
|
|
|
|
|
left: 0;
|
|
|
|
|
right: 0;
|
|
|
|
|
bottom: 0;
|
2025-08-08 22:51:14 +02:00
|
|
|
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-repeat: no-repeat;
|
|
|
|
|
background-attachment: scroll;
|
|
|
|
|
z-index: -1;
|
|
|
|
|
min-height: 100%;
|
2025-08-08 19:55:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.signup-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: 600px;
|
|
|
|
|
width: 100%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.signup-card:hover {
|
|
|
|
|
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.4) !important;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.payment-info {
|
|
|
|
|
background: rgba(163, 21, 21, 0.05);
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
padding: 20px;
|
|
|
|
|
border: 1px solid rgba(163, 21, 21, 0.1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.payment-details * {
|
|
|
|
|
color: #000 !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) {
|
|
|
|
|
.signup-container {
|
|
|
|
|
background-attachment: scroll;
|
|
|
|
|
padding: 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.signup-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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Ensure PhoneInputWrapper and MultipleNationalityInput align properly */
|
|
|
|
|
:deep(.v-field) {
|
|
|
|
|
width: 100%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.v-input) {
|
|
|
|
|
width: 100%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.v-text-field) {
|
|
|
|
|
width: 100%;
|
2025-08-08 19:40:13 +02:00
|
|
|
}
|
|
|
|
|
</style>
|