diff --git a/frontend/app/notifications/page.tsx b/frontend/app/notifications/page.tsx
index cf5bcc87..441a5e46 100644
--- a/frontend/app/notifications/page.tsx
+++ b/frontend/app/notifications/page.tsx
@@ -1,7 +1,8 @@
-"use client";
+"use client"; // Next.js: render on the client (uses state and React Query hooks)
import { useState } from "react";
import DashboardLayout from "@/components/dashboard/DashboardLayout";
+// Data hooks (React Query): fetch a page of notifications and mutate read state
import { useGetNotifications } from "@/lib/react-query/hooks/notifications/useGetNotifications";
import { useMarkNotificationRead } from "@/lib/react-query/hooks/notifications/useMarkNotificationRead";
import { useMarkAllRead } from "@/lib/react-query/hooks/notifications/useMarkAllRead";
@@ -15,8 +16,9 @@ import {
Info,
} from "lucide-react";
+// Picks an icon and colour based on the notification type
function NotificationIcon({ type }: { type: Notification["type"] }) {
- const cls = "w-4 h-4";
+ const cls = "w-4 h-4"; // shared size classes for every icon
switch (type) {
case "PAYMENT_SUCCESS":
return ;
@@ -26,33 +28,41 @@ function NotificationIcon({ type }: { type: Notification["type"] }) {
return ;
case "BOOKING_CANCELLED":
return ;
+ // Any other/unknown type falls back to a neutral info icon
default:
return ;
}
}
+// Converts an ISO timestamp into a short relative label ("5m ago", "3h ago", "2d ago")
function timeAgo(iso: string): string {
- const diff = Date.now() - new Date(iso).getTime();
+ const diff = Date.now() - new Date(iso).getTime(); // elapsed milliseconds
const mins = Math.floor(diff / 60_000);
if (mins < 1) return "just now";
if (mins < 60) return `${mins}m ago`;
const hours = Math.floor(mins / 60);
if (hours < 24) return `${hours}h ago`;
const days = Math.floor(hours / 24);
- return `${days}d ago`;
+ return `${days}d ago`; // no week/month rollup: old items show e.g. "45d ago"
}
export default function NotificationsPage() {
+ // Current page number (1-based); changing it triggers a new fetch
const [page, setPage] = useState(1);
+ // Fetch `page` with 20 items per page
const { data, isLoading, isError, refetch } = useGetNotifications(page, 20);
+ // Mutations for marking a single notification / all notifications as read
const markRead = useMarkNotificationRead();
const markAll = useMarkAllRead();
+ // Fall back to an empty list while data is undefined
const notifications = data?.data ?? [];
+ // Pagination info plus the total unread count
const meta = data?.meta;
return (
+ {/* Page header: title, unread summary, and "Mark all read" action */}
Notifications
@@ -62,11 +72,12 @@ export default function NotificationsPage() {
: "All caught up"}
+ {/* Only show the button when there is something to mark as read */}
{(meta?.unreadCount ?? 0) > 0 && (
+ {/* Body has four mutually exclusive states: loading, error, empty, list */}
{isLoading ? (
+ // Loading: four pulsing placeholder cards (skeleton UI)
{[1, 2, 3, 4].map((i) => (
) : isError ? (
+ // Error: message with a retry button that refetches the query
Failed to load notifications
@@ -98,6 +112,7 @@ export default function NotificationsPage() {
) : notifications.length === 0 ? (
+ // Empty state: the user has no notifications at all
No notifications yet
@@ -106,19 +121,23 @@ export default function NotificationsPage() {
) : (
+ // Populated list of notification cards
{notifications.map((n) => (
+ {/* Type icon in a small rounded badge */}
+ {/* Text content; min-w-0 lets long messages wrap instead of overflowing */}
{n.title}
{n.message}
@@ -126,6 +145,7 @@ export default function NotificationsPage() {
{timeAgo(n.createdAt)}
+ {/* Unread indicator dot, doubling as a "mark as read" button */}
{!n.isRead && (