Hey is a decentralized and permissionless social media app built with Lens Protocol 馃尶
1import { BellIcon } from "@heroicons/react/24/outline";
2import { NotificationFeedType } from "@hey/data/enums";
3import {
4 type NotificationRequest,
5 NotificationType,
6 useNotificationsQuery
7} from "@hey/indexer";
8import { memo, useCallback, useEffect } from "react";
9import { WindowVirtualizer } from "virtua";
10import AccountActionExecutedNotification from "@/components/Notification/Type/AccountActionExecutedNotification";
11import CommentNotification from "@/components/Notification/Type/CommentNotification";
12import FollowNotification from "@/components/Notification/Type/FollowNotification";
13import MentionNotification from "@/components/Notification/Type/MentionNotification";
14import PostActionExecutedNotification from "@/components/Notification/Type/PostActionExecutedNotification";
15import QuoteNotification from "@/components/Notification/Type/QuoteNotification";
16import ReactionNotification from "@/components/Notification/Type/ReactionNotification";
17import RepostNotification from "@/components/Notification/Type/RepostNotification";
18import { Card, EmptyState, ErrorMessage } from "@/components/Shared/UI";
19import cn from "@/helpers/cn";
20import useLoadMoreOnIntersect from "@/hooks/useLoadMoreOnIntersect";
21import { useNotificationStore } from "@/store/persisted/useNotificationStore";
22import NotificationShimmer from "./Shimmer";
23import TokenDistributedNotification from "./Type/TokenDistributedNotification";
24
25const notificationComponentMap = {
26 AccountActionExecutedNotification,
27 CommentNotification,
28 FollowNotification,
29 MentionNotification,
30 PostActionExecutedNotification,
31 QuoteNotification,
32 ReactionNotification,
33 RepostNotification,
34 TokenDistributedNotification
35};
36
37interface ListProps {
38 feedType: string;
39}
40
41const List = ({ feedType }: ListProps) => {
42 const { setLastSeenNotificationId } = useNotificationStore();
43
44 const getNotificationType = useCallback(() => {
45 switch (feedType) {
46 case NotificationFeedType.All:
47 return;
48 case NotificationFeedType.Mentions:
49 return [NotificationType.Mentioned];
50 case NotificationFeedType.Comments:
51 return [NotificationType.Commented];
52 case NotificationFeedType.Likes:
53 return [NotificationType.Reacted];
54 case NotificationFeedType.PostActions:
55 return [NotificationType.ExecutedPostAction];
56 case NotificationFeedType.Rewards:
57 return [NotificationType.TokenDistributed];
58 default:
59 return;
60 }
61 }, [feedType]);
62
63 const request: NotificationRequest = {
64 filter: {
65 includeLowScore: false,
66 notificationTypes: getNotificationType()
67 }
68 };
69
70 const { data, error, fetchMore, loading } = useNotificationsQuery({
71 variables: { request }
72 });
73
74 const notifications = data?.notifications?.items;
75 const pageInfo = data?.notifications?.pageInfo;
76 const hasMore = !!pageInfo?.next;
77
78 useEffect(() => {
79 const firstNotification = notifications?.[0];
80 if (
81 !firstNotification ||
82 typeof firstNotification !== "object" ||
83 !("id" in firstNotification)
84 ) {
85 return;
86 }
87 const firstId = firstNotification.id;
88 if (firstId) {
89 setLastSeenNotificationId(firstId);
90 }
91 }, [notifications, setLastSeenNotificationId]);
92
93 const handleEndReached = useCallback(async () => {
94 if (hasMore) {
95 await fetchMore({
96 variables: { request: { ...request, cursor: pageInfo?.next } }
97 });
98 }
99 }, [fetchMore, hasMore, pageInfo?.next, request]);
100
101 const loadMoreRef = useLoadMoreOnIntersect(handleEndReached);
102
103 if (loading) {
104 return (
105 <Card className="divide-y divide-gray-200 dark:divide-gray-700">
106 <NotificationShimmer />
107 <NotificationShimmer />
108 <NotificationShimmer />
109 <NotificationShimmer />
110 </Card>
111 );
112 }
113
114 if (error) {
115 return <ErrorMessage error={error} title="Failed to load notifications" />;
116 }
117
118 if (!notifications?.length) {
119 return (
120 <EmptyState
121 icon={<BellIcon className="size-8" />}
122 message="Inbox zero!"
123 />
124 );
125 }
126
127 return (
128 <Card className="virtual-divider-list-window">
129 <WindowVirtualizer>
130 {notifications.map((notification) => {
131 if (!("id" in notification)) {
132 return null;
133 }
134
135 const Component =
136 notificationComponentMap[
137 notification.__typename as keyof typeof notificationComponentMap
138 ];
139
140 return (
141 <div
142 className={cn({
143 "p-5": notification.__typename !== "FollowNotification"
144 })}
145 key={notification.id}
146 >
147 {Component && <Component notification={notification as never} />}
148 </div>
149 );
150 })}
151 {hasMore && <span ref={loadMoreRef} />}
152 </WindowVirtualizer>
153 </Card>
154 );
155};
156
157export default memo(List);