notifications
This commit is contained in:
parent
404c576d80
commit
decc6cf778
@ -1,8 +1,17 @@
|
||||
import React, { memo } from 'react';
|
||||
import React, { memo, useState } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { Bell } from 'lucide-react';
|
||||
import { Bell, Check, ExternalLink } from 'lucide-react';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { useNotifications } from '@/hooks/use-notifications';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuSeparator,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
|
||||
interface NotificationBadgeProps {
|
||||
className?: string;
|
||||
@ -10,29 +19,115 @@ interface NotificationBadgeProps {
|
||||
|
||||
// Use React.memo to prevent unnecessary re-renders
|
||||
export const NotificationBadge = memo(function NotificationBadge({ className }: NotificationBadgeProps) {
|
||||
const { notificationCount } = useNotifications();
|
||||
const { notifications, notificationCount, markAsRead, markAllAsRead } = useNotifications();
|
||||
const hasUnread = notificationCount.unread > 0;
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
console.log('[NOTIFICATION_BADGE] Current notification count:', notificationCount);
|
||||
|
||||
const handleMarkAsRead = async (notificationId: string) => {
|
||||
await markAsRead(notificationId);
|
||||
};
|
||||
|
||||
const handleMarkAllAsRead = async () => {
|
||||
await markAllAsRead();
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
// Take the latest 5 notifications for the dropdown
|
||||
const recentNotifications = notifications.slice(0, 5);
|
||||
|
||||
return (
|
||||
<div className={`relative ${className || ''}`}>
|
||||
<Link
|
||||
href='/notifications'
|
||||
className='text-white/80 hover:text-white transition-colors relative'
|
||||
aria-label={`Notifications${hasUnread ? ` (${notificationCount.unread} unread)` : ''}`}
|
||||
>
|
||||
<Bell className='w-5 h-5' />
|
||||
{hasUnread && (
|
||||
<Badge
|
||||
variant="notification"
|
||||
size="notification"
|
||||
className="absolute -top-2 -right-2"
|
||||
>
|
||||
{notificationCount.unread > 99 ? '99+' : notificationCount.unread}
|
||||
</Badge>
|
||||
)}
|
||||
</Link>
|
||||
<DropdownMenu open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="text-white/80 hover:text-white relative p-0">
|
||||
<Bell className='w-5 h-5' />
|
||||
{hasUnread && (
|
||||
<Badge
|
||||
variant="notification"
|
||||
size="notification"
|
||||
className="absolute -top-2 -right-2"
|
||||
>
|
||||
{notificationCount.unread > 99 ? '99+' : notificationCount.unread}
|
||||
</Badge>
|
||||
)}
|
||||
<span className="sr-only">Notifications</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-80">
|
||||
<div className="flex items-center justify-between p-4">
|
||||
<h3 className="font-medium">Notifications</h3>
|
||||
{notificationCount.unread > 0 && (
|
||||
<Button variant="ghost" size="sm" onClick={handleMarkAllAsRead}>
|
||||
<Check className="h-4 w-4 mr-2" />
|
||||
Mark all read
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
{notifications.length === 0 ? (
|
||||
<div className="py-4 px-3 text-center">
|
||||
<p className="text-sm text-muted-foreground">No notifications</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{recentNotifications.map((notification) => (
|
||||
<DropdownMenuItem key={notification.id} className="px-4 py-3 cursor-default">
|
||||
<div className="w-full">
|
||||
<div className="flex items-start justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium">
|
||||
{notification.title}
|
||||
{!notification.isRead && (
|
||||
<Badge variant="secondary" className="ml-2 bg-blue-500 text-white">New</Badge>
|
||||
)}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{formatDistanceToNow(new Date(notification.timestamp), { addSuffix: true })}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex space-x-1">
|
||||
{!notification.isRead && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-6 w-6 p-0"
|
||||
onClick={() => handleMarkAsRead(notification.id)}
|
||||
>
|
||||
<Check className="h-3.5 w-3.5" />
|
||||
<span className="sr-only">Mark as read</span>
|
||||
</Button>
|
||||
)}
|
||||
{notification.link && (
|
||||
<Link href={notification.link} target="_blank">
|
||||
<Button variant="ghost" size="sm" className="h-6 w-6 p-0">
|
||||
<ExternalLink className="h-3.5 w-3.5" />
|
||||
<span className="sr-only">Open</span>
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-xs mt-1">{notification.message}</p>
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
<div className="p-2 text-center">
|
||||
<Link
|
||||
href="/notifications"
|
||||
className="text-xs text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
View all notifications
|
||||
</Link>
|
||||
</div>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user