494 lines
16 KiB
TypeScript
494 lines
16 KiB
TypeScript
/**
|
|
* 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<string, string> = {
|
|
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);
|
|
}
|
|
}
|