refactor: reorganize hooks and add safety checks
fix(api): add validation for listing and seller in bid route feat(telemetry): add userId to MarketTelemetry style(globe): add null check for canvas element chore: add lint:fix script to package.json
This commit is contained in:
1 parent
47c04de6fe
commit
6adc8d96e8
12 files changed
+119
-89
No files matched your search
@@ -61,12 +61,32 @@ export async function POST(
|
||||
);
|
||||
}
|
||||
|
||||
// Garantir existência do anúncio e vendedor antes de notificar
|
||||
const listing = result.listing;
|
||||
if (!listing) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Anúncio não encontrado após registrar o lance" },
|
||||
{ status: 404 },
|
||||
);
|
||||
}
|
||||
|
||||
if (typeof listing.seller_id !== "number") {
|
||||
secureLogger.warn("SellerId inválido ao criar notificação de lance", {
|
||||
listingId,
|
||||
sellerId: listing.seller_id,
|
||||
});
|
||||
return NextResponse.json(
|
||||
{ success: false, error: "Vendedor inválido para notificação" },
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
|
||||
// Criar notificação para o vendedor
|
||||
await createNotification({
|
||||
user_id: result.listing?.seller_id,
|
||||
user_id: listing.seller_id,
|
||||
notification_type: "bid",
|
||||
title: "Novo lance!",
|
||||
message: `Novo lance de R$ ${amount.toFixed(2)} no item "${result.listing?.item_name}"`,
|
||||
message: `Novo lance de R$ ${amount.toFixed(2)} no item "${listing.item_name}"`,
|
||||
related_id: listingId,
|
||||
send_whatsapp: false,
|
||||
});
|
||||
@@ -77,7 +97,7 @@ export async function POST(
|
||||
user_id: result.previousBidderId,
|
||||
notification_type: "bid",
|
||||
title: "Lance superado",
|
||||
message: `Seu lance foi superado no item "${result.listing?.item_name}"`,
|
||||
message: `Seu lance foi superado no item "${listing.item_name}"`,
|
||||
related_id: listingId,
|
||||
send_whatsapp: false,
|
||||
});
|
||||
@@ -92,11 +112,11 @@ export async function POST(
|
||||
metadata: {
|
||||
bid_amount: amount,
|
||||
payment_method: payment_method,
|
||||
previous_bid: result.listing?.current_bid_amount
|
||||
? Number(result.listing?.current_bid_amount)
|
||||
previous_bid: listing.current_bid_amount
|
||||
? Number(listing.current_bid_amount)
|
||||
: null,
|
||||
min_increment: result.listing?.min_bid_increment
|
||||
? Number(result.listing?.min_bid_increment)
|
||||
min_increment: listing.min_bid_increment
|
||||
? Number(listing.min_bid_increment)
|
||||
: null,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -40,18 +40,6 @@ export function AnalyticsDashboard() {
|
||||
const [analytics, setAnalytics] = useState<AdAnalytics | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (user?.userId) {
|
||||
loadUserListings();
|
||||
}
|
||||
}, [user?.userId, loadUserListings]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedAd) {
|
||||
loadAnalytics(selectedAd);
|
||||
}
|
||||
}, [selectedAd, loadAnalytics]);
|
||||
|
||||
const loadUserListings = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
@@ -82,6 +70,18 @@ export function AnalyticsDashboard() {
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (user?.userId) {
|
||||
loadUserListings();
|
||||
}
|
||||
}, [user?.userId, loadUserListings]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedAd) {
|
||||
loadAnalytics(selectedAd);
|
||||
}
|
||||
}, [selectedAd, loadAnalytics]);
|
||||
|
||||
if (!user) {
|
||||
return (
|
||||
<div className="text-center py-12">
|
||||
|
||||
@@ -103,13 +103,6 @@ export function BidModal({
|
||||
}
|
||||
}, [minBid, bidAmount]);
|
||||
|
||||
// Buscar histórico de lances quando abrir modal
|
||||
useEffect(() => {
|
||||
if (open && listing) {
|
||||
fetchBidHistory();
|
||||
}
|
||||
}, [open, listing?.listing_id, fetchBidHistory, listing]);
|
||||
|
||||
const fetchBidHistory = async () => {
|
||||
if (!listing) return;
|
||||
|
||||
@@ -131,6 +124,13 @@ export function BidModal({
|
||||
}
|
||||
};
|
||||
|
||||
// Buscar histórico de lances quando abrir modal
|
||||
useEffect(() => {
|
||||
if (open && listing) {
|
||||
fetchBidHistory();
|
||||
}
|
||||
}, [open, listing?.listing_id, fetchBidHistory, listing]);
|
||||
|
||||
const handleBid = async () => {
|
||||
const amount = parseFloat(bidAmount);
|
||||
|
||||
|
||||
@@ -204,12 +204,6 @@ export function CreateAdModal({
|
||||
};
|
||||
|
||||
// Carregar servidores ao abrir o modal
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
loadServers();
|
||||
}
|
||||
}, [open, loadServers]);
|
||||
|
||||
const loadServers = async () => {
|
||||
setLoadingServers(true);
|
||||
try {
|
||||
@@ -225,6 +219,12 @@ export function CreateAdModal({
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
loadServers();
|
||||
}
|
||||
}, [open, loadServers]);
|
||||
|
||||
const handleClose = () => {
|
||||
if (!loading) {
|
||||
setSelectedItem(null);
|
||||
|
||||
@@ -135,10 +135,6 @@ export function MyAdsIntentionList({
|
||||
"all" | "pending" | "accepted" | "rejected"
|
||||
>("all");
|
||||
|
||||
useEffect(() => {
|
||||
loadIntentions();
|
||||
}, [loadIntentions]);
|
||||
|
||||
const loadIntentions = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
@@ -162,6 +158,10 @@ export function MyAdsIntentionList({
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadIntentions();
|
||||
}, [loadIntentions]);
|
||||
|
||||
const pendingCount = intentions.filter((i) => i.status === "pending").length;
|
||||
|
||||
if (loading) {
|
||||
|
||||
@@ -69,20 +69,6 @@ export function MessagesPanel({
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (open && user) {
|
||||
fetchChats();
|
||||
// Marcar todos como lidos ao abrir
|
||||
markAllAsRead();
|
||||
}
|
||||
}, [
|
||||
open,
|
||||
user,
|
||||
fetchChats, // Marcar todos como lidos ao abrir
|
||||
markAllAsRead,
|
||||
]);
|
||||
|
||||
const markAllAsRead = async () => {
|
||||
try {
|
||||
await fetch("/api/chats/mark-all-read", {
|
||||
@@ -105,6 +91,19 @@ export function MessagesPanel({
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (open && user) {
|
||||
fetchChats();
|
||||
// Marcar todos como lidos ao abrir
|
||||
markAllAsRead();
|
||||
}
|
||||
}, [
|
||||
open,
|
||||
user,
|
||||
fetchChats, // Marcar todos como lidos ao abrir
|
||||
markAllAsRead,
|
||||
]);
|
||||
|
||||
const handleChatClick = (chatId: string, _listingId: number) => {
|
||||
setSelectedChatId(chatId);
|
||||
setShowChatModal(true);
|
||||
|
||||
@@ -93,7 +93,12 @@ export function Globe({
|
||||
},
|
||||
});
|
||||
|
||||
setTimeout(() => (canvasRef.current?.style.opacity = "1"), 0);
|
||||
setTimeout(() => {
|
||||
const el = canvasRef.current;
|
||||
if (el) {
|
||||
el.style.opacity = "1";
|
||||
}
|
||||
}, 0);
|
||||
return () => {
|
||||
globe.destroy();
|
||||
window.removeEventListener("resize", onResize);
|
||||
|
||||
+39
-36
@@ -101,42 +101,7 @@ export const Particles: React.FC<ParticlesProps> = ({
|
||||
const rafID = useRef<number | null>(null);
|
||||
const resizeTimeout = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (canvasRef.current) {
|
||||
context.current = canvasRef.current.getContext("2d");
|
||||
}
|
||||
initCanvas();
|
||||
animate();
|
||||
|
||||
const handleResize = () => {
|
||||
if (resizeTimeout.current) {
|
||||
clearTimeout(resizeTimeout.current);
|
||||
}
|
||||
resizeTimeout.current = setTimeout(() => {
|
||||
initCanvas();
|
||||
}, 200);
|
||||
};
|
||||
|
||||
window.addEventListener("resize", handleResize);
|
||||
|
||||
return () => {
|
||||
if (rafID.current != null) {
|
||||
window.cancelAnimationFrame(rafID.current);
|
||||
}
|
||||
if (resizeTimeout.current) {
|
||||
clearTimeout(resizeTimeout.current);
|
||||
}
|
||||
window.removeEventListener("resize", handleResize);
|
||||
};
|
||||
}, [animate, initCanvas]);
|
||||
|
||||
useEffect(() => {
|
||||
onMouseMove();
|
||||
}, [onMouseMove]);
|
||||
|
||||
useEffect(() => {
|
||||
initCanvas();
|
||||
}, [initCanvas]);
|
||||
|
||||
|
||||
const initCanvas = () => {
|
||||
resizeCanvas();
|
||||
@@ -302,6 +267,44 @@ export const Particles: React.FC<ParticlesProps> = ({
|
||||
rafID.current = window.requestAnimationFrame(animate);
|
||||
};
|
||||
|
||||
// Efeitos devem vir após a declaração das funções referenciadas
|
||||
useEffect(() => {
|
||||
if (canvasRef.current) {
|
||||
context.current = canvasRef.current.getContext("2d");
|
||||
}
|
||||
initCanvas();
|
||||
animate();
|
||||
|
||||
const handleResize = () => {
|
||||
if (resizeTimeout.current) {
|
||||
clearTimeout(resizeTimeout.current);
|
||||
}
|
||||
resizeTimeout.current = setTimeout(() => {
|
||||
initCanvas();
|
||||
}, 200);
|
||||
};
|
||||
|
||||
window.addEventListener("resize", handleResize);
|
||||
|
||||
return () => {
|
||||
if (rafID.current != null) {
|
||||
window.cancelAnimationFrame(rafID.current);
|
||||
}
|
||||
if (resizeTimeout.current) {
|
||||
clearTimeout(resizeTimeout.current);
|
||||
}
|
||||
window.removeEventListener("resize", handleResize);
|
||||
};
|
||||
}, [animate, initCanvas]);
|
||||
|
||||
useEffect(() => {
|
||||
onMouseMove();
|
||||
}, [onMouseMove]);
|
||||
|
||||
useEffect(() => {
|
||||
initCanvas();
|
||||
}, [initCanvas]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn("pointer-events-none", className)}
|
||||
|
||||
@@ -123,7 +123,7 @@ export async function generateItemStructure(
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-goog-api-key": apiKey,
|
||||
...(apiKey ? { "x-goog-api-key": apiKey } : {}),
|
||||
},
|
||||
body: JSON.stringify({
|
||||
contents: [
|
||||
@@ -258,3 +258,4 @@ Instruções:
|
||||
- Garante que o JSON seja minuciosamente válido.
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Singleton pattern para reutilizar conexão
|
||||
*/
|
||||
|
||||
import type mongoose from "mongoose";
|
||||
import mongoose from "mongoose";
|
||||
|
||||
function getMongoUrl(): string {
|
||||
const url = process.env.MONGO_URL;
|
||||
|
||||
@@ -34,6 +34,7 @@ interface PendingEvent extends TelemetryEvent {
|
||||
|
||||
export class MarketTelemetry {
|
||||
private sessionId: string;
|
||||
private userId?: number;
|
||||
private eventQueue: PendingEvent[] = [];
|
||||
private viewStartTimes: Map<number, number> = new Map();
|
||||
private batchInterval: NodeJS.Timeout | null = null;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"start": "NODE_ENV=production tsx server.ts",
|
||||
"start:next": "next start",
|
||||
"lint": "biome check",
|
||||
"lint:fix": "pnpm format && biome check --write && biome check --write --unsafe && biome check --reporter summary",
|
||||
"format": "biome format --write",
|
||||
"db:migrate": "tsx lib/db/migrations/run-migrations.ts",
|
||||
"db:seed": "tsx lib/db/seeds/servers-seed.ts",
|
||||
|
||||
Reference in new issue
Block a user