From 4936d34c1b198151b6890d9ea22bc2f4e0853973 Mon Sep 17 00:00:00 2001 From: Jared Pereira Date: Thu, 13 Nov 2025 13:10:56 -0500 Subject: [PATCH] add notification pattern --- patterns/notifications.md | 144 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 patterns/notifications.md diff --git a/patterns/notifications.md b/patterns/notifications.md new file mode 100644 index 00000000..c2619b53 --- /dev/null +++ b/patterns/notifications.md @@ -0,0 +1,144 @@ +# Notification System + +## Overview + +Notifications 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. + +## Key Files + +- **`src/notifications.ts`** - Core notification types and hydration logic +- **`app/(home-pages)/notifications/NotificationList.tsx`** - Renders all notification types +- **`app/(home-pages)/notifications/Notification.tsx`** - Base notification component +- Individual notification components (e.g., `CommentNotification.tsx`, `FollowNotification.tsx`) + +## Adding a New Notification Type + +### 1. Update Notification Data Types (`src/notifications.ts`) + +Add your type to the `NotificationData` union: + +```typescript +export type NotificationData = + | { type: "comment"; comment_uri: string; parent_uri?: string } + | { type: "subscribe"; subscription_uri: string } + | { type: "your_type"; your_field: string }; // Add here +``` + +Add to the `HydratedNotification` union: + +```typescript +export type HydratedNotification = + | HydratedCommentNotification + | HydratedSubscribeNotification + | HydratedYourNotification; // Add here +``` + +### 2. Create Hydration Function (`src/notifications.ts`) + +```typescript +export type HydratedYourNotification = Awaited< + ReturnType +>[0]; + +async function hydrateYourNotifications(notifications: NotificationRow[]) { + const yourNotifications = notifications.filter( + (n): n is NotificationRow & { data: ExtractNotificationType<"your_type"> } => + (n.data as NotificationData)?.type === "your_type", + ); + + if (yourNotifications.length === 0) return []; + + // Fetch related data with joins + const { data } = await supabaseServerClient + .from("your_table") + .select("*, related_table(*)") + .in("uri", yourNotifications.map((n) => n.data.your_field)); + + return yourNotifications.map((notification) => ({ + id: notification.id, + recipient: notification.recipient, + created_at: notification.created_at, + type: "your_type" as const, + your_field: notification.data.your_field, + yourData: data?.find((d) => d.uri === notification.data.your_field)!, + })); +} +``` + +Add to `hydrateNotifications` parallel array: + +```typescript +const [commentNotifications, subscribeNotifications, yourNotifications] = await Promise.all([ + hydrateCommentNotifications(notifications), + hydrateSubscribeNotifications(notifications), + hydrateYourNotifications(notifications), // Add here +]); + +const allHydrated = [...commentNotifications, ...subscribeNotifications, ...yourNotifications]; +``` + +### 3. Trigger the Notification (in your action file) + +```typescript +import { Notification, pingIdentityToUpdateNotification } from "src/notifications"; +import { v7 } from "uuid"; + +// When the event occurs: +const recipient = /* determine who should receive it */; +if (recipient !== currentUser) { + const notification: Notification = { + id: v7(), + recipient, + data: { + type: "your_type", + your_field: "value", + }, + }; + await supabaseServerClient.from("notifications").insert(notification); + await pingIdentityToUpdateNotification(recipient); +} +``` + +### 4. Create Notification Component + +Create a new component (e.g., `YourNotification.tsx`): + +```typescript +import { HydratedYourNotification } from "src/notifications"; +import { Notification } from "./Notification"; + +export const YourNotification = (props: HydratedYourNotification) => { + // Extract data from props.yourData + + return ( + Message to display} + content={/* optional additional content */} + /> + ); +}; +``` + +### 5. Update NotificationList (`NotificationList.tsx`) + +Import and render your notification type: + +```typescript +import { YourNotification } from "./YourNotification"; + +// In the map function: +if (n.type === "your_type") { + return ; +} +``` + +## Example: Subscribe Notifications + +See the implementation in: +- `src/notifications.ts:88-125` - Hydration logic +- `app/lish/subscribeToPublication.ts:55-68` - Trigger +- `app/(home-pages)/notifications/FollowNotification.tsx` - Component +- `app/(home-pages)/notifications/NotificationList.tsx:40-42` - Rendering -- 2.51.2