forked from
leaflet.pub/leaflet
a tool for shared writing and social publishing
1# Notification System
2
3## Overview
4
5Notifications are stored in the database and hydrated with related data before being rendered. The system supports multiple notification types (comments, subscriptions, etc.) that are processed in parallel.
6
7## Key Files
8
9- **`src/notifications.ts`** - Core notification types and hydration logic
10- **`app/(home-pages)/notifications/NotificationList.tsx`** - Renders all notification types
11- **`app/(home-pages)/notifications/Notification.tsx`** - Base notification component
12- Individual notification components (e.g., `CommentNotification.tsx`, `FollowNotification.tsx`)
13
14## Adding a New Notification Type
15
16### 1. Update Notification Data Types (`src/notifications.ts`)
17
18Add your type to the `NotificationData` union:
19
20```typescript
21export type NotificationData =
22 | { type: "comment"; comment_uri: string; parent_uri?: string }
23 | { type: "subscribe"; subscription_uri: string }
24 | { type: "your_type"; your_field: string }; // Add here
25```
26
27Add to the `HydratedNotification` union:
28
29```typescript
30export type HydratedNotification =
31 | HydratedCommentNotification
32 | HydratedSubscribeNotification
33 | HydratedYourNotification; // Add here
34```
35
36### 2. Create Hydration Function (`src/notifications.ts`)
37
38```typescript
39export type HydratedYourNotification = Awaited<
40 ReturnType<typeof hydrateYourNotifications>
41>[0];
42
43async function hydrateYourNotifications(notifications: NotificationRow[]) {
44 const yourNotifications = notifications.filter(
45 (n): n is NotificationRow & { data: ExtractNotificationType<"your_type"> } =>
46 (n.data as NotificationData)?.type === "your_type",
47 );
48
49 if (yourNotifications.length === 0) return [];
50
51 // Fetch related data with joins
52 const { data } = await supabaseServerClient
53 .from("your_table")
54 .select("*, related_table(*)")
55 .in("uri", yourNotifications.map((n) => n.data.your_field));
56
57 return yourNotifications.map((notification) => ({
58 id: notification.id,
59 recipient: notification.recipient,
60 created_at: notification.created_at,
61 type: "your_type" as const,
62 your_field: notification.data.your_field,
63 yourData: data?.find((d) => d.uri === notification.data.your_field)!,
64 }));
65}
66```
67
68Add to `hydrateNotifications` parallel array:
69
70```typescript
71const [commentNotifications, subscribeNotifications, yourNotifications] = await Promise.all([
72 hydrateCommentNotifications(notifications),
73 hydrateSubscribeNotifications(notifications),
74 hydrateYourNotifications(notifications), // Add here
75]);
76
77const allHydrated = [...commentNotifications, ...subscribeNotifications, ...yourNotifications];
78```
79
80### 3. Trigger the Notification (in your action file)
81
82```typescript
83import { Notification, pingIdentityToUpdateNotification } from "src/notifications";
84import { v7 } from "uuid";
85
86// When the event occurs:
87const recipient = /* determine who should receive it */;
88if (recipient !== currentUser) {
89 const notification: Notification = {
90 id: v7(),
91 recipient,
92 data: {
93 type: "your_type",
94 your_field: "value",
95 },
96 };
97 await supabaseServerClient.from("notifications").insert(notification);
98 await pingIdentityToUpdateNotification(recipient);
99}
100```
101
102### 4. Create Notification Component
103
104Create a new component (e.g., `YourNotification.tsx`):
105
106```typescript
107import { HydratedYourNotification } from "src/notifications";
108import { Notification } from "./Notification";
109
110export const YourNotification = (props: HydratedYourNotification) => {
111 // Extract data from props.yourData
112
113 return (
114 <Notification
115 timestamp={props.created_at}
116 href={/* link to relevant page */}
117 icon={/* icon or avatar */}
118 actionText={<>Message to display</>}
119 content={/* optional additional content */}
120 />
121 );
122};
123```
124
125### 5. Update NotificationList (`NotificationList.tsx`)
126
127Import and render your notification type:
128
129```typescript
130import { YourNotification } from "./YourNotification";
131
132// In the map function:
133if (n.type === "your_type") {
134 return <YourNotification key={n.id} {...n} />;
135}
136```
137
138## Example: Subscribe Notifications
139
140See the implementation in:
141- `src/notifications.ts:88-125` - Hydration logic
142- `app/lish/subscribeToPublication.ts:55-68` - Trigger
143- `app/(home-pages)/notifications/FollowNotification.tsx` - Component
144- `app/(home-pages)/notifications/NotificationList.tsx:40-42` - Rendering