gohorsejobs/frontend/src/components/notifications-dropdown.tsx

108 lines
5.2 KiB
TypeScript

"use client"
import { useEffect, useState } from "react"
import { Bell, Check, Trash2, Info, CheckCircle, AlertTriangle, XCircle } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { ScrollArea } from "@/components/ui/scroll-area"
import { useNotificationsStore } from "@/lib/store/notifications-store"
import { cn } from "@/lib/utils"
import { Badge } from "@/components/ui/badge"
const getIcon = (type: string) => {
switch (type) {
case 'success': return <CheckCircle className="h-4 w-4 text-green-500" />;
case 'warning': return <AlertTriangle className="h-4 w-4 text-amber-500" />;
case 'error': return <XCircle className="h-4 w-4 text-red-500" />;
default: return <Info className="h-4 w-4 text-blue-500" />;
}
}
export function NotificationsDropdown() {
const { notifications, unreadCount, fetchNotifications, markAsRead, markAllAsRead } = useNotificationsStore()
const [open, setOpen] = useState(false)
useEffect(() => {
// Fetch on mount and set up polling every 30s
fetchNotifications()
const interval = setInterval(fetchNotifications, 30000)
return () => clearInterval(interval)
}, [fetchNotifications])
return (
<DropdownMenu open={open} onOpenChange={setOpen}>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className="relative">
<Bell className="h-5 w-5" />
{unreadCount > 0 && (
<span className="absolute top-1.5 right-1.5 h-2 w-2 rounded-full bg-red-600 ring-2 ring-background" />
)}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-80 p-0">
<div className="flex items-center justify-between p-4 border-b">
<h4 className="font-semibold leading-none">Notifications</h4>
{unreadCount > 0 && (
<Button
variant="ghost"
size="sm"
className="h-auto px-2 text-xs"
onClick={() => markAllAsRead()}
>
<Check className="mr-2 h-3 w-3" />
Mark all
</Button>
)}
</div>
<ScrollArea className="h-[300px]">
{notifications.length === 0 ? (
<div className="p-4 text-center text-sm text-muted-foreground">
No notifications
</div>
) : (
<div className="grid gap-1 p-1">
{notifications.map((notification) => (
<div
key={notification.id}
className={cn(
"flex flex-col gap-1 p-3 rounded-md hover:bg-muted/50 cursor-pointer transition-colors relative",
!notification.readAt && "bg-muted/20"
)}
onClick={() => markAsRead(notification.id)}
>
<div className="flex items-start gap-3">
<div className="mt-1">
{getIcon(notification.type)}
</div>
<div className="flex-1 space-y-1">
<p className={cn("text-sm font-medium leading-none", !notification.readAt && "font-bold")}>
{notification.title}
</p>
<p className="text-xs text-muted-foreground line-clamp-2">
{notification.message}
</p>
<p className="text-[10px] text-muted-foreground">
{new Date(notification.createdAt).toLocaleDateString()}
</p>
</div>
{!notification.readAt && (
<div className="h-2 w-2 rounded-full bg-primary mt-1" />
)}
</div>
</div>
))}
</div>
)}
</ScrollArea>
<div className="p-2 border-t text-center">
<Button variant="ghost" size="sm" className="w-full text-xs">View all notifications</Button>
</div>
</DropdownMenuContent>
</DropdownMenu>
)
}