mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-24 13:51:14 -05:00
feat: Implement Novu notification system
This commit is contained in:
94
src/components/notifications/NotificationCenter.tsx
Normal file
94
src/components/notifications/NotificationCenter.tsx
Normal file
@@ -0,0 +1,94 @@
|
||||
import { useState } from 'react';
|
||||
import { Bell } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from '@/components/ui/popover';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
import { useNovuNotifications } from '@/hooks/useNovuNotifications';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
|
||||
export function NotificationCenter() {
|
||||
const { notifications, unreadCount, markAsRead, markAllAsRead, isEnabled } = useNovuNotifications();
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
if (!isEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="relative">
|
||||
<Bell className="h-5 w-5" />
|
||||
{unreadCount > 0 && (
|
||||
<Badge
|
||||
variant="destructive"
|
||||
className="absolute -top-1 -right-1 h-5 w-5 rounded-full p-0 flex items-center justify-center text-xs"
|
||||
>
|
||||
{unreadCount > 9 ? '9+' : unreadCount}
|
||||
</Badge>
|
||||
)}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-80 p-0" align="end">
|
||||
<div className="flex items-center justify-between p-4 border-b">
|
||||
<h3 className="font-semibold">Notifications</h3>
|
||||
{unreadCount > 0 && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={markAllAsRead}
|
||||
className="text-xs"
|
||||
>
|
||||
Mark all as read
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<ScrollArea className="h-[400px]">
|
||||
{notifications.length === 0 ? (
|
||||
<div className="p-8 text-center text-muted-foreground">
|
||||
<Bell className="h-8 w-8 mx-auto mb-2 opacity-50" />
|
||||
<p className="text-sm">No notifications yet</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="divide-y">
|
||||
{notifications.map((notification) => (
|
||||
<div
|
||||
key={notification.id}
|
||||
className={`p-4 cursor-pointer hover:bg-muted/50 transition-colors ${
|
||||
!notification.read ? 'bg-muted/30' : ''
|
||||
}`}
|
||||
onClick={() => {
|
||||
if (!notification.read) {
|
||||
markAsRead(notification.id);
|
||||
}
|
||||
// Handle CTA action if exists
|
||||
if (notification.cta) {
|
||||
// Navigate or perform action based on notification.cta
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
{!notification.read && (
|
||||
<div className="w-2 h-2 rounded-full bg-primary mt-1.5 flex-shrink-0" />
|
||||
)}
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm break-words">{notification.content}</p>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
{new Date(notification.createdAt).toLocaleString()}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</ScrollArea>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user