Files
pagodera/lib/utils/formatters.ts
T
Evandro Fonseca Junior 55eebde0ba feat: add user authentication UI to header component
Add login and register dialogs, user dropdown menu with profile link and logout option, and integrate with session management hook. Prevent hydration mismatches by conditionally rendering auth elements after mount. Update package-lock.json with new @esbuild dependencies for build tooling.
2025-11-04 02:33:29 -03:00

449 lines
15 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,
};
}