From 1593861d7cb3d7899d15fb39885af5f3e3d9af26 Mon Sep 17 00:00:00 2001 From: Bruno Quaresma Date: Fri, 21 Mar 2025 13:38:17 -0300 Subject: [PATCH] feat: add load more notifications on inbox (#17030) Users need to see older notifications, so to make that happen, we added a load more button at the end of the notifications list. **Demo:** https://github.com/user-attachments/assets/bd3d7964-a8f5-4164-8da0-9ba89ae88c9c **What is missing?** As you can notice, I didn't add tests for this feature. I tried, but I didn't find a good solution for testing scroll events. However I was able to get it working, but it was too cumbersome that I decided to remove because of its maintenence burden. --- site/src/api/api.ts | 8 +++- site/src/components/ScrollArea/ScrollArea.tsx | 2 +- .../NotificationsInbox/InboxPopover.tsx | 31 ++++++++++++- .../NotificationsInbox/NotificationsInbox.tsx | 46 ++++++++++++++++--- 4 files changed, 76 insertions(+), 11 deletions(-) diff --git a/site/src/api/api.ts b/site/src/api/api.ts index 0959a5c791..b042735357 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -2434,9 +2434,13 @@ class ApiMethods { return res.data; }; - getInboxNotifications = async () => { + getInboxNotifications = async (startingBeforeId?: string) => { + const params = new URLSearchParams(); + if (startingBeforeId) { + params.append("starting_before", startingBeforeId); + } const res = await this.axios.get( - "/api/v2/notifications/inbox", + `/api/v2/notifications/inbox?${params.toString()}`, ); return res.data; }; diff --git a/site/src/components/ScrollArea/ScrollArea.tsx b/site/src/components/ScrollArea/ScrollArea.tsx index d4544a0ca2..2c3b2f3255 100644 --- a/site/src/components/ScrollArea/ScrollArea.tsx +++ b/site/src/components/ScrollArea/ScrollArea.tsx @@ -18,7 +18,7 @@ export const ScrollArea = React.forwardRef< {children} - + )); diff --git a/site/src/modules/notifications/NotificationsInbox/InboxPopover.tsx b/site/src/modules/notifications/NotificationsInbox/InboxPopover.tsx index 7651a83ebe..3a5cd92248 100644 --- a/site/src/modules/notifications/NotificationsInbox/InboxPopover.tsx +++ b/site/src/modules/notifications/NotificationsInbox/InboxPopover.tsx @@ -19,9 +19,12 @@ type InboxPopoverProps = { notifications: readonly InboxNotification[] | undefined; unreadCount: number; error: unknown; + isLoadingMoreNotifications: boolean; + hasMoreNotifications: boolean; onRetry: () => void; onMarkAllAsRead: () => void; onMarkNotificationAsRead: (notificationId: string) => void; + onLoadMoreNotifications: () => void; defaultOpen?: boolean; }; @@ -30,9 +33,12 @@ export const InboxPopover: FC = ({ unreadCount, notifications, error, + isLoadingMoreNotifications, + hasMoreNotifications, onRetry, onMarkAllAsRead, onMarkNotificationAsRead, + onLoadMoreNotifications, }) => { const [isOpen, setIsOpen] = useState(defaultOpen); @@ -41,12 +47,21 @@ export const InboxPopover: FC = ({ - + {/* * data-radix-scroll-area-viewport is used to set the max-height of the ScrollArea * https://github.com/shadcn-ui/ui/issues/542#issuecomment-2339361283 */} - + [data-radix-scroll-area-viewport]]:max-h-[var(--max-height)]", + ])} + >
= ({ onMarkNotificationAsRead={onMarkNotificationAsRead} /> ))} + {hasMoreNotifications && ( + + )}
) : (
diff --git a/site/src/modules/notifications/NotificationsInbox/NotificationsInbox.tsx b/site/src/modules/notifications/NotificationsInbox/NotificationsInbox.tsx index bee4d7482b..78d119a7e3 100644 --- a/site/src/modules/notifications/NotificationsInbox/NotificationsInbox.tsx +++ b/site/src/modules/notifications/NotificationsInbox/NotificationsInbox.tsx @@ -1,4 +1,4 @@ -import { API, watchInboxNotifications } from "api/api"; +import { watchInboxNotifications } from "api/api"; import { getErrorDetail, getErrorMessage } from "api/errors"; import type { ListInboxNotificationsResponse, @@ -11,10 +11,13 @@ import { useMutation, useQuery, useQueryClient } from "react-query"; import { InboxPopover } from "./InboxPopover"; const NOTIFICATIONS_QUERY_KEY = ["notifications"]; +const NOTIFICATIONS_LIMIT = 25; // This is hard set in the API type NotificationsInboxProps = { defaultOpen?: boolean; - fetchNotifications: () => Promise; + fetchNotifications: ( + startingBeforeId?: string, + ) => Promise; markAllAsRead: () => Promise; markNotificationAsRead: ( notificationId: string, @@ -30,12 +33,12 @@ export const NotificationsInbox: FC = ({ const queryClient = useQueryClient(); const { - data: res, + data: inboxRes, error, refetch, } = useQuery({ queryKey: NOTIFICATIONS_QUERY_KEY, - queryFn: fetchNotifications, + queryFn: () => fetchNotifications(), }); const updateNotificationsCache = useEffectEvent( @@ -75,6 +78,32 @@ export const NotificationsInbox: FC = ({ }; }, [updateNotificationsCache]); + const { + mutate: loadMoreNotifications, + isLoading: isLoadingMoreNotifications, + } = useMutation({ + mutationFn: async () => { + if (!inboxRes || inboxRes.notifications.length === 0) { + return; + } + const lastNotification = + inboxRes.notifications[inboxRes.notifications.length - 1]; + const newRes = await fetchNotifications(lastNotification.id); + updateNotificationsCache((prev) => { + return { + unread_count: newRes.unread_count, + notifications: [...prev.notifications, ...newRes.notifications], + }; + }); + }, + onError: (error) => { + displayError( + getErrorMessage(error, "Error loading more notifications"), + getErrorDetail(error), + ); + }, + }); + const markAllAsReadMutation = useMutation({ mutationFn: markAllAsRead, onSuccess: () => { @@ -122,12 +151,17 @@ export const NotificationsInbox: FC = ({ return ( ); };