/** * Funções utilitárias para formatação de dados */ /** * Formata número de telefone brasileiro * Exemplo: 11988887777 -> (11) 98888-7777 */ export function formatPhone(value: string): string { const cleaned = value.replace(/\D/g, ""); if (cleaned.length <= 2) { return cleaned; } if (cleaned.length <= 6) { return `(${cleaned.slice(0, 2)}) ${cleaned.slice(2)}`; } if (cleaned.length <= 10) { return `(${cleaned.slice(0, 2)}) ${cleaned.slice(2, 6)}-${cleaned.slice(6)}`; } // Telefone com 11 dígitos (celular) return `(${cleaned.slice(0, 2)}) ${cleaned.slice(2, 7)}-${cleaned.slice(7, 11)}`; } /** * Formata número de telefone específico por país * @param phoneNumber - Número do telefone (apenas dígitos, sem código de país) * @param countryCode - Código ISO do país (ex: "BR", "US", "GB") */ function formatPhoneByCountry( phoneNumber: string, countryCode: string, ): string { const len = phoneNumber.length; switch (countryCode) { // Brasil: (XX) XXXXX-XXXX ou (XX) XXXX-XXXX case "BR": if (len <= 2) return phoneNumber; if (len <= 6) return `(${phoneNumber.slice(0, 2)}) ${phoneNumber.slice(2)}`; if (len <= 10) return `(${phoneNumber.slice(0, 2)}) ${phoneNumber.slice(2, 6)}-${phoneNumber.slice(6)}`; return `(${phoneNumber.slice(0, 2)}) ${phoneNumber.slice(2, 7)}-${phoneNumber.slice(7, 11)}`; // EUA/Canadá: (XXX) XXX-XXXX case "US": case "CA": if (len <= 3) return phoneNumber; if (len <= 6) return `(${phoneNumber.slice(0, 3)}) ${phoneNumber.slice(3)}`; return `(${phoneNumber.slice(0, 3)}) ${phoneNumber.slice(3, 6)}-${phoneNumber.slice(6, 10)}`; // México: (XX) XXXX XXXX case "MX": if (len <= 2) return phoneNumber; if (len <= 6) return `(${phoneNumber.slice(0, 2)}) ${phoneNumber.slice(2)}`; return `(${phoneNumber.slice(0, 2)}) ${phoneNumber.slice(2, 6)} ${phoneNumber.slice(6, 10)}`; // Argentina: (XX) XXXX-XXXX case "AR": if (len <= 2) return phoneNumber; if (len <= 6) return `(${phoneNumber.slice(0, 2)}) ${phoneNumber.slice(2)}`; return `(${phoneNumber.slice(0, 2)}) ${phoneNumber.slice(2, 6)}-${phoneNumber.slice(6, 10)}`; // Chile: X XXXX XXXX case "CL": if (len <= 1) return phoneNumber; if (len <= 5) return `${phoneNumber.slice(0, 1)} ${phoneNumber.slice(1)}`; return `${phoneNumber.slice(0, 1)} ${phoneNumber.slice(1, 5)} ${phoneNumber.slice(5, 9)}`; // Colômbia: XXX XXX XXXX case "CO": if (len <= 3) return phoneNumber; if (len <= 6) return `${phoneNumber.slice(0, 3)} ${phoneNumber.slice(3)}`; return `${phoneNumber.slice(0, 3)} ${phoneNumber.slice(3, 6)} ${phoneNumber.slice(6, 10)}`; // Peru: XXX XXX XXX case "PE": if (len <= 3) return phoneNumber; if (len <= 6) return `${phoneNumber.slice(0, 3)} ${phoneNumber.slice(3)}`; return `${phoneNumber.slice(0, 3)} ${phoneNumber.slice(3, 6)} ${phoneNumber.slice(6, 9)}`; // Portugal: XXX XXX XXX case "PT": if (len <= 3) return phoneNumber; if (len <= 6) return `${phoneNumber.slice(0, 3)} ${phoneNumber.slice(3)}`; return `${phoneNumber.slice(0, 3)} ${phoneNumber.slice(3, 6)} ${phoneNumber.slice(6, 9)}`; // Espanha: XXX XXX XXX case "ES": if (len <= 3) return phoneNumber; if (len <= 6) return `${phoneNumber.slice(0, 3)} ${phoneNumber.slice(3)}`; return `${phoneNumber.slice(0, 3)} ${phoneNumber.slice(3, 6)} ${phoneNumber.slice(6, 9)}`; // Reino Unido: XXXX XXX XXXX case "GB": if (len <= 4) return phoneNumber; if (len <= 7) return `${phoneNumber.slice(0, 4)} ${phoneNumber.slice(4)}`; return `${phoneNumber.slice(0, 4)} ${phoneNumber.slice(4, 7)} ${phoneNumber.slice(7, 11)}`; // França: XX XX XX XX XX case "FR": { if (len <= 2) return phoneNumber; const groups: string[] = []; for (let i = 0; i < len; i += 2) { if (i + 2 <= len) { groups.push(phoneNumber.slice(i, i + 2)); } else { groups.push(phoneNumber.slice(i)); } } return groups.join(" "); } // Alemanha: XXXX XXXXXXX case "DE": if (len <= 4) return phoneNumber; return `${phoneNumber.slice(0, 4)} ${phoneNumber.slice(4)}`; // Itália: XXX XXX XXXX case "IT": if (len <= 3) return phoneNumber; if (len <= 6) return `${phoneNumber.slice(0, 3)} ${phoneNumber.slice(3)}`; return `${phoneNumber.slice(0, 3)} ${phoneNumber.slice(3, 6)} ${phoneNumber.slice(6, 10)}`; // Holanda: XX XXXXXXXX case "NL": if (len <= 2) return phoneNumber; return `${phoneNumber.slice(0, 2)} ${phoneNumber.slice(2)}`; // Bélgica: XXXX XX XX XX case "BE": if (len <= 4) return phoneNumber; if (len <= 6) return `${phoneNumber.slice(0, 4)} ${phoneNumber.slice(4)}`; if (len <= 8) return `${phoneNumber.slice(0, 4)} ${phoneNumber.slice(4, 6)} ${phoneNumber.slice(6)}`; return `${phoneNumber.slice(0, 4)} ${phoneNumber.slice(4, 6)} ${phoneNumber.slice(6, 8)} ${phoneNumber.slice(8)}`; // Suíça: XX XXX XX XX case "CH": if (len <= 2) return phoneNumber; if (len <= 5) return `${phoneNumber.slice(0, 2)} ${phoneNumber.slice(2)}`; if (len <= 7) return `${phoneNumber.slice(0, 2)} ${phoneNumber.slice(2, 5)} ${phoneNumber.slice(5)}`; return `${phoneNumber.slice(0, 2)} ${phoneNumber.slice(2, 5)} ${phoneNumber.slice(5, 7)} ${phoneNumber.slice(7)}`; // Áustria: XXXX XXXX case "AT": if (len <= 4) return phoneNumber; return `${phoneNumber.slice(0, 4)} ${phoneNumber.slice(4, 8)}`; // Polônia: XXX XXX XXX case "PL": if (len <= 3) return phoneNumber; if (len <= 6) return `${phoneNumber.slice(0, 3)} ${phoneNumber.slice(3)}`; return `${phoneNumber.slice(0, 3)} ${phoneNumber.slice(3, 6)} ${phoneNumber.slice(6, 9)}`; // Rússia: XXX XXX-XX-XX case "RU": if (len <= 3) return phoneNumber; if (len <= 6) return `${phoneNumber.slice(0, 3)} ${phoneNumber.slice(3)}`; if (len <= 8) return `${phoneNumber.slice(0, 3)} ${phoneNumber.slice(3, 6)}-${phoneNumber.slice(6)}`; return `${phoneNumber.slice(0, 3)} ${phoneNumber.slice(3, 6)}-${phoneNumber.slice(6, 8)}-${phoneNumber.slice(8)}`; // China: XXX XXXX XXXX case "CN": if (len <= 3) return phoneNumber; if (len <= 7) return `${phoneNumber.slice(0, 3)} ${phoneNumber.slice(3)}`; return `${phoneNumber.slice(0, 3)} ${phoneNumber.slice(3, 7)} ${phoneNumber.slice(7, 11)}`; // Japão: XX-XXXX-XXXX case "JP": if (len <= 2) return phoneNumber; if (len <= 6) return `${phoneNumber.slice(0, 2)}-${phoneNumber.slice(2)}`; return `${phoneNumber.slice(0, 2)}-${phoneNumber.slice(2, 6)}-${phoneNumber.slice(6, 10)}`; // Coreia do Sul: XXX-XXXX-XXXX case "KR": if (len <= 3) return phoneNumber; if (len <= 7) return `${phoneNumber.slice(0, 3)}-${phoneNumber.slice(3)}`; return `${phoneNumber.slice(0, 3)}-${phoneNumber.slice(3, 7)}-${phoneNumber.slice(7, 11)}`; // Índia: XXXX XXX XXX case "IN": if (len <= 4) return phoneNumber; if (len <= 7) return `${phoneNumber.slice(0, 4)} ${phoneNumber.slice(4)}`; return `${phoneNumber.slice(0, 4)} ${phoneNumber.slice(4, 7)} ${phoneNumber.slice(7, 10)}`; // Austrália: XXXX XXX XXX case "AU": if (len <= 4) return phoneNumber; if (len <= 7) return `${phoneNumber.slice(0, 4)} ${phoneNumber.slice(4)}`; return `${phoneNumber.slice(0, 4)} ${phoneNumber.slice(4, 7)} ${phoneNumber.slice(7, 10)}`; // Nova Zelândia: XXXX XXX XXX case "NZ": if (len <= 4) return phoneNumber; if (len <= 7) return `${phoneNumber.slice(0, 4)} ${phoneNumber.slice(4)}`; return `${phoneNumber.slice(0, 4)} ${phoneNumber.slice(4, 7)} ${phoneNumber.slice(7, 10)}`; // África do Sul: XX XXX XXXX case "ZA": if (len <= 2) return phoneNumber; if (len <= 5) return `${phoneNumber.slice(0, 2)} ${phoneNumber.slice(2)}`; return `${phoneNumber.slice(0, 2)} ${phoneNumber.slice(2, 5)} ${phoneNumber.slice(5, 9)}`; // Turquia: XXX XXX XX XX case "TR": if (len <= 3) return phoneNumber; if (len <= 6) return `${phoneNumber.slice(0, 3)} ${phoneNumber.slice(3)}`; if (len <= 8) return `${phoneNumber.slice(0, 3)} ${phoneNumber.slice(3, 6)} ${phoneNumber.slice(6)}`; return `${phoneNumber.slice(0, 3)} ${phoneNumber.slice(3, 6)} ${phoneNumber.slice(6, 8)} ${phoneNumber.slice(8)}`; // Arábia Saudita: XX XXX XXXX case "SA": if (len <= 2) return phoneNumber; if (len <= 5) return `${phoneNumber.slice(0, 2)} ${phoneNumber.slice(2)}`; return `${phoneNumber.slice(0, 2)} ${phoneNumber.slice(2, 5)} ${phoneNumber.slice(5, 9)}`; // Emirados Árabes: XX XXX XXXX case "AE": if (len <= 2) return phoneNumber; if (len <= 5) return `${phoneNumber.slice(0, 2)} ${phoneNumber.slice(2)}`; return `${phoneNumber.slice(0, 2)} ${phoneNumber.slice(2, 5)} ${phoneNumber.slice(5, 9)}`; // Israel: XX-XXX-XXXX case "IL": if (len <= 2) return phoneNumber; if (len <= 5) return `${phoneNumber.slice(0, 2)}-${phoneNumber.slice(2)}`; return `${phoneNumber.slice(0, 2)}-${phoneNumber.slice(2, 5)}-${phoneNumber.slice(5, 9)}`; // Uruguai: XXXX XXXX case "UY": if (len <= 4) return phoneNumber; return `${phoneNumber.slice(0, 4)} ${phoneNumber.slice(4, 8)}`; // Paraguai: XXX XXX XXX case "PY": if (len <= 3) return phoneNumber; if (len <= 6) return `${phoneNumber.slice(0, 3)} ${phoneNumber.slice(3)}`; return `${phoneNumber.slice(0, 3)} ${phoneNumber.slice(3, 6)} ${phoneNumber.slice(6, 9)}`; // Bolívia: XXXX XXXX case "BO": if (len <= 4) return phoneNumber; return `${phoneNumber.slice(0, 4)} ${phoneNumber.slice(4, 8)}`; // Venezuela: XXXX-XXXXXXX case "VE": if (len <= 4) return phoneNumber; return `${phoneNumber.slice(0, 4)}-${phoneNumber.slice(4)}`; // Equador: XX XXX XXXX case "EC": if (len <= 2) return phoneNumber; if (len <= 5) return `${phoneNumber.slice(0, 2)} ${phoneNumber.slice(2)}`; return `${phoneNumber.slice(0, 2)} ${phoneNumber.slice(2, 5)} ${phoneNumber.slice(5, 9)}`; // Egito: XX XXXX XXXX case "EG": if (len <= 2) return phoneNumber; if (len <= 6) return `${phoneNumber.slice(0, 2)} ${phoneNumber.slice(2)}`; return `${phoneNumber.slice(0, 2)} ${phoneNumber.slice(2, 6)} ${phoneNumber.slice(6, 10)}`; // Formatação genérica padrão (espaços a cada 3-4 dígitos) default: { if (len <= 4) return phoneNumber; if (len <= 7) return `${phoneNumber.slice(0, 3)} ${phoneNumber.slice(3)}`; if (len <= 10) return `${phoneNumber.slice(0, 3)} ${phoneNumber.slice(3, 6)} ${phoneNumber.slice(6)}`; // Para números maiores, agrupa em grupos de 3-4 dígitos const groups: string[] = []; let remaining = phoneNumber; while (remaining.length > 4) { groups.push(remaining.slice(0, 3)); remaining = remaining.slice(3); } if (remaining.length > 0) { groups.push(remaining); } return groups.join(" "); } } } /** * Gera um placeholder de telefone baseado no formato do país * @param countryCode - Código ISO do país (ex: "BR", "US", "GB") * @returns Placeholder formatado (ex: "(11) 98888-7777") */ export function getPhonePlaceholder(countryCode: string): string { // Números exemplo para cada país (apenas para mostrar o formato) const examples: Record = { BR: "(11) 98888-7777", US: "(415) 555-1234", CA: "(416) 555-1234", MX: "(55) 1234 5678", AR: "(11) 1234-5678", CL: "9 1234 5678", CO: "300 123 4567", PE: "987 654 321", PT: "912 345 678", ES: "612 345 678", GB: "7123 456 789", FR: "61 23 45 67 89", DE: "3012 3456789", IT: "321 234 5678", NL: "61 23456789", BE: "1234 56 78 90", CH: "21 321 45 67", AT: "1234 5678", PL: "123 456 789", RU: "495 123-45-67", CN: "138 1234 5678", JP: "90-1234-5678", KR: "010-1234-5678", IN: "9876 543 210", AU: "0412 345 678", NZ: "021 234 5678", ZA: "82 123 4567", TR: "532 123 45 67", SA: "50 123 4567", AE: "50 123 4567", IL: "50-123-4567", UY: "1234 5678", PY: "981 234 567", BO: "7123 4567", VE: "2126-1234567", EC: "99 123 4567", EG: "10 1234 5678", }; return examples[countryCode] || "123 456 7890"; } /** * Formata número de telefone internacional com código de país * @param value - Número do telefone (com ou sem código de país) * @param dialCode - Código de discagem internacional (ex: "+55") * @param countryCode - Código ISO do país (ex: "BR") */ export function formatInternationalPhone( value: string, dialCode: string, countryCode?: string, ): string { const cleaned = value.replace(/\D/g, ""); // Remove o código de país se já estiver presente let phoneNumber = cleaned; const dialCodeDigits = dialCode.replace(/\D/g, ""); if (phoneNumber.startsWith(dialCodeDigits)) { phoneNumber = phoneNumber.slice(dialCodeDigits.length); } // Se não temos código de país, usar formatação genérica if (!countryCode) { return formatPhoneByCountry(phoneNumber, "DEFAULT"); } // Aplicar formatação específica do país return formatPhoneByCountry(phoneNumber, countryCode); } /** * Remove formatação do telefone * Exemplo: (11) 98888-7777 -> 11988887777 */ export function unformatPhone(value: string): string { return value.replace(/\D/g, ""); } /** * Combina código de país com número de telefone * @param dialCode - Código de discagem (ex: "+55") * @param phoneNumber - Número do telefone (sem código) */ export function combinePhoneWithDialCode( dialCode: string, phoneNumber: string, ): string { const cleanedPhone = unformatPhone(phoneNumber); const cleanedDialCode = dialCode.replace(/\D/g, ""); // Remove código de país se já estiver presente if (cleanedPhone.startsWith(cleanedDialCode)) { return `${dialCode}${cleanedPhone.slice(cleanedDialCode.length)}`; } return `${dialCode}${cleanedPhone}`; } /** * Calcula a força da senha * Retorna: 'weak' | 'medium' | 'strong' */ export function checkPasswordStrength( password: string, ): "weak" | "medium" | "strong" { if (password.length < 8) { return "weak"; } let strength = 0; // Verificar requisitos básicos if (password.length >= 8) strength++; if (password.length >= 12) strength++; if (/[a-z]/.test(password)) strength++; if (/[A-Z]/.test(password)) strength++; if (/[0-9]/.test(password)) strength++; if (/[^a-zA-Z0-9]/.test(password)) strength++; // Caracteres especiais if (strength <= 3) { return "weak"; } else if (strength <= 5) { return "medium"; } else { return "strong"; } } /** * Valida se a senha atende aos requisitos mínimos */ export function validatePasswordRequirements(password: string): { isValid: boolean; errors: string[]; } { const errors: string[] = []; if (password.length < 8) { errors.push("Senha deve ter no mínimo 8 caracteres"); } if (!/[A-Z]/.test(password)) { errors.push("Senha deve conter pelo menos 1 letra maiúscula"); } if (!/[a-z]/.test(password)) { errors.push("Senha deve conter pelo menos 1 letra minúscula"); } if (!/[0-9]/.test(password)) { errors.push("Senha deve conter pelo menos 1 número"); } return { isValid: errors.length === 0, errors, }; } /** * Mapeia o locale da aplicação (ex: "pt", "en") para um BCP 47 válido * para APIs de formatação (ex: "pt-BR", "en-US"). */ export function resolveBCP47Locale(locale?: string): string { switch (locale) { case "pt": return "pt-BR"; case "en": return "en-US"; default: // Fallback consistente para evitar divergência SSR/cliente return "pt-BR"; } } /** * Formata números com locale consistente, evitando mismatch de hidratação * entre SSR e cliente. */ export function formatNumber(value: number, locale?: string): string { try { return value.toLocaleString(resolveBCP47Locale(locale)); } catch { return String(value); } }