Replace date-fns with native date formatting and remove unused code
All checks were successful
Build And Push Image / docker (push) Successful in 1m34s

Remove date-fns dependency in favor of native Intl.DateTimeFormat APIs, clean up obsolete admin endpoints, utility files, and archived documentation. Consolidate docs structure and remove unused plugins.
This commit is contained in:
2025-08-14 15:08:40 +02:00
parent 676bbc04f6
commit 503d68cd2d
40 changed files with 19225 additions and 5851 deletions

View File

@@ -1,214 +0,0 @@
// Mobile detection and debugging utilities
export interface DeviceInfo {
isMobile: boolean;
isIOS: boolean;
isAndroid: boolean;
isTablet: boolean;
browser: string;
version: string;
userAgent: string;
cookieEnabled: boolean;
screenWidth: number;
screenHeight: number;
}
export function getDeviceInfo(): DeviceInfo {
if (typeof window === 'undefined') {
return {
isMobile: false,
isIOS: false,
isAndroid: false,
isTablet: false,
browser: 'Unknown',
version: 'Unknown',
userAgent: 'Server',
cookieEnabled: false,
screenWidth: 0,
screenHeight: 0
};
}
const ua = navigator.userAgent;
// Mobile detection
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(ua);
const isIOS = /iPad|iPhone|iPod/.test(ua);
const isAndroid = /Android/.test(ua);
const isTablet = /iPad|Android(?=.*\bMobile\b)(?!.*\bMobile\b)/i.test(ua);
// Browser detection
let browser = 'Unknown';
let version = 'Unknown';
if (ua.includes('Chrome') && !ua.includes('Edge')) {
browser = 'Chrome';
const match = ua.match(/Chrome\/(\d+)/);
version = match ? match[1] : 'Unknown';
} else if (ua.includes('Safari') && !ua.includes('Chrome')) {
browser = 'Safari';
const match = ua.match(/Version\/(\d+)/);
version = match ? match[1] : 'Unknown';
} else if (ua.includes('Firefox')) {
browser = 'Firefox';
const match = ua.match(/Firefox\/(\d+)/);
version = match ? match[1] : 'Unknown';
} else if (ua.includes('Edge')) {
browser = 'Edge';
const match = ua.match(/Edge\/(\d+)/);
version = match ? match[1] : 'Unknown';
}
return {
isMobile,
isIOS,
isAndroid,
isTablet,
browser,
version,
userAgent: ua,
cookieEnabled: navigator.cookieEnabled,
screenWidth: window.screen.width,
screenHeight: window.screen.height
};
}
export function logDeviceInfo(label: string = 'Device Info'): void {
if (typeof window === 'undefined') return;
const info = getDeviceInfo();
console.group(`📱 ${label}`);
console.log('Mobile:', info.isMobile);
console.log('iOS:', info.isIOS);
console.log('Android:', info.isAndroid);
console.log('Tablet:', info.isTablet);
console.log('Browser:', `${info.browser} ${info.version}`);
console.log('Cookies Enabled:', info.cookieEnabled);
console.log('Screen:', `${info.screenWidth}x${info.screenHeight}`);
console.log('User Agent:', info.userAgent);
// Additional debugging info
console.log('Viewport:', `${window.innerWidth}x${window.innerHeight}`);
console.log('Device Pixel Ratio:', window.devicePixelRatio);
console.log('Online:', navigator.onLine);
console.log('Language:', navigator.language);
console.log('Platform:', navigator.platform);
// Cookie testing
try {
document.cookie = 'test=1; path=/';
const canSetCookie = document.cookie.includes('test=1');
console.log('Can Set Cookies:', canSetCookie);
if (canSetCookie) {
document.cookie = 'test=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT';
}
} catch (error) {
console.log('Cookie Test Error:', error);
}
console.groupEnd();
}
export function isMobileDevice(): boolean {
return getDeviceInfo().isMobile;
}
export function isIOSDevice(): boolean {
return getDeviceInfo().isIOS;
}
export function isAndroidDevice(): boolean {
return getDeviceInfo().isAndroid;
}
export function getMobileBrowser(): string {
const info = getDeviceInfo();
return `${info.browser} ${info.version}`;
}
// Enhanced mobile login debugging
export function debugMobileLogin(context: string): void {
if (!isMobileDevice()) return;
console.group(`🔐 Mobile Login Debug - ${context}`);
logDeviceInfo('Current Device');
// Check for known mobile issues
const info = getDeviceInfo();
const issues: string[] = [];
if (info.isIOS && info.browser === 'Safari' && parseInt(info.version) < 14) {
issues.push('Safari < 14: Cookie issues with SameSite');
}
if (info.isAndroid && info.browser === 'Chrome' && parseInt(info.version) < 80) {
issues.push('Chrome < 80: SameSite cookie support limited');
}
if (!info.cookieEnabled) {
issues.push('Cookies disabled in browser');
}
if (info.screenWidth < 375) {
issues.push('Small screen may affect layout');
}
if (issues.length > 0) {
console.warn('⚠️ Potential Issues:', issues);
} else {
console.log('✅ No known compatibility issues detected');
}
console.groupEnd();
}
// Network debugging
export function debugNetworkConditions(): void {
if (typeof navigator === 'undefined') return;
console.group('🌐 Network Conditions');
console.log('Online:', navigator.onLine);
// @ts-ignore - connection API is experimental
if (navigator.connection) {
// @ts-ignore
const conn = navigator.connection;
console.log('Connection Type:', conn.effectiveType);
console.log('Downlink:', conn.downlink, 'Mbps');
console.log('RTT:', conn.rtt, 'ms');
console.log('Save Data:', conn.saveData);
} else {
console.log('Network API not available');
}
console.groupEnd();
}
// PWA debugging
export function debugPWACapabilities(): void {
if (typeof window === 'undefined') return;
console.group('📱 PWA Capabilities');
console.log('Service Worker:', 'serviceWorker' in navigator);
console.log('Web App Manifest:', 'onbeforeinstallprompt' in window);
console.log('Standalone Mode:', window.matchMedia('(display-mode: standalone)').matches);
console.log('Full Screen API:', 'requestFullscreen' in document.documentElement);
console.log('Notification API:', 'Notification' in window);
console.log('Push API:', 'PushManager' in window);
console.groupEnd();
}
// Complete mobile debugging suite
export function runMobileDiagnostics(): void {
if (!isMobileDevice()) {
console.log('📱 Not a mobile device, skipping mobile diagnostics');
return;
}
console.group('🔍 Mobile Diagnostics Suite');
logDeviceInfo();
debugNetworkConditions();
debugPWACapabilities();
console.groupEnd();
}

View File

@@ -1,310 +0,0 @@
/**
* Reload Loop Detection and Prevention Utility
* Advanced mobile Safari reload loop prevention system
*/
interface PageLoadInfo {
url: string;
timestamp: number;
userAgent: string;
loadCount: number;
}
interface ReloadLoopState {
enabled: boolean;
pageLoads: PageLoadInfo[];
blockedPages: Set<string>;
emergencyMode: boolean;
debugMode: boolean;
}
const RELOAD_LOOP_THRESHOLD = 5; // Max page loads in time window
const TIME_WINDOW = 10000; // 10 seconds
const EMERGENCY_BLOCK_TIME = 30000; // 30 seconds
const STORAGE_KEY = 'reload_loop_prevention';
/**
* Get or initialize reload loop state
*/
function getReloadLoopState(): ReloadLoopState {
if (typeof window === 'undefined') {
return {
enabled: false,
pageLoads: [],
blockedPages: new Set(),
emergencyMode: false,
debugMode: false
};
}
// Use sessionStorage for persistence across reloads
try {
const stored = sessionStorage.getItem(STORAGE_KEY);
if (stored) {
const parsed = JSON.parse(stored);
return {
...parsed,
blockedPages: new Set(parsed.blockedPages || [])
};
}
} catch (error) {
console.warn('[reload-prevention] Failed to parse stored state:', error);
}
// Initialize new state
const initialState: ReloadLoopState = {
enabled: true,
pageLoads: [],
blockedPages: new Set(),
emergencyMode: false,
debugMode: process.env.NODE_ENV === 'development'
};
saveReloadLoopState(initialState);
return initialState;
}
/**
* Save reload loop state to sessionStorage
*/
function saveReloadLoopState(state: ReloadLoopState): void {
if (typeof window === 'undefined') return;
try {
const toStore = {
...state,
blockedPages: Array.from(state.blockedPages)
};
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(toStore));
} catch (error) {
console.warn('[reload-prevention] Failed to save state:', error);
}
}
/**
* Record a page load and check for reload loops
*/
export function recordPageLoad(url: string): boolean {
const state = getReloadLoopState();
if (!state.enabled) {
return true; // Allow load
}
const now = Date.now();
const userAgent = navigator.userAgent || '';
// Clean old page loads
state.pageLoads = state.pageLoads.filter(
load => now - load.timestamp < TIME_WINDOW
);
// Find existing load for this URL
const existingLoad = state.pageLoads.find(load => load.url === url);
if (existingLoad) {
existingLoad.loadCount++;
existingLoad.timestamp = now;
} else {
state.pageLoads.push({
url,
timestamp: now,
userAgent,
loadCount: 1
});
}
// Check for reload loop
const urlLoads = state.pageLoads.filter(load => load.url === url);
const totalLoads = urlLoads.reduce((sum, load) => sum + load.loadCount, 0);
if (totalLoads >= RELOAD_LOOP_THRESHOLD) {
console.error(`[reload-prevention] Reload loop detected for ${url} (${totalLoads} loads)`);
// Block this page
state.blockedPages.add(url);
state.emergencyMode = true;
// Set emergency timeout
setTimeout(() => {
state.emergencyMode = false;
state.blockedPages.delete(url);
saveReloadLoopState(state);
console.log(`[reload-prevention] Emergency block lifted for ${url}`);
}, EMERGENCY_BLOCK_TIME);
saveReloadLoopState(state);
return false; // Block load
}
saveReloadLoopState(state);
return true; // Allow load
}
/**
* Check if a page is currently blocked
*/
export function isPageBlocked(url: string): boolean {
const state = getReloadLoopState();
return state.blockedPages.has(url);
}
/**
* Initialize reload loop prevention for a page
*/
export function initReloadLoopPrevention(pageName: string): boolean {
if (typeof window === 'undefined') {
return true;
}
const currentUrl = window.location.pathname;
const canLoad = recordPageLoad(currentUrl);
if (!canLoad) {
console.error(`[reload-prevention] Page load blocked: ${pageName} (${currentUrl})`);
// Show emergency message
showEmergencyMessage(pageName);
return false;
}
if (getReloadLoopState().debugMode) {
console.log(`[reload-prevention] Page load allowed: ${pageName} (${currentUrl})`);
}
return true;
}
/**
* Show emergency message when page is blocked
*/
function showEmergencyMessage(pageName: string): void {
const message = `
<div style="
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(163, 21, 21, 0.95);
color: white;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 10000;
font-family: Arial, sans-serif;
text-align: center;
padding: 20px;
">
<h1 style="font-size: 24px; margin-bottom: 20px;">Page Loading Temporarily Blocked</h1>
<p style="font-size: 16px; margin-bottom: 20px;">
Multiple rapid page loads detected for ${pageName}.<br>
This is a safety measure to prevent infinite loading loops.
</p>
<p style="font-size: 14px; margin-bottom: 30px;">
The block will be automatically lifted in 30 seconds.
</p>
<button onclick="location.href='/'" style="
background: white;
color: #a31515;
border: none;
padding: 12px 24px;
border-radius: 6px;
font-size: 16px;
cursor: pointer;
margin-right: 15px;
">Return to Home</button>
<button onclick="window.history.back()" style="
background: transparent;
color: white;
border: 2px solid white;
padding: 12px 24px;
border-radius: 6px;
font-size: 16px;
cursor: pointer;
">Go Back</button>
</div>
`;
document.body.innerHTML = message;
}
/**
* Clear reload loop prevention state (for testing)
*/
export function clearReloadLoopPrevention(): void {
if (typeof window === 'undefined') return;
try {
sessionStorage.removeItem(STORAGE_KEY);
console.log('[reload-prevention] State cleared');
} catch (error) {
console.warn('[reload-prevention] Failed to clear state:', error);
}
}
/**
* Get current reload loop prevention status
*/
export function getReloadLoopStatus(): {
enabled: boolean;
emergencyMode: boolean;
blockedPages: string[];
pageLoads: PageLoadInfo[];
} {
const state = getReloadLoopState();
return {
enabled: state.enabled,
emergencyMode: state.emergencyMode,
blockedPages: Array.from(state.blockedPages),
pageLoads: state.pageLoads
};
}
/**
* Mobile Safari specific optimizations
*/
export function applyMobileSafariReloadLoopFixes(): void {
if (typeof window === 'undefined') return;
const userAgent = navigator.userAgent || '';
const isMobileSafari = /iPad|iPhone|iPod/.test(userAgent) && /Safari/i.test(userAgent) && !/Chrome/i.test(userAgent);
if (!isMobileSafari) return;
console.log('[reload-prevention] Applying mobile Safari specific fixes');
// Prevent Safari's aggressive caching that can cause reload loops
window.addEventListener('pageshow', (event) => {
if (event.persisted) {
console.log('[reload-prevention] Page restored from bfcache');
// Force a small delay to prevent immediate re-execution
setTimeout(() => {
console.log('[reload-prevention] bfcache restore handled');
}, 100);
}
});
// Handle Safari's navigation timing issues
window.addEventListener('beforeunload', () => {
// Mark navigation in progress to prevent reload loop detection
const state = getReloadLoopState();
state.enabled = false;
saveReloadLoopState(state);
});
// Re-enable after navigation completes
window.addEventListener('load', () => {
setTimeout(() => {
const state = getReloadLoopState();
state.enabled = true;
saveReloadLoopState(state);
}, 500);
});
}
// Auto-initialize on module load
if (typeof window !== 'undefined') {
applyMobileSafariReloadLoopFixes();
}

View File

@@ -1,128 +0,0 @@
/**
* Static Device Detection Utility
* Provides non-reactive device detection for Safari iOS reload loop prevention
* Uses direct navigator.userAgent analysis without creating Vue reactive dependencies
*/
export interface DeviceInfo {
isMobile: boolean;
isIos: boolean;
isSafari: boolean;
isMobileSafari: boolean;
isAndroid: boolean;
userAgent: string;
}
let cachedDeviceInfo: DeviceInfo | null = null;
/**
* Get static device information without creating reactive dependencies
* Results are cached to prevent multiple userAgent parsing
*/
export function getStaticDeviceInfo(): DeviceInfo {
// Return cached result if available
if (cachedDeviceInfo) {
return cachedDeviceInfo;
}
// Only run on client-side
if (typeof window === 'undefined' || typeof navigator === 'undefined') {
cachedDeviceInfo = {
isMobile: false,
isIos: false,
isSafari: false,
isMobileSafari: false,
isAndroid: false,
userAgent: ''
};
return cachedDeviceInfo;
}
const userAgent = navigator.userAgent;
// Device detection logic
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 isAndroid = /Android/i.test(userAgent);
const isSafari = /^((?!chrome|android).)*safari/i.test(userAgent);
const isMobileSafari = isIos && isSafari;
// Cache the result
cachedDeviceInfo = {
isMobile,
isIos,
isSafari,
isMobileSafari,
isAndroid,
userAgent
};
return cachedDeviceInfo;
}
/**
* Get CSS classes for device-specific styling
* Returns a space-separated string of CSS classes
*/
export function getDeviceCssClasses(baseClass: string = ''): string {
const device = getStaticDeviceInfo();
const classes = [baseClass].filter(Boolean);
if (device.isMobile) classes.push('is-mobile');
if (device.isIos) classes.push('is-ios');
if (device.isSafari) classes.push('is-safari');
if (device.isMobileSafari) classes.push('is-mobile-safari');
if (device.isAndroid) classes.push('is-android');
return classes.join(' ');
}
/**
* Check if current device is mobile Safari specifically
* This is the primary problematic browser for reload loops
*/
export function isMobileSafari(): boolean {
return getStaticDeviceInfo().isMobileSafari;
}
/**
* Apply mobile Safari specific optimizations to DOM element
* Should be called once per component to prevent reactive updates
*/
export function applyMobileSafariOptimizations(element?: HTMLElement): void {
if (!isMobileSafari()) return;
const targetElement = element || document.documentElement;
// Apply performance optimization classes
targetElement.classList.add('is-mobile-safari', 'performance-optimized');
// Set viewport height CSS variable for mobile Safari
const vh = window.innerHeight * 0.01;
targetElement.style.setProperty('--vh', `${vh}px`);
// Disable problematic CSS features for performance
targetElement.style.setProperty('--backdrop-filter', 'none');
targetElement.style.setProperty('--will-change', 'auto');
}
/**
* Get viewport meta content optimized for mobile Safari
*/
export function getMobileSafariViewportMeta(): string {
const device = getStaticDeviceInfo();
if (device.isMobileSafari) {
// Prevent zoom on input focus for iOS Safari
return 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no';
}
return 'width=device-width, initial-scale=1.0';
}
/**
* Clear cached device info (useful for testing)
*/
export function clearDeviceInfoCache(): void {
cachedDeviceInfo = null;
}

View File

@@ -1,300 +0,0 @@
/**
* Client-side verification state management with circuit breaker pattern
* Prevents endless reload loops on mobile browsers
*/
export interface VerificationAttempt {
token: string;
attempts: number;
lastAttempt: number;
maxAttempts: number;
status: 'pending' | 'success' | 'failed' | 'blocked';
errors: string[];
}
const STORAGE_KEY = 'email_verification_state';
const MAX_ATTEMPTS_DEFAULT = 3;
const ATTEMPT_WINDOW = 5 * 60 * 1000; // 5 minutes
const CIRCUIT_BREAKER_TIMEOUT = 10 * 60 * 1000; // 10 minutes
/**
* Get verification state for a token
*/
export function getVerificationState(token: string): VerificationAttempt | null {
if (typeof window === 'undefined' || !token) return null;
try {
const stored = sessionStorage.getItem(`${STORAGE_KEY}_${token.substring(0, 10)}`);
if (!stored) return null;
const state = JSON.parse(stored) as VerificationAttempt;
// Check if circuit breaker timeout has passed
const now = Date.now();
if (state.status === 'blocked' && (now - state.lastAttempt) > CIRCUIT_BREAKER_TIMEOUT) {
console.log('[verification-state] Circuit breaker timeout passed, resetting state');
clearVerificationState(token);
return null;
}
return state;
} catch (error) {
console.warn('[verification-state] Failed to parse stored state:', error);
return null;
}
}
/**
* Initialize or update verification state
*/
export function initVerificationState(token: string, maxAttempts: number = MAX_ATTEMPTS_DEFAULT): VerificationAttempt {
if (typeof window === 'undefined' || !token) {
throw new Error('Cannot initialize verification state: no window or token');
}
const existing = getVerificationState(token);
if (existing) {
return existing;
}
const state: VerificationAttempt = {
token,
attempts: 0,
lastAttempt: 0,
maxAttempts,
status: 'pending',
errors: []
};
try {
sessionStorage.setItem(`${STORAGE_KEY}_${token.substring(0, 10)}`, JSON.stringify(state));
console.log('[verification-state] Initialized verification state for token');
return state;
} catch (error) {
console.error('[verification-state] Failed to save state:', error);
return state;
}
}
/**
* Record a verification attempt
*/
export function recordAttempt(token: string, success: boolean = false, error?: string): VerificationAttempt {
if (typeof window === 'undefined' || !token) {
throw new Error('Cannot record attempt: no window or token');
}
const state = getVerificationState(token) || initVerificationState(token);
const now = Date.now();
// Check if we're within the attempt window
if (state.lastAttempt > 0 && (now - state.lastAttempt) > ATTEMPT_WINDOW) {
console.log('[verification-state] Attempt window expired, resetting counter');
state.attempts = 0;
state.errors = [];
}
state.attempts++;
state.lastAttempt = now;
if (success) {
state.status = 'success';
console.log('[verification-state] Verification successful, clearing state');
// Don't clear immediately - let the navigation complete first
setTimeout(() => clearVerificationState(token), 1000);
} else {
if (error) {
state.errors.push(error);
}
if (state.attempts >= state.maxAttempts) {
state.status = 'blocked';
console.log(`[verification-state] Maximum attempts (${state.maxAttempts}) reached, blocking further attempts`);
} else {
state.status = 'failed';
console.log(`[verification-state] Attempt ${state.attempts}/${state.maxAttempts} failed`);
}
}
try {
sessionStorage.setItem(`${STORAGE_KEY}_${token.substring(0, 10)}`, JSON.stringify(state));
} catch (error) {
console.error('[verification-state] Failed to update state:', error);
}
return state;
}
/**
* Check if verification should be blocked
*/
export function shouldBlockVerification(token: string): boolean {
if (typeof window === 'undefined' || !token) return false;
const state = getVerificationState(token);
if (!state) return false;
if (state.status === 'blocked') {
const timeRemaining = CIRCUIT_BREAKER_TIMEOUT - (Date.now() - state.lastAttempt);
if (timeRemaining > 0) {
console.log(`[verification-state] Verification blocked for ${Math.ceil(timeRemaining / 1000 / 60)} more minutes`);
return true;
}
}
return state.status === 'success' || (state.attempts >= state.maxAttempts && state.status !== 'pending');
}
/**
* Clear verification state for a token
*/
export function clearVerificationState(token: string): void {
if (typeof window === 'undefined' || !token) return;
try {
sessionStorage.removeItem(`${STORAGE_KEY}_${token.substring(0, 10)}`);
console.log('[verification-state] Cleared verification state');
} catch (error) {
console.warn('[verification-state] Failed to clear state:', error);
}
}
/**
* Get user-friendly status message
*/
export function getStatusMessage(state: VerificationAttempt | null): string {
if (!state) return '';
switch (state.status) {
case 'pending':
return '';
case 'success':
return 'Email verified successfully!';
case 'failed':
if (state.attempts === 1) {
return 'Verification failed. Retrying...';
}
return `Verification failed (${state.attempts}/${state.maxAttempts} attempts). ${state.maxAttempts - state.attempts} attempts remaining.`;
case 'blocked':
const timeRemaining = Math.ceil((CIRCUIT_BREAKER_TIMEOUT - (Date.now() - state.lastAttempt)) / 1000 / 60);
return `Too many failed attempts. Please wait ${timeRemaining} minutes before trying again, or contact support.`;
default:
return '';
}
}
/**
* Progressive navigation with fallbacks for mobile browsers
*/
export async function navigateWithFallback(url: string, options: { replace?: boolean } = {}): Promise<boolean> {
if (typeof window === 'undefined') return false;
console.log(`[verification-state] Attempting navigation to: ${url}`);
try {
// Method 1: Use Nuxt navigateTo
if (typeof navigateTo === 'function') {
console.log('[verification-state] Using navigateTo');
await navigateTo(url, options);
return true;
}
} catch (error) {
console.warn('[verification-state] navigateTo failed:', error);
}
try {
// Method 2: Use Vue Router (if available)
const nuxtApp = (window as any)?.$nuxt;
if (nuxtApp?.$router) {
console.log('[verification-state] Using Vue Router');
if (options.replace) {
await nuxtApp.$router.replace(url);
} else {
await nuxtApp.$router.push(url);
}
return true;
}
} catch (error) {
console.warn('[verification-state] Vue Router failed:', error);
}
// Method 3: Direct window.location (mobile fallback)
console.log('[verification-state] Using window.location fallback');
if (options.replace) {
window.location.replace(url);
} else {
window.location.href = url;
}
return true;
}
/**
* Mobile-specific delay before navigation to ensure stability
*/
export function getMobileNavigationDelay(): number {
if (typeof window === 'undefined') return 0;
// Detect mobile browsers
const isMobile = /Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
const isSafari = /Safari/i.test(navigator.userAgent) && !/Chrome/i.test(navigator.userAgent);
if (isMobile && isSafari) {
return 500; // Extra delay for Safari on iOS
} else if (isMobile) {
return 300; // Standard mobile delay
}
return 100; // Minimal delay for desktop
}
/**
* Clean up all expired verification states
*/
export function cleanupExpiredStates(): void {
if (typeof window === 'undefined') return;
try {
const now = Date.now();
const keysToRemove: string[] = [];
for (let i = 0; i < sessionStorage.length; i++) {
const key = sessionStorage.key(i);
if (!key?.startsWith(STORAGE_KEY)) continue;
try {
const stored = sessionStorage.getItem(key);
if (!stored) continue;
const state = JSON.parse(stored) as VerificationAttempt;
// Remove states older than circuit breaker timeout
if ((now - state.lastAttempt) > CIRCUIT_BREAKER_TIMEOUT) {
keysToRemove.push(key);
}
} catch (error) {
// Remove invalid stored data
keysToRemove.push(key);
}
}
keysToRemove.forEach(key => {
sessionStorage.removeItem(key);
});
if (keysToRemove.length > 0) {
console.log(`[verification-state] Cleaned up ${keysToRemove.length} expired verification states`);
}
} catch (error) {
console.warn('[verification-state] Failed to cleanup expired states:', error);
}
}
// Auto-cleanup on page load
if (typeof window !== 'undefined') {
// Clean up immediately
cleanupExpiredStates();
// Clean up periodically
setInterval(cleanupExpiredStates, 5 * 60 * 1000); // Every 5 minutes
}

View File

@@ -1,142 +0,0 @@
/**
* CSS-Only Viewport Management System
* Handles mobile Safari viewport height changes through CSS custom properties only,
* without triggering any Vue component reactivity.
*/
class ViewportManager {
private static instance: ViewportManager;
private initialized = false;
private resizeTimeout: NodeJS.Timeout | null = null;
static getInstance(): ViewportManager {
if (!ViewportManager.instance) {
ViewportManager.instance = new ViewportManager();
}
return ViewportManager.instance;
}
init() {
if (this.initialized || typeof window === 'undefined') return;
console.log('[ViewportManager] Initializing CSS-only viewport management');
// Static device detection (no reactive dependencies)
const userAgent = navigator.userAgent;
const isIOS = /iPad|iPhone|iPod/.test(userAgent) && !(window as any).MSStream;
const isSafari = /^((?!chrome|android).)*safari/i.test(userAgent);
const isMobileSafari = isIOS && isSafari;
// Only apply to mobile Safari where viewport issues occur
if (!isMobileSafari) {
console.log('[ViewportManager] Not mobile Safari, skipping viewport management');
return;
}
let lastHeight = window.innerHeight;
let initialHeight = window.innerHeight;
let keyboardOpen = false;
const handleResize = () => {
// Skip if document is hidden (tab not active)
if (document.hidden) return;
// Clear any existing timeout
if (this.resizeTimeout) {
clearTimeout(this.resizeTimeout);
}
// Debounce with longer delay for mobile Safari
this.resizeTimeout = setTimeout(() => {
const newHeight = window.innerHeight;
const heightDiff = newHeight - lastHeight;
const absoluteDiff = Math.abs(heightDiff);
// Detect keyboard open/close patterns
if (heightDiff < -100 && newHeight < initialHeight * 0.75) {
keyboardOpen = true;
console.log('[ViewportManager] Keyboard opened, skipping update');
return;
}
if (heightDiff > 100 && keyboardOpen) {
keyboardOpen = false;
console.log('[ViewportManager] Keyboard closed, skipping update');
return;
}
// Only update for significant non-keyboard changes
const isOrientationChange = absoluteDiff > initialHeight * 0.3;
const isSignificantChange = absoluteDiff > 50;
if (isOrientationChange || (isSignificantChange && !keyboardOpen)) {
lastHeight = newHeight;
// Update CSS custom property only - no Vue reactivity
const vh = newHeight * 0.01;
document.documentElement.style.setProperty('--vh', `${vh}px`);
console.log('[ViewportManager] Updated --vh to:', `${vh}px`);
// Update initial height after orientation change
if (isOrientationChange) {
initialHeight = newHeight;
console.log('[ViewportManager] Orientation change detected, updated initial height');
}
}
}, 300); // Longer debounce for mobile Safari
};
// Set initial CSS custom property
const initialVh = initialHeight * 0.01;
document.documentElement.style.setProperty('--vh', `${initialVh}px`);
console.log('[ViewportManager] Set initial --vh to:', `${initialVh}px`);
// Add resize listener with passive option for better performance
window.addEventListener('resize', handleResize, { passive: true });
// Also listen for orientation changes on mobile
window.addEventListener('orientationchange', () => {
keyboardOpen = false; // Reset keyboard state on orientation change
console.log('[ViewportManager] Orientation change event, scheduling resize handler');
// Wait for orientation change to complete
setTimeout(handleResize, 200);
});
// Add visibility change listener to pause updates when tab is hidden
document.addEventListener('visibilitychange', () => {
if (document.hidden && this.resizeTimeout) {
clearTimeout(this.resizeTimeout);
this.resizeTimeout = null;
}
});
this.initialized = true;
console.log('[ViewportManager] Initialization complete');
}
cleanup() {
if (this.resizeTimeout) {
clearTimeout(this.resizeTimeout);
this.resizeTimeout = null;
}
this.initialized = false;
console.log('[ViewportManager] Cleanup complete');
}
}
// Export singleton instance
export const viewportManager = ViewportManager.getInstance();
// Auto-initialize on client side
if (typeof window !== 'undefined') {
// Initialize after DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
viewportManager.init();
});
} else {
// DOM is already ready
viewportManager.init();
}
}