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:
1 parent
e641138887
commit
55eebde0ba
55 files changed
+7353
-7
No files matched your search
+231
-6
@@ -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>;
|
||||
Reference in new issue
Block a user