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.
This commit is contained in:
Evandro Fonseca Junior committed 2025-11-04 02:33:29 -03:00
1 parent e641138887
commit 55eebde0ba
55 files changed
+7353 -7

No files matched your search

+231 -6
View File
@@ -18,17 +18,30 @@ export const emailSchema = z
})
.transform((email) => email.trim().toLowerCase());
// Schema para telefone brasileiro
// Schema para telefone (aceita brasileiro e internacional)
export const phoneSchema = z
.string()
.min(1, "Telefone é obrigatório")
.refine((phone) => {
// Remove todos os caracteres não numéricos
// Remove todos os caracteres não numéricos (incluindo +)
// Aceita formato internacional com código de país (ex: +55119988887777)
const cleaned = phone.replace(/[^\d]/g, "");
// Deve ter 10 ou 11 dígitos (telefone fixo ou celular com DDD)
return cleaned.length >= 10 && cleaned.length <= 11;
}, "Telefone deve ter 10 ou 11 dígitos (incluindo DDD)")
.transform((phone) => phone.replace(/[^\d]/g, ""));
// Aceita telefones de 8 a 15 dígitos (padrão internacional E.164)
// O código de país pode ter 1-3 dígitos, então o número total pode ser maior
// Mas para validação, aceitamos de 10 a 16 dígitos (código + número)
return cleaned.length >= 10 && cleaned.length <= 16;
}, "Telefone deve ter entre 10 e 16 dígitos (incluindo código de país)")
.transform((phone) => {
// Remove todos os caracteres não numéricos, mantendo apenas dígitos
// O formato final será: código de país + número (ex: 55119988887777)
return phone.replace(/[^\d]/g, "");
});
// Schema para telefone com código de país (para registro)
export const phoneWithCountrySchema = z.object({
dialCode: z.string().min(1, "Código de país é obrigatório"),
phoneNumber: phoneSchema,
});
// Schema para CPF/CNPJ
export const documentSchema = z
@@ -136,7 +149,219 @@ export const verifyBillingSchema = z.object({
billingId: z.string().min(1, "ID da cobrança é obrigatório"),
});
// ============================================
// SCHEMAS DE AUTENTICAÇÃO E USUÁRIOS
// ============================================
// Schema para username (alfanumérico + underscore)
export const usernameSchema = z
.string()
.min(3, "Username deve ter no mínimo 3 caracteres")
.max(50, "Username deve ter no máximo 50 caracteres")
.regex(
/^[a-zA-Z0-9_]+$/,
"Username deve conter apenas letras, números e underscore"
)
.transform((username) => username.toLowerCase().trim());
// Schema para senha (complexidade mínima)
export const passwordSchema = z
.string()
.min(8, "Senha deve ter no mínimo 8 caracteres")
.refine(
(password) => {
// Pelo menos 1 maiúscula
if (!/[A-Z]/.test(password)) return false;
// Pelo menos 1 minúscula
if (!/[a-z]/.test(password)) return false;
// Pelo menos 1 número
if (!/[0-9]/.test(password)) return false;
return true;
},
{
message:
"Senha deve conter pelo menos 1 letra maiúscula, 1 letra minúscula e 1 número",
}
);
// Schema para servidor/personagem no registro
export const registerServerCharacterSchema = z.object({
serverName: z
.string()
.min(1, "Nome do servidor é obrigatório")
.max(50, "Nome do servidor deve ter no máximo 50 caracteres")
.transform((name) => name.trim()),
characterName: z
.string()
.min(3, "Nome do personagem deve ter no mínimo 3 caracteres")
.max(100, "Nome do personagem deve ter no máximo 100 caracteres")
.transform((name) => name.trim()),
isMain: z.boolean().optional().default(false),
});
// Schema para registro de usuário
export const registerSchema = z.object({
username: usernameSchema,
email: emailSchema,
password: passwordSchema,
firstName: nameSchema,
lastName: nameSchema,
phone: phoneSchema.optional(),
servers: z
.array(registerServerCharacterSchema)
.min(1, "Selecione pelo menos um servidor"),
});
// Schema para login (aceita email ou username)
export const loginSchema = z.object({
emailOrUsername: z
.string()
.min(1, "Email ou username é obrigatório")
.transform((val) => val.trim().toLowerCase()),
password: z.string().min(1, "Senha é obrigatória"),
});
// Schema para atualização de perfil
export const updateUserSchema = z.object({
firstName: nameSchema.optional(),
lastName: nameSchema.optional(),
phone: phoneSchema.optional(),
whatsappNotifications: z.boolean().optional(),
});
// Schema para personagem
export const characterSchema = z.object({
characterName: z
.string()
.min(3, "Nome do personagem deve ter no mínimo 3 caracteres")
.max(100, "Nome do personagem deve ter no máximo 100 caracteres")
.transform((name) => name.trim()),
serverName: z
.string()
.min(1, "Nome do servidor é obrigatório")
.max(50, "Nome do servidor deve ter no máximo 50 caracteres")
.transform((name) => name.trim()),
isMain: z.boolean().optional().default(false),
});
// ============================================
// SCHEMAS DE MARKETPLACE (LISTINGS E BIDS)
// ============================================
// Schema para criar anúncio (listing)
export const createListingSchema = z.object({
item_id: z.number().int().positive("ID do item deve ser positivo"),
item_name: z
.string()
.min(1, "Nome do item é obrigatório")
.max(255, "Nome do item deve ter no máximo 255 caracteres")
.transform((name) => name.trim()),
quantity: z
.number()
.int()
.positive("Quantidade deve ser maior que zero")
.default(1),
server_name: z
.string()
.min(1, "Nome do servidor é obrigatório")
.max(50, "Nome do servidor deve ter no máximo 50 caracteres")
.transform((name) => name.trim()),
price_pix: z
.number()
.positive("Preço PIX deve ser positivo")
.optional()
.nullable(),
price_tibia_coins: z
.number()
.int()
.min(0, "Preço em Tibia Coins deve ser não negativo")
.optional()
.nullable(),
price_gold_coins: z
.number()
.int()
.min(0, "Preço em Gold Coins deve ser não negativo")
.optional()
.nullable(),
is_auction: z.boolean().optional().default(false),
auction_ends_at: z
.string()
.datetime("Data de término do leilão inválida")
.optional()
.nullable()
.transform((val) => (val ? new Date(val) : null)),
description: z
.string()
.max(5000, "Descrição deve ter no máximo 5000 caracteres")
.optional()
.nullable()
.transform((val) => val?.trim() || null),
}).refine(
(data) => {
// Pelo menos um preço deve ser fornecido
return (
data.price_pix !== null ||
data.price_tibia_coins !== null ||
data.price_gold_coins !== null
);
},
{
message: "Pelo menos um método de pagamento (PIX, Tibia Coins ou Gold Coins) deve ser especificado",
path: ["price_pix"],
}
).refine(
(data) => {
// Se for leilão, deve ter data de término
if (data.is_auction && !data.auction_ends_at) {
return false;
}
return true;
},
{
message: "Data de término do leilão é obrigatória quando is_auction é true",
path: ["auction_ends_at"],
}
);
// Schema para atualizar anúncio
export const updateListingSchema = z.object({
quantity: z.number().int().positive().optional(),
price_pix: z.number().positive().optional().nullable(),
price_tibia_coins: z.number().int().min(0).optional().nullable(),
price_gold_coins: z.number().int().min(0).optional().nullable(),
description: z.string().max(5000).optional().nullable(),
listing_status: z
.enum(["active", "sold", "cancelled", "in_escrow", "expired"])
.optional(),
}).refine(
(data) => {
// Se atualizando preços, pelo menos um deve ser fornecido ou manter existentes
const hasPrice =
data.price_pix !== undefined ||
data.price_tibia_coins !== undefined ||
data.price_gold_coins !== undefined;
return true; // Permite atualizar sem especificar preços (mantém existentes)
}
);
// Schema para criar lance (bid)
export const bidSchema = z.object({
listing_id: z.number().int().positive("ID do anúncio deve ser positivo"),
bid_amount: z.number().positive("Valor do lance deve ser positivo"),
bid_type: z
.enum(["pix", "tibia_coins", "gold_coins"])
.optional()
.nullable(),
});
// Type exports para uso no código
export type CreateDonationInput = z.infer<typeof createDonationSchema>;
export type VerifyStatusInput = z.infer<typeof verifyStatusSchema>;
export type VerifyBillingInput = z.infer<typeof verifyBillingSchema>;
export type RegisterInput = z.infer<typeof registerSchema>;
export type LoginInput = z.infer<typeof loginSchema>;
export type UpdateUserInput = z.infer<typeof updateUserSchema>;
export type CharacterInput = z.infer<typeof characterSchema>;
export type CreateListingInput = z.infer<typeof createListingSchema>;
export type UpdateListingInput = z.infer<typeof updateListingSchema>;
export type BidInput = z.infer<typeof bidSchema>;