Fix Safari iOS reload loop with static device detection and caching
All checks were successful
Build And Push Image / docker (push) Successful in 3m7s
All checks were successful
Build And Push Image / docker (push) Successful in 3m7s
- Replace reactive device detection with static utilities to prevent infinite reload loops on mobile Safari - Add static-device-detection.ts for one-time device info computation - Add config-cache.ts for improved configuration loading performance - Apply mobile Safari viewport and CSS optimizations across auth pages - Remove reactive dependencies that caused rendering issues on iOS
This commit is contained in:
@@ -163,8 +163,13 @@ definePageMeta({
|
||||
middleware: 'guest'
|
||||
});
|
||||
|
||||
// Static CSS classes based on device (no reactive dependencies)
|
||||
const containerClasses = ref('password-setup-page');
|
||||
import { getStaticDeviceInfo, getDeviceCssClasses, applyMobileSafariOptimizations, getMobileSafariViewportMeta } from '~/utils/static-device-detection';
|
||||
|
||||
// Static device detection - no reactive dependencies
|
||||
const deviceInfo = getStaticDeviceInfo();
|
||||
|
||||
// Static CSS classes - computed once, never reactive
|
||||
const containerClasses = ref(getDeviceCssClasses('password-setup-page'));
|
||||
|
||||
// Reactive state
|
||||
const loading = ref(false);
|
||||
@@ -238,7 +243,7 @@ useHead({
|
||||
name: 'description',
|
||||
content: 'Set your password to complete your MonacoUSA Portal registration.'
|
||||
},
|
||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no' }
|
||||
{ name: 'viewport', content: getMobileSafariViewportMeta() }
|
||||
]
|
||||
});
|
||||
|
||||
@@ -306,35 +311,20 @@ const setupPassword = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// Component initialization
|
||||
// Component initialization - Safari iOS reload loop prevention
|
||||
onMounted(() => {
|
||||
console.log('[setup-password] Password setup page loaded for:', email.value);
|
||||
|
||||
// Static device detection from Nuxt Device Module - no reactive dependencies
|
||||
const { isMobile, isIos, isSafari } = useDevice();
|
||||
|
||||
// Detect mobile Safari specifically
|
||||
const isMobileSafari = isMobile && isIos && isSafari;
|
||||
|
||||
// Apply classes once (static, no reactivity)
|
||||
const containerClassList = ['password-setup-page'];
|
||||
if (isMobile) containerClassList.push('is-mobile');
|
||||
if (isMobileSafari) containerClassList.push('is-mobile-safari');
|
||||
if (isIos) containerClassList.push('is-ios');
|
||||
containerClasses.value = containerClassList.join(' ');
|
||||
// Apply mobile Safari optimizations early
|
||||
if (deviceInfo.isMobileSafari) {
|
||||
applyMobileSafariOptimizations();
|
||||
console.log('[setup-password] Mobile Safari optimizations applied');
|
||||
}
|
||||
|
||||
// Check if we have required parameters
|
||||
if (!email.value) {
|
||||
errorMessage.value = 'No email address provided. Please use the link from your verification email.';
|
||||
}
|
||||
|
||||
// Prevent auto-zoom on iOS when focusing input fields
|
||||
if (isIos) {
|
||||
const metaViewport = document.querySelector('meta[name="viewport"]');
|
||||
if (metaViewport) {
|
||||
metaViewport.setAttribute('content', 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no');
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -138,8 +138,13 @@ const error = ref('');
|
||||
let verificationStarted = false;
|
||||
let verificationComplete = false;
|
||||
|
||||
import { getStaticDeviceInfo, getDeviceCssClasses, applyMobileSafariOptimizations, getMobileSafariViewportMeta } from '~/utils/static-device-detection';
|
||||
|
||||
// Static device detection - no reactive dependencies
|
||||
const deviceInfo = getStaticDeviceInfo();
|
||||
|
||||
// Static container classes - compute once to prevent re-renders
|
||||
let containerClasses = 'verification-page';
|
||||
let containerClasses = getDeviceCssClasses('verification-page');
|
||||
|
||||
// Set page title with mobile viewport optimization
|
||||
useHead({
|
||||
@@ -149,7 +154,7 @@ useHead({
|
||||
name: 'description',
|
||||
content: 'Verifying your email address for the MonacoUSA Portal.'
|
||||
},
|
||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no' }
|
||||
{ name: 'viewport', content: getMobileSafariViewportMeta() }
|
||||
]
|
||||
});
|
||||
|
||||
@@ -236,24 +241,15 @@ const retryVerification = () => {
|
||||
verifyEmail();
|
||||
};
|
||||
|
||||
// Initialize mobile detection and classes AFTER component is stable
|
||||
// Component initialization - Safari iOS reload loop prevention
|
||||
onMounted(() => {
|
||||
// Only set mobile classes once to prevent re-renders
|
||||
if (typeof window !== 'undefined') {
|
||||
const userAgent = navigator.userAgent;
|
||||
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(userAgent);
|
||||
const isIOS = /iPad|iPhone|iPod/.test(userAgent) && !(window as any).MSStream;
|
||||
const isSafari = /^((?!chrome|android).)*safari/i.test(userAgent);
|
||||
const isMobileSafari = isIOS && isSafari;
|
||||
|
||||
const classes = ['verification-page'];
|
||||
if (isMobile) classes.push('is-mobile');
|
||||
if (isMobileSafari) classes.push('is-mobile-safari');
|
||||
if (isIOS) classes.push('is-ios');
|
||||
containerClasses = classes.join(' ');
|
||||
}
|
||||
|
||||
console.log('[auth/verify] Component mounted with token:', token?.substring(0, 20) + '...');
|
||||
|
||||
// Apply mobile Safari optimizations early
|
||||
if (deviceInfo.isMobileSafari) {
|
||||
applyMobileSafariOptimizations();
|
||||
console.log('[auth/verify] Mobile Safari optimizations applied');
|
||||
}
|
||||
|
||||
// Start verification process with a small delay to ensure stability
|
||||
setTimeout(() => {
|
||||
|
||||
331
pages/signup.vue
331
pages/signup.vue
@@ -28,7 +28,6 @@
|
||||
<v-col cols="12" sm="6">
|
||||
<v-text-field
|
||||
v-model="firstName"
|
||||
:key="'first-name-field'"
|
||||
name="firstName"
|
||||
autocomplete="given-name"
|
||||
label="First Name"
|
||||
@@ -42,7 +41,6 @@
|
||||
<v-col cols="12" sm="6">
|
||||
<v-text-field
|
||||
v-model="lastName"
|
||||
:key="'last-name-field'"
|
||||
name="lastName"
|
||||
autocomplete="family-name"
|
||||
label="Last Name"
|
||||
@@ -56,7 +54,6 @@
|
||||
|
||||
<v-text-field
|
||||
v-model="email"
|
||||
:key="'email-field'"
|
||||
name="email"
|
||||
autocomplete="email"
|
||||
label="Email Address"
|
||||
@@ -70,7 +67,6 @@
|
||||
|
||||
<PhoneInputWrapper
|
||||
v-model="phone"
|
||||
:key="'phone-field'"
|
||||
label="Phone Number"
|
||||
:rules="phoneRules"
|
||||
:disabled="loading"
|
||||
@@ -79,7 +75,6 @@
|
||||
|
||||
<v-text-field
|
||||
v-model="dateOfBirth"
|
||||
:key="'dob-field'"
|
||||
label="Date of Birth"
|
||||
type="date"
|
||||
:rules="dobRules"
|
||||
@@ -91,7 +86,6 @@
|
||||
|
||||
<v-textarea
|
||||
v-model="address"
|
||||
:key="'address-field'"
|
||||
name="address"
|
||||
autocomplete="street-address"
|
||||
label="Address"
|
||||
@@ -105,13 +99,22 @@
|
||||
|
||||
<MultipleNationalityInput
|
||||
v-model="nationality"
|
||||
:key="'nationality-field'"
|
||||
label="Nationality"
|
||||
:rules="nationalityRules"
|
||||
:disabled="loading"
|
||||
required
|
||||
/>
|
||||
|
||||
<!-- reCAPTCHA v2 Checkbox (only if configured) -->
|
||||
<div v-if="!!recaptchaSiteKey" class="mb-4">
|
||||
<div
|
||||
ref="recaptchaContainer"
|
||||
class="g-recaptcha"
|
||||
:data-sitekey="recaptchaSiteKey"
|
||||
data-callback="onRecaptchaVerified"
|
||||
data-expired-callback="onRecaptchaExpired"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<!-- Error Alert -->
|
||||
<v-alert
|
||||
@@ -126,13 +129,12 @@
|
||||
{{ errorMessage }}
|
||||
</v-alert>
|
||||
|
||||
|
||||
<v-btn
|
||||
type="submit"
|
||||
color="primary"
|
||||
size="large"
|
||||
block
|
||||
:disabled="!valid || loading"
|
||||
:disabled="!valid || loading || (!!recaptchaSiteKey && !recaptchaToken)"
|
||||
:loading="loading"
|
||||
class="mb-4"
|
||||
style="background-color: #a31515 !important; color: white !important;"
|
||||
@@ -160,25 +162,25 @@
|
||||
<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>
|
||||
<span class="text-body-2" style="color: #000 !important;">€{{ membershipFee }}/year</span>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row dense class="mb-2" v-if="registrationConfig.iban">
|
||||
<v-row dense class="mb-2" v-if="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>
|
||||
<span class="text-body-2 font-family-monospace" style="color: #000 !important;">{{ iban }}</span>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row dense class="mb-2" v-if="registrationConfig.accountHolder">
|
||||
<v-row dense class="mb-2" v-if="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>
|
||||
<span class="text-body-2" style="color: #000 !important;">{{ accountHolder }}</span>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
@@ -201,40 +203,46 @@
|
||||
<RegistrationSuccessDialog
|
||||
v-model="showSuccessDialog"
|
||||
:member-data="registrationResult"
|
||||
:payment-info="registrationConfig"
|
||||
:payment-info="{ membershipFee, iban, accountHolder }"
|
||||
@go-to-login="goToLogin"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { RegistrationFormData, RecaptchaConfig, RegistrationConfig } from '~/utils/types';
|
||||
|
||||
// Declare global grecaptcha interface for TypeScript
|
||||
declare global {
|
||||
interface Window {
|
||||
grecaptcha: {
|
||||
ready: (callback: () => void) => void;
|
||||
execute: (siteKey: string, options: { action: string }) => Promise<string>;
|
||||
};
|
||||
}
|
||||
}
|
||||
import type { RegistrationFormData } from '~/utils/types';
|
||||
import { getStaticDeviceInfo, getDeviceCssClasses, applyMobileSafariOptimizations, getMobileSafariViewportMeta } from '~/utils/static-device-detection';
|
||||
import { loadAllConfigs } from '~/utils/config-cache';
|
||||
|
||||
// Page metadata
|
||||
definePageMeta({
|
||||
layout: false
|
||||
});
|
||||
|
||||
// Static device detection - no reactive dependencies
|
||||
const deviceInfo = getStaticDeviceInfo();
|
||||
|
||||
// Configs with fallback defaults
|
||||
const recaptchaConfig = ref<RecaptchaConfig>({ siteKey: '', secretKey: '' });
|
||||
const registrationConfig = ref<RegistrationConfig>({
|
||||
membershipFee: 150,
|
||||
iban: 'MC58 1756 9000 0104 0050 1001 860',
|
||||
accountHolder: 'ASSOCIATION MONACO USA'
|
||||
// Head configuration with static device-optimized viewport
|
||||
useHead({
|
||||
title: 'Register - MonacoUSA Portal',
|
||||
meta: [
|
||||
{ name: 'description', content: 'Register to become a member of MonacoUSA Association' },
|
||||
{ name: 'robots', content: 'noindex, nofollow' },
|
||||
{ name: 'viewport', content: getMobileSafariViewportMeta() }
|
||||
]
|
||||
});
|
||||
|
||||
// Reactive data - Using individual refs to prevent Vue reactivity corruption
|
||||
// Static CSS classes - computed once, never reactive
|
||||
const containerClasses = ref(getDeviceCssClasses('signup-container'));
|
||||
const cardClasses = ref(() => {
|
||||
const classes = ['signup-card'];
|
||||
if (deviceInfo.isMobileSafari) {
|
||||
classes.push('performance-optimized', 'no-backdrop-filter');
|
||||
}
|
||||
return classes.join(' ');
|
||||
});
|
||||
|
||||
// Form data - individual refs to prevent Vue reactivity corruption
|
||||
const firstName = ref('');
|
||||
const lastName = ref('');
|
||||
const email = ref('');
|
||||
@@ -243,6 +251,24 @@ const dateOfBirth = ref('');
|
||||
const address = ref('');
|
||||
const nationality = ref('');
|
||||
|
||||
// Form state
|
||||
const valid = ref(false);
|
||||
const loading = ref(false);
|
||||
const errorMessage = ref('');
|
||||
|
||||
// reCAPTCHA v2 state (static, not reactive)
|
||||
let recaptchaSiteKey = '';
|
||||
const recaptchaToken = ref('');
|
||||
|
||||
// Registration config (static, not reactive)
|
||||
let membershipFee = 150;
|
||||
let iban = 'MC58 1756 9000 0104 0050 1001 860';
|
||||
let accountHolder = 'ASSOCIATION MONACO USA';
|
||||
|
||||
// Success dialog state
|
||||
const showSuccessDialog = ref(false);
|
||||
const registrationResult = ref<{ memberId: string; email: string } | undefined>(undefined);
|
||||
|
||||
// Computed property to create form object for submission
|
||||
const form = computed(() => ({
|
||||
first_name: firstName.value,
|
||||
@@ -254,31 +280,6 @@ const form = computed(() => ({
|
||||
nationality: nationality.value
|
||||
}));
|
||||
|
||||
const valid = ref(false);
|
||||
const loading = ref(false);
|
||||
const recaptchaToken = ref('');
|
||||
const successMessage = ref('');
|
||||
const errorMessage = ref('');
|
||||
const configsLoaded = ref(false);
|
||||
|
||||
// Success dialog state
|
||||
const showSuccessDialog = ref(false);
|
||||
const registrationResult = ref<{ memberId: string; email: string } | undefined>(undefined);
|
||||
|
||||
// Head configuration
|
||||
useHead({
|
||||
title: 'Register - MonacoUSA Portal',
|
||||
meta: [
|
||||
{ name: 'description', content: 'Register to become a member of MonacoUSA Association' },
|
||||
{ name: 'robots', content: 'noindex, nofollow' },
|
||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no' }
|
||||
]
|
||||
});
|
||||
|
||||
// Static CSS classes based on device (no reactive dependencies)
|
||||
const containerClasses = ref('signup-container');
|
||||
const cardClasses = ref('signup-card');
|
||||
|
||||
// Form validation rules
|
||||
const nameRules = [
|
||||
(v: string) => !!v || 'Name is required',
|
||||
@@ -321,39 +322,39 @@ const nationalityRules = [
|
||||
(v: string) => !!v || 'Nationality is required'
|
||||
];
|
||||
|
||||
// reCAPTCHA handling
|
||||
function onRecaptchaVerified(token: string) {
|
||||
recaptchaToken.value = token;
|
||||
// reCAPTCHA v2 callbacks - global functions for Google's API
|
||||
function setupRecaptchaCallbacks() {
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
(window as any).onRecaptchaVerified = (token: string) => {
|
||||
console.log('[signup] reCAPTCHA verified');
|
||||
recaptchaToken.value = token;
|
||||
};
|
||||
|
||||
(window as any).onRecaptchaExpired = () => {
|
||||
console.log('[signup] reCAPTCHA expired');
|
||||
recaptchaToken.value = '';
|
||||
};
|
||||
}
|
||||
|
||||
function onRecaptchaExpired() {
|
||||
recaptchaToken.value = '';
|
||||
}
|
||||
|
||||
// Template refs
|
||||
const recaptcha = ref<any>(null);
|
||||
|
||||
|
||||
|
||||
// reCAPTCHA v3 token generation
|
||||
async function generateRecaptchaToken(): Promise<string> {
|
||||
if (!recaptchaConfig.value.siteKey || typeof window === 'undefined') {
|
||||
return '';
|
||||
// Load reCAPTCHA v2 script
|
||||
function loadRecaptchaScript(siteKey: string) {
|
||||
if (typeof window === 'undefined' || !siteKey) return;
|
||||
|
||||
// Check if already loaded
|
||||
if (document.querySelector('script[src*="recaptcha/api.js"]')) {
|
||||
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('');
|
||||
}
|
||||
});
|
||||
console.log('[signup] Loading reCAPTCHA v2 script');
|
||||
const script = document.createElement('script');
|
||||
script.src = 'https://www.google.com/recaptcha/api.js';
|
||||
script.async = true;
|
||||
script.defer = true;
|
||||
script.onload = () => {
|
||||
console.log('[signup] reCAPTCHA v2 script loaded');
|
||||
};
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
|
||||
// Form submission
|
||||
@@ -364,36 +365,32 @@ async function submitRegistration() {
|
||||
|
||||
loading.value = true;
|
||||
errorMessage.value = '';
|
||||
successMessage.value = '';
|
||||
|
||||
try {
|
||||
// Generate reCAPTCHA v3 token if configured
|
||||
let token = '';
|
||||
if (recaptchaConfig.value.siteKey) {
|
||||
token = await generateRecaptchaToken();
|
||||
}
|
||||
|
||||
const registrationData: RegistrationFormData = {
|
||||
...form.value,
|
||||
recaptcha_token: token
|
||||
recaptcha_token: recaptchaToken.value || ''
|
||||
};
|
||||
|
||||
console.log('[signup] Submitting registration...');
|
||||
const response = await $fetch('/api/registration', {
|
||||
method: 'POST',
|
||||
body: registrationData
|
||||
}) as any;
|
||||
|
||||
if (response?.success) {
|
||||
console.log('[signup] Registration successful');
|
||||
|
||||
// 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
|
||||
// Show success dialog
|
||||
showSuccessDialog.value = true;
|
||||
|
||||
// Reset form by resetting individual refs
|
||||
// Reset form
|
||||
firstName.value = '';
|
||||
lastName.value = '';
|
||||
email.value = '';
|
||||
@@ -401,10 +398,20 @@ async function submitRegistration() {
|
||||
dateOfBirth.value = '';
|
||||
address.value = '';
|
||||
nationality.value = '';
|
||||
recaptchaToken.value = '';
|
||||
|
||||
// Reset reCAPTCHA widget if present
|
||||
if (typeof window !== 'undefined' && (window as any).grecaptcha) {
|
||||
try {
|
||||
(window as any).grecaptcha.reset();
|
||||
} catch (e) {
|
||||
console.debug('[signup] Could not reset reCAPTCHA:', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('Registration failed:', error);
|
||||
console.error('[signup] Registration failed:', error);
|
||||
errorMessage.value = error.data?.message || error.message || 'Registration failed. Please try again.';
|
||||
} finally {
|
||||
loading.value = false;
|
||||
@@ -416,79 +423,66 @@ const goToLogin = () => {
|
||||
navigateTo('/login');
|
||||
};
|
||||
|
||||
// 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
|
||||
}
|
||||
// Flag to prevent multiple initialization calls
|
||||
let initialized = false;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// Simplified initialization - prevent reload loops
|
||||
// Component initialization - Safari iOS reload loop prevention
|
||||
onMounted(async () => {
|
||||
// Prevent multiple initializations
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
// Static device detection from Nuxt Device Module - no reactive dependencies
|
||||
const { isMobile, isIos, isSafari } = useDevice();
|
||||
|
||||
// Detect mobile Safari specifically
|
||||
const isMobileSafari = isMobile && isIos && isSafari;
|
||||
|
||||
// Apply classes once (static, no reactivity)
|
||||
const containerClassList = ['signup-container'];
|
||||
if (isMobile) containerClassList.push('is-mobile');
|
||||
if (isMobileSafari) containerClassList.push('is-mobile-safari');
|
||||
if (isIos) containerClassList.push('is-ios');
|
||||
containerClasses.value = containerClassList.join(' ');
|
||||
if (initialized || typeof window === 'undefined') return;
|
||||
initialized = true;
|
||||
|
||||
const cardClassList = ['signup-card'];
|
||||
if (isMobileSafari) {
|
||||
cardClassList.push('performance-optimized');
|
||||
cardClassList.push('no-backdrop-filter');
|
||||
console.log('[signup] Initializing signup page...');
|
||||
|
||||
// Apply mobile Safari optimizations early
|
||||
if (deviceInfo.isMobileSafari) {
|
||||
applyMobileSafariOptimizations();
|
||||
console.log('[signup] Mobile Safari optimizations applied');
|
||||
}
|
||||
cardClasses.value = cardClassList.filter(Boolean).join(' ');
|
||||
|
||||
|
||||
// Set up reCAPTCHA callbacks before loading configs
|
||||
setupRecaptchaCallbacks();
|
||||
|
||||
try {
|
||||
// Load reCAPTCHA config
|
||||
$fetch('/api/recaptcha-config')
|
||||
.then((response: any) => {
|
||||
if (response?.success && response?.data?.siteKey) {
|
||||
recaptchaConfig.value.siteKey = response.data.siteKey;
|
||||
loadRecaptchaScript(response.data.siteKey);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// Silently fail for reCAPTCHA - not critical
|
||||
console.debug('reCAPTCHA config not available');
|
||||
});
|
||||
// Load configs using cached utility (prevents repeated API calls)
|
||||
console.log('[signup] Loading configurations...');
|
||||
const configs = await loadAllConfigs();
|
||||
|
||||
// Load registration config
|
||||
$fetch('/api/registration-config')
|
||||
.then((response: any) => {
|
||||
if (response?.success) {
|
||||
registrationConfig.value = response.data;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// Use defaults if config fails to load
|
||||
console.debug('Using default registration config');
|
||||
registrationConfig.value = {
|
||||
membershipFee: 150,
|
||||
iban: 'MC58 1756 9000 0104 0050 1001 860',
|
||||
accountHolder: 'ASSOCIATION MONACO USA'
|
||||
};
|
||||
});
|
||||
// Set static config values
|
||||
if (configs.recaptcha?.siteKey) {
|
||||
recaptchaSiteKey = configs.recaptcha.siteKey;
|
||||
console.log('[signup] reCAPTCHA configured');
|
||||
|
||||
// Load reCAPTCHA v2 script with delay to prevent render blocking
|
||||
setTimeout(() => {
|
||||
loadRecaptchaScript(recaptchaSiteKey);
|
||||
}, 100);
|
||||
}
|
||||
|
||||
if (configs.registration) {
|
||||
membershipFee = configs.registration.membershipFee || 150;
|
||||
iban = configs.registration.iban || 'MC58 1756 9000 0104 0050 1001 860';
|
||||
accountHolder = configs.registration.accountHolder || 'ASSOCIATION MONACO USA';
|
||||
console.log('[signup] Registration config loaded');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
// Prevent any errors from bubbling up and causing reload
|
||||
console.warn('Signup page initialization error:', error);
|
||||
console.warn('[signup] Configuration loading error (using defaults):', error);
|
||||
// Use default values which are already set above
|
||||
}
|
||||
|
||||
console.log('[signup] Signup page initialization complete');
|
||||
});
|
||||
|
||||
// Cleanup on component unmount
|
||||
onUnmounted(() => {
|
||||
// Clean up global reCAPTCHA callbacks
|
||||
if (typeof window !== 'undefined') {
|
||||
delete (window as any).onRecaptchaVerified;
|
||||
delete (window as any).onRecaptchaExpired;
|
||||
}
|
||||
initialized = false;
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -515,7 +509,7 @@ onMounted(async () => {
|
||||
background-attachment: scroll !important; /* Force scroll attachment */
|
||||
}
|
||||
|
||||
.signup-container.performance-mode {
|
||||
.signup-container.performance-optimized {
|
||||
will-change: auto; /* Reduce repainting */
|
||||
transform: translateZ(0); /* Force hardware acceleration but lighter */
|
||||
}
|
||||
@@ -539,7 +533,7 @@ onMounted(async () => {
|
||||
}
|
||||
|
||||
/* Performance mode background - simpler for mobile */
|
||||
.signup-container.performance-mode::before {
|
||||
.signup-container.performance-optimized::before {
|
||||
background: linear-gradient(rgba(163, 21, 21, 0.8), rgba(0, 0, 0, 0.6));
|
||||
/* Remove background image on low-performance devices */
|
||||
}
|
||||
@@ -591,6 +585,19 @@ onMounted(async () => {
|
||||
color: #000 !important;
|
||||
}
|
||||
|
||||
/* reCAPTCHA v2 styling */
|
||||
.g-recaptcha {
|
||||
transform: scale(0.9);
|
||||
transform-origin: 0 0;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.g-recaptcha {
|
||||
transform: scale(0.8);
|
||||
}
|
||||
}
|
||||
|
||||
/* Custom scrollbar for mobile */
|
||||
::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
|
||||
Reference in New Issue
Block a user