diff --git a/frontend/src/components/layout/Topbar.tsx b/frontend/src/components/layout/Topbar.tsx index 65fd02d8..c648eb6d 100644 --- a/frontend/src/components/layout/Topbar.tsx +++ b/frontend/src/components/layout/Topbar.tsx @@ -1,7 +1,8 @@ -import { Bell, MessageSquare, Plus, Search, Sparkles, Menu, Moon, Sun } from "lucide-react"; +import { MessageSquare, Plus, Search, Sparkles, Menu, Moon, Sun } from "lucide-react"; import { Link } from "@tanstack/react-router"; import { currentUser } from "@/mocks/seed"; import { useTheme } from "@/hooks/useTheme"; +import { NotificationCenter } from "@/components/shared/NotificationCenter"; export function Topbar({ onMenu }: { onMenu: () => void }) { const { isDark, toggleTheme } = useTheme(); @@ -46,9 +47,7 @@ export function Topbar({ onMenu }: { onMenu: () => void }) { > {isDark ? : } - - - + diff --git a/frontend/src/components/shared/NotificationCenter.tsx b/frontend/src/components/shared/NotificationCenter.tsx new file mode 100644 index 00000000..7534c22d --- /dev/null +++ b/frontend/src/components/shared/NotificationCenter.tsx @@ -0,0 +1,254 @@ +import React, { useState } from "react"; +import { + Bell, + Check, + CheckCircle, + FolderPlus, + MessageCircle, + MessageSquare, + Users, + XCircle, + AtSign +} from "lucide-react"; +import { formatDistanceToNow } from "date-fns"; +import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { Button } from "@/components/ui/button"; +import { cn } from "@/lib/utils"; + +type NotificationType = + | "project_invitation" + | "team_request" + | "comment" + | "mention" + | "application_accepted" + | "application_rejected" + | "message_received"; + +interface Notification { + id: string; + type: NotificationType; + title: string; + description: string; + createdAt: Date; + read: boolean; + avatar?: string; +} + +const mockNotifications: Notification[] = [ + { + id: "1", + type: "project_invitation", + title: "Project Invitation", + description: "Sarah invited you to join the 'Next.js E-commerce' project.", + createdAt: new Date(Date.now() - 1000 * 60 * 5), + read: false, + }, + { + id: "2", + type: "mention", + title: "Mentioned you", + description: "Alex mentioned you in a comment: '@johndoe can you review this?'", + createdAt: new Date(Date.now() - 1000 * 60 * 30), + read: false, + }, + { + id: "3", + type: "team_request", + title: "Team Request", + description: "David wants to join your 'DevLink' team.", + createdAt: new Date(Date.now() - 1000 * 60 * 60 * 2), + read: false, + }, + { + id: "4", + type: "application_accepted", + title: "Application Accepted", + description: "Your application for 'Senior React Developer' was accepted!", + createdAt: new Date(Date.now() - 1000 * 60 * 60 * 24), + read: true, + }, + { + id: "5", + type: "comment", + title: "New Comment", + description: "Emily commented on your post 'State Management in 2024'.", + createdAt: new Date(Date.now() - 1000 * 60 * 60 * 48), + read: true, + }, + { + id: "6", + type: "application_rejected", + title: "Application Status Update", + description: "Unfortunately, your application for 'UI Designer' was not selected.", + createdAt: new Date(Date.now() - 1000 * 60 * 60 * 72), + read: true, + }, + { + id: "7", + type: "message_received", + title: "New Message", + description: "Michael sent you a message: 'Hey, are you available for a quick chat?'", + createdAt: new Date(Date.now() - 1000 * 60 * 60 * 96), + read: true, + }, +]; + +const getNotificationIcon = (type: NotificationType) => { + switch (type) { + case "project_invitation": + return ; + case "team_request": + return ; + case "comment": + return ; + case "mention": + return ; + case "application_accepted": + return ; + case "application_rejected": + return ; + case "message_received": + return ; + } +}; + +export function NotificationCenter() { + const [notifications, setNotifications] = useState(mockNotifications); + const [isOpen, setIsOpen] = useState(false); + + const unreadCount = notifications.filter((n) => !n.read).length; + + const markAllAsRead = () => { + setNotifications(notifications.map((n) => ({ ...n, read: true }))); + }; + + const markAsRead = (id: string) => { + setNotifications(notifications.map((n) => (n.id === id ? { ...n, read: true } : n))); + }; + + const NotificationItem = ({ notification }: { notification: Notification }) => ( +
markAsRead(notification.id)} + > +
+ {getNotificationIcon(notification.type)} +
+
+
+

+ {notification.title} +

+ + {formatDistanceToNow(notification.createdAt, { addSuffix: true })} + +
+

+ {notification.description} +

+
+ {!notification.read && ( +
+
+
+ )} +
+ ); + + return ( + + + + + + +
+

Notifications

+
+ + All + + Unread + {unreadCount > 0 && ( + + {unreadCount} + + )} + + +
+
+ +
+ + You have {unreadCount} unread notifications + + {unreadCount > 0 && ( + + )} +
+ + + + {notifications.length === 0 ? ( +
+ +

No notifications yet

+
+ ) : ( +
+ {notifications.map((notification) => ( + + ))} +
+ )} +
+ + {notifications.filter((n) => !n.read).length === 0 ? ( +
+ +

You're all caught up!

+
+ ) : ( +
+ {notifications + .filter((n) => !n.read) + .map((notification) => ( + + ))} +
+ )} +
+
+
+
+
+ ); +}