tangled
alpha
login
or
join now
leaflet.pub
/
leaflet
289
fork
atom
a tool for shared writing and social publishing
289
fork
atom
overview
issues
28
pulls
pipelines
add follow notification
awarm.space
4 months ago
6c48cd30
889e4e4c
+56
-21
4 changed files
expand all
collapse all
unified
split
app
(home-pages)
notifications
FollowNotification.tsx
NotificationList.tsx
lish
subscribeToPublication.ts
src
notifications.ts
+23
-7
app/(home-pages)/notifications/FollowNotification.tsx
···
1
1
import { Avatar } from "components/Avatar";
2
2
import { Notification } from "./Notification";
3
3
+
import { HydratedSubscribeNotification } from "src/notifications";
4
4
+
import { blobRefToSrc } from "src/utils/blobRefToSrc";
5
5
+
import { AppBskyActorProfile, PubLeafletPublication } from "lexicons/api";
3
6
4
4
-
export const DummyFollowNotification = (props: {}) => {
5
5
-
const identity = "celine";
6
6
-
const pubName = "Pub Name Here";
7
7
+
export const FollowNotification = (props: HydratedSubscribeNotification) => {
8
8
+
const profileRecord = props.subscriptionData?.identities?.bsky_profiles
9
9
+
?.record as AppBskyActorProfile.Record;
10
10
+
const displayName =
11
11
+
profileRecord?.displayName ||
12
12
+
props.subscriptionData?.identities?.bsky_profiles?.handle ||
13
13
+
"Someone";
14
14
+
const pubRecord = props.subscriptionData?.publications
15
15
+
?.record as PubLeafletPublication.Record;
16
16
+
const avatarSrc =
17
17
+
profileRecord?.avatar?.ref &&
18
18
+
blobRefToSrc(
19
19
+
profileRecord.avatar.ref,
20
20
+
props.subscriptionData?.identity || "",
21
21
+
);
22
22
+
7
23
return (
8
24
<Notification
9
9
-
timestamp={""}
10
10
-
href="/"
11
11
-
icon={<Avatar src={undefined} displayName={identity} tiny />}
25
25
+
timestamp={props.created_at}
26
26
+
href={`https://${pubRecord?.base_path}`}
27
27
+
icon={<Avatar src={avatarSrc} displayName={displayName} tiny />}
12
28
actionText={
13
29
<>
14
14
-
{identity} followed {pubName}!
30
30
+
{displayName} subscribed to {pubRecord?.name}!
15
31
</>
16
32
}
17
33
/>
+4
app/(home-pages)/notifications/NotificationList.tsx
···
6
6
import { markAsRead } from "./getNotifications";
7
7
import { ReplyNotification } from "./ReplyNotification";
8
8
import { useIdentityData } from "components/IdentityProvider";
9
9
+
import { FollowNotification } from "./FollowNotification";
9
10
10
11
export function NotificationList({
11
12
notifications,
···
35
36
if (n.type === "comment") {
36
37
if (n.parentData) return <ReplyNotification key={n.id} {...n} />;
37
38
return <CommentNotification key={n.id} {...n} />;
39
39
+
}
40
40
+
if (n.type === "subscribe") {
41
41
+
return <FollowNotification key={n.id} {...n} />;
38
42
}
39
43
})}
40
44
</div>
+21
app/lish/subscribeToPublication.ts
···
12
12
import { encodeActionToSearchParam } from "app/api/oauth/[route]/afterSignInActions";
13
13
import { Json } from "supabase/database.types";
14
14
import { IdResolver } from "@atproto/identity";
15
15
+
import {
16
16
+
Notification,
17
17
+
pingIdentityToUpdateNotification,
18
18
+
} from "src/notifications";
19
19
+
import { v7 } from "uuid";
15
20
16
21
let leafletFeedURI =
17
22
"at://did:plc:btxrwcaeyodrap5mnjw2fvmz/app.bsky.feed.generator/subscribedPublications";
···
46
51
publication,
47
52
identity: credentialSession.did!,
48
53
});
54
54
+
55
55
+
// Create notification for the publication owner
56
56
+
let publicationOwner = new AtUri(publication).host;
57
57
+
if (publicationOwner !== credentialSession.did) {
58
58
+
let notification: Notification = {
59
59
+
id: v7(),
60
60
+
recipient: publicationOwner,
61
61
+
data: {
62
62
+
type: "subscribe",
63
63
+
subscription_uri: record.uri,
64
64
+
},
65
65
+
};
66
66
+
await supabaseServerClient.from("notifications").insert(notification);
67
67
+
await pingIdentityToUpdateNotification(publicationOwner);
68
68
+
}
69
69
+
49
70
let bsky = new BskyAgent(credentialSession);
50
71
let [prefs, profile, resolveDid] = await Promise.all([
51
72
bsky.app.bsky.actor.getPreferences(),
+8
-14
src/notifications.ts
···
85
85
}));
86
86
}
87
87
88
88
-
export type HydratedSubscribeNotification = {
89
89
-
id: string;
90
90
-
recipient: string;
91
91
-
created_at: string;
92
92
-
type: "subscribe";
93
93
-
subscription_uri: string;
94
94
-
subscriptionData?: Tables<"publication_subscriptions">;
95
95
-
};
96
96
-
async function hydrateSubscribeNotifications(
97
97
-
notifications: NotificationRow[],
98
98
-
): Promise<HydratedSubscribeNotification[]> {
88
88
+
export type HydratedSubscribeNotification = Awaited<
89
89
+
ReturnType<typeof hydrateSubscribeNotifications>
90
90
+
>[0];
91
91
+
92
92
+
async function hydrateSubscribeNotifications(notifications: NotificationRow[]) {
99
93
const subscribeNotifications = notifications.filter(
100
94
(
101
95
n,
···
107
101
return [];
108
102
}
109
103
110
110
-
// Fetch subscription data from the database
104
104
+
// Fetch subscription data from the database with related data
111
105
const subscriptionUris = subscribeNotifications.map(
112
106
(n) => n.data.subscription_uri,
113
107
);
114
108
const { data: subscriptions } = await supabaseServerClient
115
109
.from("publication_subscriptions")
116
116
-
.select("*")
110
110
+
.select("*, identities(bsky_profiles(*)), publications(*)")
117
111
.in("uri", subscriptionUris);
118
112
119
113
return subscribeNotifications.map((notification) => ({
···
124
118
subscription_uri: notification.data.subscription_uri,
125
119
subscriptionData: subscriptions?.find(
126
120
(s) => s.uri === notification.data.subscription_uri,
127
127
-
),
121
121
+
)!,
128
122
}));
129
123
}
130
124