- wrap root layout with QueryProvider for React Query - replace market UnderConstruction placeholder with MarketClient and real data fetching - implement search, pagination, and sort via search params using getActiveListings - add necessary client components and queries - update dependencies (e.g., prisma, pkg-types)
155 lines
4.1 KiB
TypeScript
155 lines
4.1 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect } from "react";
|
|
import { Bell } from "lucide-react";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuTrigger,
|
|
} from "@/components/ui/dropdown-menu";
|
|
import { NotificationDropdown } from "./NotificationDropdown";
|
|
import { useSession } from "@/lib/hooks/use-session";
|
|
|
|
interface Notification {
|
|
notification_id: number;
|
|
notification_type: string;
|
|
title: string;
|
|
message: string;
|
|
is_read: boolean;
|
|
related_id: number | null;
|
|
created_at: string;
|
|
}
|
|
|
|
interface NotificationBellProps {
|
|
userId?: number;
|
|
className?: string;
|
|
}
|
|
|
|
export function NotificationBell({ userId, className }: NotificationBellProps) {
|
|
const { user } = useSession();
|
|
const [unreadCount, setUnreadCount] = useState(0);
|
|
const [notifications, setNotifications] = useState<Notification[]>([]);
|
|
const [isOpen, setIsOpen] = useState(false);
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
const effectiveUserId = userId || user?.userId;
|
|
|
|
// Buscar contagem de não lidas
|
|
const fetchUnreadCount = async () => {
|
|
if (!effectiveUserId) return;
|
|
|
|
try {
|
|
const res = await fetch("/api/notifications/unread-count");
|
|
if (res.ok) {
|
|
const data = await res.json();
|
|
if (data.success) {
|
|
setUnreadCount(data.count);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error("Erro ao buscar contagem de notificações:", error);
|
|
}
|
|
};
|
|
|
|
// Buscar notificações recentes
|
|
const fetchNotifications = async () => {
|
|
if (!effectiveUserId) return;
|
|
|
|
setLoading(true);
|
|
try {
|
|
const res = await fetch("/api/notifications?limit=10");
|
|
if (res.ok) {
|
|
const data = await res.json();
|
|
if (data.success) {
|
|
setNotifications(data.notifications || []);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error("Erro ao buscar notificações:", error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
// Polling automático a cada 30 segundos
|
|
useEffect(() => {
|
|
if (!effectiveUserId) return;
|
|
|
|
fetchUnreadCount();
|
|
fetchNotifications();
|
|
|
|
const interval = setInterval(() => {
|
|
fetchUnreadCount();
|
|
if (isOpen) {
|
|
fetchNotifications();
|
|
}
|
|
}, 30000); // 30 segundos
|
|
|
|
return () => clearInterval(interval);
|
|
}, [effectiveUserId, isOpen]);
|
|
|
|
// Atualizar quando abrir popover
|
|
useEffect(() => {
|
|
if (isOpen) {
|
|
fetchNotifications();
|
|
}
|
|
}, [isOpen]);
|
|
|
|
const handleMarkAsRead = async (notificationId: number) => {
|
|
try {
|
|
const res = await fetch(`/api/notifications/${notificationId}/read`, {
|
|
method: "POST",
|
|
});
|
|
|
|
if (res.ok) {
|
|
// Atualizar estado local
|
|
setNotifications((prev) =>
|
|
prev.map((n) =>
|
|
n.notification_id === notificationId ? { ...n, is_read: true } : n
|
|
)
|
|
);
|
|
// Atualizar contagem
|
|
setUnreadCount((prev) => Math.max(0, prev - 1));
|
|
}
|
|
} catch (error) {
|
|
console.error("Erro ao marcar notificação como lida:", error);
|
|
}
|
|
};
|
|
|
|
if (!effectiveUserId) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<DropdownMenu open={isOpen} onOpenChange={setIsOpen}>
|
|
<DropdownMenuTrigger asChild>
|
|
<Button variant="ghost" size="icon" className={`relative ${className}`}>
|
|
<Bell className="h-5 w-5" />
|
|
{unreadCount > 0 && (
|
|
<Badge
|
|
variant="destructive"
|
|
className="absolute -top-1 -right-1 h-5 w-5 flex items-center justify-center p-0 text-xs rounded-full animate-pulse"
|
|
>
|
|
{unreadCount > 99 ? "99+" : unreadCount}
|
|
</Badge>
|
|
)}
|
|
</Button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent className="w-80 p-0" align="end">
|
|
<NotificationDropdown
|
|
notifications={notifications}
|
|
onMarkAsRead={handleMarkAsRead}
|
|
onViewAll={() => {
|
|
setIsOpen(false);
|
|
// Navegar para página de notificações (implementar depois)
|
|
window.location.href = "/notifications";
|
|
}}
|
|
/>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
);
|
|
}
|
|
|