a tool for shared writing and social publishing

ping notifications and fix reply notifications

+42 -20
+5 -2
app/(home-pages)/notifications/NotificationList.tsx
··· 5 import { useEffect, createContext } from "react"; 6 import { markAsRead } from "./getNotifications"; 7 import { ReplyNotification } from "./ReplyNotification"; 8 9 export function NotificationList({ 10 notifications, ··· 13 notifications: HydratedNotification[]; 14 compact?: boolean; 15 }) { 16 useEffect(() => { 17 - setTimeout(() => { 18 - markAsRead(); 19 }, 500); 20 }, []); 21
··· 5 import { useEffect, createContext } from "react"; 6 import { markAsRead } from "./getNotifications"; 7 import { ReplyNotification } from "./ReplyNotification"; 8 + import { useIdentityData } from "components/IdentityProvider"; 9 10 export function NotificationList({ 11 notifications, ··· 14 notifications: HydratedNotification[]; 15 compact?: boolean; 16 }) { 17 + let { mutate } = useIdentityData(); 18 useEffect(() => { 19 + setTimeout(async () => { 20 + await markAsRead(); 21 + mutate(); 22 }, 500); 23 }, []); 24
+13 -17
app/lish/[did]/[publication]/[rkey]/Interactions/Comments/commentAction.ts
··· 8 import { AtUri, lexToJson, Un$Typed } from "@atproto/api"; 9 import { supabaseServerClient } from "supabase/serverClient"; 10 import { Json } from "supabase/database.types"; 11 - import { Notification } from "src/notifications"; 12 import { v7 } from "uuid"; 13 14 export async function publishComment(args: { ··· 68 }) 69 .select(); 70 let notifications: Notification[] = []; 71 - if ( 72 - !args.comment.replyTo && 73 - new AtUri(args.document).host !== credentialSession.did 74 - ) 75 notifications.push({ 76 id: v7(), 77 - recipient: new AtUri(args.document).host, 78 - data: { type: "comment", comment_uri: uri.toString() }, 79 - }); 80 - if ( 81 - args.comment.replyTo && 82 - new AtUri(args.comment.replyTo).host !== credentialSession.did 83 - ) 84 - notifications.push({ 85 - id: v7(), 86 - recipient: new AtUri(args.comment.replyTo).host, 87 data: { 88 type: "comment", 89 comment_uri: uri.toString(), 90 parent_uri: args.comment.replyTo, 91 }, 92 }); 93 - // SOMEDAY: move this out the action with inngest or workflows 94 - await supabaseServerClient.from("notifications").insert(notifications); 95 96 return { 97 record: data?.[0].record as Json,
··· 8 import { AtUri, lexToJson, Un$Typed } from "@atproto/api"; 9 import { supabaseServerClient } from "supabase/serverClient"; 10 import { Json } from "supabase/database.types"; 11 + import { 12 + Notification, 13 + pingIdentityToUpdateNotification, 14 + } from "src/notifications"; 15 import { v7 } from "uuid"; 16 17 export async function publishComment(args: { ··· 71 }) 72 .select(); 73 let notifications: Notification[] = []; 74 + let recipient = args.comment.replyTo 75 + ? new AtUri(args.comment.replyTo).host 76 + : new AtUri(args.document).host; 77 + if (recipient !== credentialSession.did) { 78 notifications.push({ 79 id: v7(), 80 + recipient, 81 data: { 82 type: "comment", 83 comment_uri: uri.toString(), 84 parent_uri: args.comment.replyTo, 85 }, 86 }); 87 + // SOMEDAY: move this out the action with inngest or workflows 88 + await supabaseServerClient.from("notifications").insert(notifications); 89 + await pingIdentityToUpdateNotification(recipient); 90 + } 91 92 return { 93 record: data?.[0].record as Json,
+14 -1
components/IdentityProvider.tsx
··· 1 "use client"; 2 import { getIdentityData } from "actions/getIdentityData"; 3 - import { createContext, useContext } from "react"; 4 import useSWR, { KeyedMutator, mutate } from "swr"; 5 import { DashboardState } from "./PageLayouts/DashboardLayout"; 6 7 export type InterfaceState = { 8 dashboards: { [id: string]: DashboardState | undefined }; ··· 20 let { data: identity, mutate } = useSWR("identity", () => getIdentityData(), { 21 fallbackData: props.initialValue, 22 }); 23 return ( 24 <IdentityContext.Provider value={{ identity, mutate }}> 25 {props.children}
··· 1 "use client"; 2 import { getIdentityData } from "actions/getIdentityData"; 3 + import { createContext, useContext, useEffect } from "react"; 4 import useSWR, { KeyedMutator, mutate } from "swr"; 5 import { DashboardState } from "./PageLayouts/DashboardLayout"; 6 + import { supabaseBrowserClient } from "supabase/browserClient"; 7 8 export type InterfaceState = { 9 dashboards: { [id: string]: DashboardState | undefined }; ··· 21 let { data: identity, mutate } = useSWR("identity", () => getIdentityData(), { 22 fallbackData: props.initialValue, 23 }); 24 + useEffect(() => { 25 + if (!identity?.atp_did) return; 26 + let supabase = supabaseBrowserClient(); 27 + let channel = supabase.channel(`identity.atp_did:${identity.atp_did}`); 28 + channel.on("broadcast", { event: "notification" }, () => { 29 + mutate(); 30 + }); 31 + channel.subscribe(); 32 + return () => { 33 + channel.unsubscribe(); 34 + }; 35 + }, [identity?.atp_did]); 36 return ( 37 <IdentityContext.Provider value={{ identity, mutate }}> 38 {props.children}
+10
src/notifications.ts
··· 127 ), 128 })); 129 }
··· 127 ), 128 })); 129 } 130 + 131 + export async function pingIdentityToUpdateNotification(did: string) { 132 + let channel = supabaseServerClient.channel(`identity.atp_did:${did}`); 133 + await channel.send({ 134 + type: "broadcast", 135 + event: "notification", 136 + payload: { message: "poke" }, 137 + }); 138 + await supabaseServerClient.removeChannel(channel); 139 + }