Skip to content

Commit 1593861

Browse files
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.
1 parent 82e3773 commit 1593861

File tree

4 files changed

+76
-11
lines changed

4 files changed

+76
-11
lines changed

site/src/api/api.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2434,9 +2434,13 @@ class ApiMethods {
24342434
return res.data;
24352435
};
24362436

2437-
getInboxNotifications = async () => {
2437+
getInboxNotifications = async (startingBeforeId?: string) => {
2438+
const params = new URLSearchParams();
2439+
if (startingBeforeId) {
2440+
params.append("starting_before", startingBeforeId);
2441+
}
24382442
const res = await this.axios.get<TypesGen.ListInboxNotificationsResponse>(
2439-
"/api/v2/notifications/inbox",
2443+
`/api/v2/notifications/inbox?${params.toString()}`,
24402444
);
24412445
return res.data;
24422446
};

site/src/components/ScrollArea/ScrollArea.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export const ScrollArea = React.forwardRef<
1818
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
1919
{children}
2020
</ScrollAreaPrimitive.Viewport>
21-
<ScrollBar />
21+
<ScrollBar className="z-10" />
2222
<ScrollAreaPrimitive.Corner />
2323
</ScrollAreaPrimitive.Root>
2424
));

site/src/modules/notifications/NotificationsInbox/InboxPopover.tsx

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@ type InboxPopoverProps = {
1919
notifications: readonly InboxNotification[] | undefined;
2020
unreadCount: number;
2121
error: unknown;
22+
isLoadingMoreNotifications: boolean;
23+
hasMoreNotifications: boolean;
2224
onRetry: () => void;
2325
onMarkAllAsRead: () => void;
2426
onMarkNotificationAsRead: (notificationId: string) => void;
27+
onLoadMoreNotifications: () => void;
2528
defaultOpen?: boolean;
2629
};
2730

@@ -30,9 +33,12 @@ export const InboxPopover: FC<InboxPopoverProps> = ({
3033
unreadCount,
3134
notifications,
3235
error,
36+
isLoadingMoreNotifications,
37+
hasMoreNotifications,
3338
onRetry,
3439
onMarkAllAsRead,
3540
onMarkNotificationAsRead,
41+
onLoadMoreNotifications,
3642
}) => {
3743
const [isOpen, setIsOpen] = useState(defaultOpen);
3844

@@ -41,12 +47,21 @@ export const InboxPopover: FC<InboxPopoverProps> = ({
4147
<PopoverTrigger asChild>
4248
<InboxButton unreadCount={unreadCount} />
4349
</PopoverTrigger>
44-
<PopoverContent className="w-[466px]" align="end">
50+
<PopoverContent
51+
className="w-[var(--radix-popper-available-width)] max-w-[466px]"
52+
align="end"
53+
>
4554
{/*
4655
* data-radix-scroll-area-viewport is used to set the max-height of the ScrollArea
4756
* https://github.com/shadcn-ui/ui/issues/542#issuecomment-2339361283
4857
*/}
49-
<ScrollArea className="[&>[data-radix-scroll-area-viewport]]:max-h-[calc(var(--radix-popover-content-available-height)-24px)]">
58+
<ScrollArea
59+
className={cn([
60+
"[--bottom-offset:48px]",
61+
"[--max-height:calc(var(--radix-popover-content-available-height)-var(--bottom-offset))]",
62+
"[&>[data-radix-scroll-area-viewport]]:max-h-[var(--max-height)]",
63+
])}
64+
>
5065
<div
5166
className={cn([
5267
"flex items-center justify-between p-3 border-0 border-b border-solid border-border",
@@ -94,6 +109,18 @@ export const InboxPopover: FC<InboxPopoverProps> = ({
94109
onMarkNotificationAsRead={onMarkNotificationAsRead}
95110
/>
96111
))}
112+
{hasMoreNotifications && (
113+
<Button
114+
variant="subtle"
115+
size="sm"
116+
disabled={isLoadingMoreNotifications}
117+
onClick={onLoadMoreNotifications}
118+
className="w-full"
119+
>
120+
<Spinner loading={isLoadingMoreNotifications} size="sm" />
121+
Load more
122+
</Button>
123+
)}
97124
</div>
98125
) : (
99126
<div className="p-6 flex items-center justify-center min-h-48">

site/src/modules/notifications/NotificationsInbox/NotificationsInbox.tsx

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { API, watchInboxNotifications } from "api/api";
1+
import { watchInboxNotifications } from "api/api";
22
import { getErrorDetail, getErrorMessage } from "api/errors";
33
import type {
44
ListInboxNotificationsResponse,
@@ -11,10 +11,13 @@ import { useMutation, useQuery, useQueryClient } from "react-query";
1111
import { InboxPopover } from "./InboxPopover";
1212

1313
const NOTIFICATIONS_QUERY_KEY = ["notifications"];
14+
const NOTIFICATIONS_LIMIT = 25; // This is hard set in the API
1415

1516
type NotificationsInboxProps = {
1617
defaultOpen?: boolean;
17-
fetchNotifications: () => Promise<ListInboxNotificationsResponse>;
18+
fetchNotifications: (
19+
startingBeforeId?: string,
20+
) => Promise<ListInboxNotificationsResponse>;
1821
markAllAsRead: () => Promise<void>;
1922
markNotificationAsRead: (
2023
notificationId: string,
@@ -30,12 +33,12 @@ export const NotificationsInbox: FC<NotificationsInboxProps> = ({
3033
const queryClient = useQueryClient();
3134

3235
const {
33-
data: res,
36+
data: inboxRes,
3437
error,
3538
refetch,
3639
} = useQuery({
3740
queryKey: NOTIFICATIONS_QUERY_KEY,
38-
queryFn: fetchNotifications,
41+
queryFn: () => fetchNotifications(),
3942
});
4043

4144
const updateNotificationsCache = useEffectEvent(
@@ -75,6 +78,32 @@ export const NotificationsInbox: FC<NotificationsInboxProps> = ({
7578
};
7679
}, [updateNotificationsCache]);
7780

81+
const {
82+
mutate: loadMoreNotifications,
83+
isLoading: isLoadingMoreNotifications,
84+
} = useMutation({
85+
mutationFn: async () => {
86+
if (!inboxRes || inboxRes.notifications.length === 0) {
87+
return;
88+
}
89+
const lastNotification =
90+
inboxRes.notifications[inboxRes.notifications.length - 1];
91+
const newRes = await fetchNotifications(lastNotification.id);
92+
updateNotificationsCache((prev) => {
93+
return {
94+
unread_count: newRes.unread_count,
95+
notifications: [...prev.notifications, ...newRes.notifications],
96+
};
97+
});
98+
},
99+
onError: (error) => {
100+
displayError(
101+
getErrorMessage(error, "Error loading more notifications"),
102+
getErrorDetail(error),
103+
);
104+
},
105+
});
106+
78107
const markAllAsReadMutation = useMutation({
79108
mutationFn: markAllAsRead,
80109
onSuccess: () => {
@@ -122,12 +151,17 @@ export const NotificationsInbox: FC<NotificationsInboxProps> = ({
122151
return (
123152
<InboxPopover
124153
defaultOpen={defaultOpen}
125-
notifications={res?.notifications}
126-
unreadCount={res?.unread_count ?? 0}
154+
notifications={inboxRes?.notifications}
155+
unreadCount={inboxRes?.unread_count ?? 0}
127156
error={error}
157+
isLoadingMoreNotifications={isLoadingMoreNotifications}
158+
hasMoreNotifications={Boolean(
159+
inboxRes && inboxRes.notifications.length === NOTIFICATIONS_LIMIT,
160+
)}
128161
onRetry={refetch}
129162
onMarkAllAsRead={markAllAsReadMutation.mutate}
130163
onMarkNotificationAsRead={markNotificationAsReadMutation.mutate}
164+
onLoadMoreNotifications={loadMoreNotifications}
131165
/>
132166
);
133167
};

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy