diff --git a/cinetrack-app/.gitignore b/cinetrack-app/.gitignore index d292b8d..9a8512b 100644 --- a/cinetrack-app/.gitignore +++ b/cinetrack-app/.gitignore @@ -16,6 +16,7 @@ dist-ssr !.env.example infra/data server/benchmarks +server/scripts # Editor directories and files .vscode/* diff --git a/cinetrack-app/client/src/App.tsx b/cinetrack-app/client/src/App.tsx index 3dc8638..a0a809f 100644 --- a/cinetrack-app/client/src/App.tsx +++ b/cinetrack-app/client/src/App.tsx @@ -8,11 +8,13 @@ import { useDemoWelcome } from "./hooks/useDemoWelcome"; import { RouterProvider } from "react-router-dom"; import { router } from "./router"; import { useWatchlistInit } from "./store/useWatchlistStore"; +import { useNotificationInit } from "./store/useNotificationStore"; const AuthPage = lazy(() => import("./pages/AuthPage").then((m) => ({ default: m.AuthPage }))); const WatchlistInitializer: React.FC<{ children: React.ReactNode }> = ({ children }) => { useWatchlistInit(); + useNotificationInit(); return <>{children}; }; diff --git a/cinetrack-app/client/src/components/common/NotificationsModal.tsx b/cinetrack-app/client/src/components/common/NotificationsModal.tsx index 5c6d601..ec1259e 100644 --- a/cinetrack-app/client/src/components/common/NotificationsModal.tsx +++ b/cinetrack-app/client/src/components/common/NotificationsModal.tsx @@ -1,12 +1,41 @@ import React from "react"; -import { FiBell, FiX } from "react-icons/fi"; +import { FiBell, FiX, FiCheck, FiTrash2, FiInfo, FiAward, FiTv } from "react-icons/fi"; +import { useNotificationStore } from "../../store/useNotificationStore"; +import type { Notification } from "../../services/dbService"; interface NotificationsModalProps { isOpen: boolean; onClose: () => void; } +const timeAgo = (dateStr: string) => { + const date = new Date(dateStr); + const now = new Date(); + const seconds = Math.floor((now.getTime() - date.getTime()) / 1000); + + if (seconds < 60) return "Just now"; + const minutes = Math.floor(seconds / 60); + if (minutes < 60) return `${minutes}m ago`; + const hours = Math.floor(minutes / 60); + if (hours < 24) return `${hours}h ago`; + const days = Math.floor(hours / 24); + return `${days}d ago`; +}; + +const NotificationIcon = ({ type }: { type: Notification["type"] }) => { + switch (type) { + case "milestone": + return ; + case "premiere": + return ; + default: + return ; + } +}; + export const NotificationsModal: React.FC = ({ isOpen, onClose }) => { + const { notifications, markAsRead, markAllAsRead, removeNotification } = useNotificationStore(); + if (!isOpen) return null; return ( @@ -17,39 +46,107 @@ export const NotificationsModal: React.FC = ({ isOpen, role="dialog" >
e.stopPropagation()} > -
+ {/* Header */} +

Notifications

- -
- -
-
- +
+ {notifications.some((n) => !n.isRead) && ( + + )} +
-

Coming Soon

-

- Notifications are not available yet. Work in progress!! -

- + {/* List */} +
+ {notifications.length === 0 ? ( +
+
+ +
+

No notifications yet

+
+ ) : ( + notifications.map((n) => ( +
+
+
+ +
+
+
+

+ {n.title} +

+ + {timeAgo(n.createdAt)} + +
+

+ {n.message} +

+
+
+ + {/* Actions */} +
+ {!n.isRead && ( + + )} + +
+
+ )) + )} +
); diff --git a/cinetrack-app/client/src/components/layout/SideNavBar.tsx b/cinetrack-app/client/src/components/layout/SideNavBar.tsx index e59abf6..3a279bc 100644 --- a/cinetrack-app/client/src/components/layout/SideNavBar.tsx +++ b/cinetrack-app/client/src/components/layout/SideNavBar.tsx @@ -1,5 +1,6 @@ import React, { useState } from "react"; import { useAuthContext } from "../../contexts/AuthContext"; +import { useNotificationStore } from "../../store/useNotificationStore"; import { ConfirmModal } from "../common/ConfirmModal"; import { FiCompass, @@ -63,6 +64,7 @@ export const SideNavBar: React.FC = ({ onOpenNotifications, }) => { const { user, logout } = useAuthContext(); + const { unreadCount } = useNotificationStore(); const [showLogoutConfirm, setShowLogoutConfirm] = useState(false); const [isHovered, setIsHovered] = useState(false); @@ -156,9 +158,11 @@ export const SideNavBar: React.FC = ({ > {isExpanded && Notifications} - + {unreadCount > 0 && ( + + )} { } }; + const unreadCount = useNotificationStore((state) => state.unreadCount); + return (
@@ -96,7 +99,11 @@ const Header: React.FC = memo(() => { aria-label="Notifications" > - + {unreadCount > 0 && ( + + + + )}