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
|
// Criar notificação para o vendedor
|
||||||
await createNotification({
|
await createNotification({
|
||||||
user_id: result.listing?.seller_id,
|
user_id: listing.seller_id,
|
||||||
notification_type: "bid",
|
notification_type: "bid",
|
||||||
title: "Novo lance!",
|
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,
|
related_id: listingId,
|
||||||
send_whatsapp: false,
|
send_whatsapp: false,
|
||||||
});
|
});
|
||||||
@@ -77,7 +97,7 @@ export async function POST(
|
|||||||
user_id: result.previousBidderId,
|
user_id: result.previousBidderId,
|
||||||
notification_type: "bid",
|
notification_type: "bid",
|
||||||
title: "Lance superado",
|
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,
|
related_id: listingId,
|
||||||
send_whatsapp: false,
|
send_whatsapp: false,
|
||||||
});
|
});
|
||||||
@@ -92,11 +112,11 @@ export async function POST(
|
|||||||
metadata: {
|
metadata: {
|
||||||
bid_amount: amount,
|
bid_amount: amount,
|
||||||
payment_method: payment_method,
|
payment_method: payment_method,
|
||||||
previous_bid: result.listing?.current_bid_amount
|
previous_bid: listing.current_bid_amount
|
||||||
? Number(result.listing?.current_bid_amount)
|
? Number(listing.current_bid_amount)
|
||||||
: null,
|
: null,
|
||||||
min_increment: result.listing?.min_bid_increment
|
min_increment: listing.min_bid_increment
|
||||||
? Number(result.listing?.min_bid_increment)
|
? Number(listing.min_bid_increment)
|
||||||
: null,
|
: null,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -40,18 +40,6 @@ export function AnalyticsDashboard() {
|
|||||||
const [analytics, setAnalytics] = useState<AdAnalytics | null>(null);
|
const [analytics, setAnalytics] = useState<AdAnalytics | null>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (user?.userId) {
|
|
||||||
loadUserListings();
|
|
||||||
}
|
|
||||||
}, [user?.userId, loadUserListings]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (selectedAd) {
|
|
||||||
loadAnalytics(selectedAd);
|
|
||||||
}
|
|
||||||
}, [selectedAd, loadAnalytics]);
|
|
||||||
|
|
||||||
const loadUserListings = async () => {
|
const loadUserListings = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
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) {
|
if (!user) {
|
||||||
return (
|
return (
|
||||||
<div className="text-center py-12">
|
<div className="text-center py-12">
|
||||||
|
|||||||
@@ -103,13 +103,6 @@ export function BidModal({
|
|||||||
}
|
}
|
||||||
}, [minBid, bidAmount]);
|
}, [minBid, bidAmount]);
|
||||||
|
|
||||||
// Buscar histórico de lances quando abrir modal
|
|
||||||
useEffect(() => {
|
|
||||||
if (open && listing) {
|
|
||||||
fetchBidHistory();
|
|
||||||
}
|
|
||||||
}, [open, listing?.listing_id, fetchBidHistory, listing]);
|
|
||||||
|
|
||||||
const fetchBidHistory = async () => {
|
const fetchBidHistory = async () => {
|
||||||
if (!listing) return;
|
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 handleBid = async () => {
|
||||||
const amount = parseFloat(bidAmount);
|
const amount = parseFloat(bidAmount);
|
||||||
|
|
||||||
|
|||||||
@@ -204,12 +204,6 @@ export function CreateAdModal({
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Carregar servidores ao abrir o modal
|
// Carregar servidores ao abrir o modal
|
||||||
useEffect(() => {
|
|
||||||
if (open) {
|
|
||||||
loadServers();
|
|
||||||
}
|
|
||||||
}, [open, loadServers]);
|
|
||||||
|
|
||||||
const loadServers = async () => {
|
const loadServers = async () => {
|
||||||
setLoadingServers(true);
|
setLoadingServers(true);
|
||||||
try {
|
try {
|
||||||
@@ -225,6 +219,12 @@ export function CreateAdModal({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (open) {
|
||||||
|
loadServers();
|
||||||
|
}
|
||||||
|
}, [open, loadServers]);
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
if (!loading) {
|
if (!loading) {
|
||||||
setSelectedItem(null);
|
setSelectedItem(null);
|
||||||
|
|||||||
@@ -135,10 +135,6 @@ export function MyAdsIntentionList({
|
|||||||
"all" | "pending" | "accepted" | "rejected"
|
"all" | "pending" | "accepted" | "rejected"
|
||||||
>("all");
|
>("all");
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
loadIntentions();
|
|
||||||
}, [loadIntentions]);
|
|
||||||
|
|
||||||
const loadIntentions = async () => {
|
const loadIntentions = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
@@ -162,6 +158,10 @@ export function MyAdsIntentionList({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadIntentions();
|
||||||
|
}, [loadIntentions]);
|
||||||
|
|
||||||
const pendingCount = intentions.filter((i) => i.status === "pending").length;
|
const pendingCount = intentions.filter((i) => i.status === "pending").length;
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
|
|||||||
@@ -69,20 +69,6 @@ export function MessagesPanel({
|
|||||||
setIsLoading(false);
|
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 () => {
|
const markAllAsRead = async () => {
|
||||||
try {
|
try {
|
||||||
await fetch("/api/chats/mark-all-read", {
|
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) => {
|
const handleChatClick = (chatId: string, _listingId: number) => {
|
||||||
setSelectedChatId(chatId);
|
setSelectedChatId(chatId);
|
||||||
setShowChatModal(true);
|
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 () => {
|
return () => {
|
||||||
globe.destroy();
|
globe.destroy();
|
||||||
window.removeEventListener("resize", onResize);
|
window.removeEventListener("resize", onResize);
|
||||||
|
|||||||
+39
-36
@@ -101,42 +101,7 @@ export const Particles: React.FC<ParticlesProps> = ({
|
|||||||
const rafID = useRef<number | null>(null);
|
const rafID = useRef<number | null>(null);
|
||||||
const resizeTimeout = useRef<NodeJS.Timeout | 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 = () => {
|
const initCanvas = () => {
|
||||||
resizeCanvas();
|
resizeCanvas();
|
||||||
@@ -302,6 +267,44 @@ export const Particles: React.FC<ParticlesProps> = ({
|
|||||||
rafID.current = window.requestAnimationFrame(animate);
|
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 (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn("pointer-events-none", className)}
|
className={cn("pointer-events-none", className)}
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ export async function generateItemStructure(
|
|||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"x-goog-api-key": apiKey,
|
...(apiKey ? { "x-goog-api-key": apiKey } : {}),
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
contents: [
|
contents: [
|
||||||
@@ -258,3 +258,4 @@ Instruções:
|
|||||||
- Garante que o JSON seja minuciosamente válido.
|
- Garante que o JSON seja minuciosamente válido.
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
* Singleton pattern para reutilizar conexão
|
* Singleton pattern para reutilizar conexão
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type mongoose from "mongoose";
|
import mongoose from "mongoose";
|
||||||
|
|
||||||
function getMongoUrl(): string {
|
function getMongoUrl(): string {
|
||||||
const url = process.env.MONGO_URL;
|
const url = process.env.MONGO_URL;
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ interface PendingEvent extends TelemetryEvent {
|
|||||||
|
|
||||||
export class MarketTelemetry {
|
export class MarketTelemetry {
|
||||||
private sessionId: string;
|
private sessionId: string;
|
||||||
|
private userId?: number;
|
||||||
private eventQueue: PendingEvent[] = [];
|
private eventQueue: PendingEvent[] = [];
|
||||||
private viewStartTimes: Map<number, number> = new Map();
|
private viewStartTimes: Map<number, number> = new Map();
|
||||||
private batchInterval: NodeJS.Timeout | null = null;
|
private batchInterval: NodeJS.Timeout | null = null;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
"start": "NODE_ENV=production tsx server.ts",
|
"start": "NODE_ENV=production tsx server.ts",
|
||||||
"start:next": "next start",
|
"start:next": "next start",
|
||||||
"lint": "biome check",
|
"lint": "biome check",
|
||||||
|
"lint:fix": "pnpm format && biome check --write && biome check --write --unsafe && biome check --reporter summary",
|
||||||
"format": "biome format --write",
|
"format": "biome format --write",
|
||||||
"db:migrate": "tsx lib/db/migrations/run-migrations.ts",
|
"db:migrate": "tsx lib/db/migrations/run-migrations.ts",
|
||||||
"db:seed": "tsx lib/db/seeds/servers-seed.ts",
|
"db:seed": "tsx lib/db/seeds/servers-seed.ts",
|
||||||
|
|||||||
Reference in new issue
Block a user