From e450bd2a6d6a2a33aac58716efb65dd180b89bb2 Mon Sep 17 00:00:00 2001 From: favour-gl Date: Tue, 20 Oct 2026 10:07:25 +0100 Subject: [PATCH] feat: Handle daylight-saving changes in slot generation --- frontend/app/notifications/page.tsx | 34 +++++++++++++++++++++----- frontend/app/privacy-policy/layout.tsx | 8 +++++- frontend/app/profile/layout.tsx | 9 ++++++- 3 files changed, 43 insertions(+), 8 deletions(-) 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 && (