import { STORAGE_KEYS } from './constants'; // Local Storage Helpers export const storage = { get: (key: string): string | null => { if (typeof window === 'undefined') return null; try { return localStorage.getItem(key); } catch (error) { console.error('Error reading from localStorage:', error); return null; } }, set: (key: string, value: string): void => { if (typeof window === 'undefined') return; try { localStorage.setItem(key, value); } catch (error) { console.error('Error writing to localStorage:', error); } }, remove: (key: string): void => { if (typeof window === 'undefined') return; try { localStorage.removeItem(key); } catch (error) { console.error('Error removing from localStorage:', error); } }, clear: (): void => { if (typeof window === 'undefined') return; try { localStorage.clear(); } catch (error) { console.error('Error clearing localStorage:', error); } }, }; // Cookie Helpers export const setCookie = (name: string, value: string, days: number = 7): void => { if (typeof window === 'undefined') return; const expires = new Date(); expires.setTime(expires.getTime() + days * 24 * 60 * 60 * 1000); document.cookie = `${name}=${value};expires=${expires.toUTCString()};path=/;SameSite=Strict`; }; export const getCookie = (name: string): string | null => { if (typeof window === 'undefined') return null; const nameEQ = name + '='; const ca = document.cookie.split(';'); for (let i = 0; i < ca.length; i++) { let c = ca[i]; while (c.charAt(0) === ' ') c = c.substring(1, c.length); if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length); } return null; }; export const removeCookie = (name: string): void => { if (typeof window === 'undefined') return; document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:01 GMT;path=/;SameSite=Strict`; }; // Token Helpers export const getToken = (): string | null => { // Try to get from localStorage first, then cookies return storage.get(STORAGE_KEYS.TOKEN) || getCookie(STORAGE_KEYS.TOKEN); }; export const setToken = (token: string): void => { // Store in both localStorage and cookies for better compatibility storage.set(STORAGE_KEYS.TOKEN, token); setCookie(STORAGE_KEYS.TOKEN, token, 7); // 7 days expiration }; export const removeToken = (): void => { // Remove from both localStorage and cookies storage.remove(STORAGE_KEYS.TOKEN); removeCookie(STORAGE_KEYS.TOKEN); }; // Date Formatting export const formatDate = (date: string | Date): string => { if (!date) return '-'; const d = new Date(date); if (isNaN(d.getTime())) return '-'; return new Intl.DateTimeFormat('fr-FR', { year: 'numeric', month: 'long', day: 'numeric', }).format(d); }; export const formatDateTime = (date: string | Date): string => { if (!date) return '-'; const d = new Date(date); if (isNaN(d.getTime())) return '-'; return new Intl.DateTimeFormat('fr-FR', { year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit', }).format(d); }; // Number Formatting export const formatCurrency = (amount: number): string => { return new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR', }).format(amount); }; export const formatPercentage = (value: number): string => { return new Intl.NumberFormat('fr-FR', { style: 'percent', minimumFractionDigits: 1, maximumFractionDigits: 1, }).format(value); }; // String Helpers export const truncate = (str: string, length: number): string => { if (str.length <= length) return str; return str.slice(0, length) + '...'; }; export const capitalize = (str: string): string => { return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase(); }; // Validation Helpers export const isValidEmail = (email: string): boolean => { // Limit input length to prevent ReDoS attacks if (!email || email.length > 254) return false; // Simple and safe email regex (non-backtracking) const emailRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/; return emailRegex.test(email); }; export const isValidPhone = (phone: string): boolean => { const phoneRegex = /^(\+33|0)[1-9](\d{2}){4}$/; return phoneRegex.test(phone); }; // Class Name Helper export const cn = (...classes: (string | boolean | undefined | null)[]): string => { return classes.filter(Boolean).join(' '); }; // Delay Helper export const delay = (ms: number): Promise => { return new Promise(resolve => setTimeout(resolve, ms)); }; // Deep Clone export const deepClone = (obj: T): T => { return JSON.parse(JSON.stringify(obj)); }; // Generate Random ID (cryptographically secure) export const generateId = (): string => { const timestamp = Date.now().toString(36); if (typeof window !== 'undefined' && window.crypto) { const array = new Uint32Array(2); window.crypto.getRandomValues(array); return array[0].toString(36) + array[1].toString(36) + timestamp; } // SSR fallback using Node.js crypto if (typeof globalThis !== 'undefined' && globalThis.crypto) { const array = new Uint32Array(2); globalThis.crypto.getRandomValues(array); return array[0].toString(36) + array[1].toString(36) + timestamp; } // Last resort: timestamp-based only (no Math.random) const counter = (Date.now() % 1000000).toString(36); return timestamp + counter + process.hrtime?.()[1]?.toString(36) || timestamp + counter; }; // Debounce Function export function debounce any>( func: T, wait: number ): (...args: Parameters) => void { let timeout: NodeJS.Timeout | null = null; return (...args: Parameters) => { if (timeout) clearTimeout(timeout); timeout = setTimeout(() => func(...args), wait); }; } // Throttle Function export function throttle any>( func: T, limit: number ): (...args: Parameters) => void { let inThrottle: boolean; return (...args: Parameters) => { if (!inThrottle) { func(...args); inThrottle = true; setTimeout(() => (inThrottle = false), limit); } }; }