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