Refactor mobile detection to use built-in Nuxt device module
Some checks failed
Build And Push Image / docker (push) Failing after 2m27s

Replace custom useMobileDetection composable with Nuxt's useDevice(),
removing reactive mobile detection in favor of static detection to
prevent reload loops and simplify viewport handling
This commit is contained in:
2025-08-10 14:38:02 +02:00
parent fd08c38ade
commit 2eaf9cda95
10 changed files with 532 additions and 462 deletions

View File

@@ -157,24 +157,14 @@
</template>
<script setup lang="ts">
import { useMobileDetection } from '~/composables/useMobileDetection';
definePageMeta({
layout: false,
middleware: 'guest'
});
// Use unified mobile detection
const mobileDetection = useMobileDetection();
// Mobile Safari optimization classes
const containerClasses = computed(() => {
const classes = ['password-setup-page'];
if (mobileDetection.isMobile) classes.push('is-mobile');
if (mobileDetection.isMobileSafari) classes.push('is-mobile-safari');
if (mobileDetection.isIOS) classes.push('is-ios');
return classes.join(' ');
});
// Static CSS classes based on device (no reactive dependencies)
const containerClasses = ref('password-setup-page');
// Reactive state
const loading = ref(false);
@@ -252,25 +242,12 @@ useHead({
]
});
// Toggle password visibility with debouncing on mobile
// Toggle password visibility - simplified for static detection
const togglePasswordVisibility = (field: 'password' | 'confirm') => {
// Prevent rapid toggling which can cause issues on mobile
if (mobileDetection.isMobile) {
// Use nextTick to defer the update
nextTick(() => {
if (field === 'password') {
showPassword.value = !showPassword.value;
} else {
showConfirmPassword.value = !showConfirmPassword.value;
}
});
if (field === 'password') {
showPassword.value = !showPassword.value;
} else {
// Immediate toggle on desktop
if (field === 'password') {
showPassword.value = !showPassword.value;
} else {
showConfirmPassword.value = !showConfirmPassword.value;
}
showConfirmPassword.value = !showConfirmPassword.value;
}
};
@@ -333,13 +310,26 @@ const setupPassword = async () => {
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(' ');
// 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 (mobileDetection.isIOS) {
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');

View File

@@ -209,7 +209,6 @@
<script setup lang="ts">
import type { RegistrationFormData, RecaptchaConfig, RegistrationConfig } from '~/utils/types';
import { useMobileDetection } from '~/composables/useMobileDetection';
// Declare global grecaptcha interface for TypeScript
declare global {
@@ -226,8 +225,6 @@ definePageMeta({
layout: false
});
// Use unified mobile detection
const mobileDetection = useMobileDetection();
// Configs with fallback defaults
const recaptchaConfig = ref<RecaptchaConfig>({ siteKey: '', secretKey: '' });
@@ -266,7 +263,7 @@ const configsLoaded = ref(false);
// Success dialog state
const showSuccessDialog = ref(false);
const registrationResult = ref<{ memberId: string; email: string } | null>(null);
const registrationResult = ref<{ memberId: string; email: string } | undefined>(undefined);
// Head configuration
useHead({
@@ -278,23 +275,9 @@ useHead({
]
});
// Dynamic CSS classes based on device
const containerClasses = computed(() => {
const classes = ['signup-container'];
if (mobileDetection.isMobile) classes.push('is-mobile');
if (mobileDetection.isMobileSafari) classes.push('is-mobile-safari');
if (mobileDetection.isIOS) classes.push('is-ios');
return classes.join(' ');
});
const cardClasses = computed(() => {
const classes = ['signup-card'];
if (mobileDetection.isMobileSafari) {
classes.push('performance-optimized');
classes.push('no-backdrop-filter');
}
return classes.filter(Boolean).join(' ');
});
// Static CSS classes based on device (no reactive dependencies)
const containerClasses = ref('signup-container');
const cardClasses = ref('signup-card');
// Form validation rules
const nameRules = [
@@ -451,6 +434,26 @@ 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(' ');
const cardClassList = ['signup-card'];
if (isMobileSafari) {
cardClassList.push('performance-optimized');
cardClassList.push('no-backdrop-filter');
}
cardClasses.value = cardClassList.filter(Boolean).join(' ');
try {
// Load reCAPTCHA config
$fetch('/api/recaptcha-config')