@streamplace/components 0.7.32 → 0.7.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,232 @@
1
+ import { AppBskyGraphFollow } from "@atproto/api";
2
+ import { useEffect, useState } from "react";
3
+ import { useStreamplaceStore } from "./streamplace-store";
4
+ import { usePDSAgent } from "./xrpc";
5
+
6
+ export function useCreateFollowRecord() {
7
+ let agent = usePDSAgent();
8
+ const [isLoading, setIsLoading] = useState(false);
9
+
10
+ const createFollow = async (subjectDID: string) => {
11
+ if (!agent) {
12
+ throw new Error("No PDS agent found");
13
+ }
14
+
15
+ if (!agent.did) {
16
+ throw new Error("No user DID found, assuming not logged in");
17
+ }
18
+
19
+ setIsLoading(true);
20
+ try {
21
+ const record: AppBskyGraphFollow.Record = {
22
+ $type: "app.bsky.graph.follow",
23
+ subject: subjectDID,
24
+ createdAt: new Date().toISOString(),
25
+ };
26
+ const result = await agent.com.atproto.repo.createRecord({
27
+ repo: agent.did,
28
+ collection: "app.bsky.graph.follow",
29
+ record,
30
+ });
31
+ return result;
32
+ } finally {
33
+ setIsLoading(false);
34
+ }
35
+ };
36
+
37
+ return { createFollow, isLoading };
38
+ }
39
+
40
+ export function useDeleteFollowRecord() {
41
+ let agent = usePDSAgent();
42
+ const [isLoading, setIsLoading] = useState(false);
43
+
44
+ const deleteFollow = async (followRecordUri: string) => {
45
+ if (!agent) {
46
+ throw new Error("No PDS agent found");
47
+ }
48
+
49
+ if (!agent.did) {
50
+ throw new Error("No user DID found, assuming not logged in");
51
+ }
52
+
53
+ setIsLoading(true);
54
+ try {
55
+ const result = await agent.com.atproto.repo.deleteRecord({
56
+ repo: agent.did,
57
+ collection: "app.bsky.graph.follow",
58
+ rkey: followRecordUri.split("/").pop()!,
59
+ });
60
+ return result;
61
+ } finally {
62
+ setIsLoading(false);
63
+ }
64
+ };
65
+
66
+ return { deleteFollow, isLoading };
67
+ }
68
+
69
+ interface GraphManagerState {
70
+ isFollowing: boolean | null;
71
+ followUri: string | null;
72
+ isLoading: boolean;
73
+ error: string | null;
74
+ }
75
+
76
+ interface GraphManagerActions {
77
+ follow: () => Promise<void>;
78
+ unfollow: () => Promise<void>;
79
+ refresh: () => Promise<void>;
80
+ }
81
+
82
+ export function useGraphManager(
83
+ subjectDID: string | null | undefined,
84
+ ): GraphManagerState & GraphManagerActions {
85
+ const agent = usePDSAgent();
86
+ const [isFollowing, setIsFollowing] = useState<boolean | null>(null);
87
+ const [followUri, setFollowUri] = useState<string | null>(null);
88
+ const [isLoading, setIsLoading] = useState(false);
89
+ const [error, setError] = useState<string | null>(null);
90
+
91
+ const userDID = agent?.did;
92
+
93
+ const streamplaceUrl = useStreamplaceStore((state) => state.url);
94
+
95
+ const fetchFollowStatus = async () => {
96
+ if (!userDID || !subjectDID || !streamplaceUrl) {
97
+ setIsFollowing(null);
98
+ setFollowUri(null);
99
+ return;
100
+ }
101
+
102
+ setIsLoading(true);
103
+ setError(null);
104
+ try {
105
+ const res = await fetch(
106
+ `${streamplaceUrl}/xrpc/place.stream.graph.getFollowingUser?subjectDID=${encodeURIComponent(subjectDID)}&userDID=${encodeURIComponent(userDID)}`,
107
+ {
108
+ credentials: "include",
109
+ },
110
+ );
111
+
112
+ if (!res.ok) {
113
+ const errorText = await res.text();
114
+ throw new Error(`Failed to fetch follow status: ${errorText}`);
115
+ }
116
+
117
+ const data = await res.json();
118
+
119
+ if (data.follow) {
120
+ setIsFollowing(true);
121
+ setFollowUri(data.follow.uri);
122
+ } else {
123
+ setIsFollowing(false);
124
+ setFollowUri(null);
125
+ }
126
+ } catch (err) {
127
+ setError(
128
+ `Could not determine follow state: ${err instanceof Error ? err.message : `Unknown error: ${err}`}`,
129
+ );
130
+ setIsFollowing(null);
131
+ } finally {
132
+ setIsLoading(false);
133
+ }
134
+ };
135
+
136
+ useEffect(() => {
137
+ if (!userDID || !subjectDID) {
138
+ setIsFollowing(null);
139
+ setFollowUri(null);
140
+ setError(null);
141
+ return;
142
+ }
143
+
144
+ fetchFollowStatus();
145
+ }, [userDID, subjectDID, streamplaceUrl]);
146
+
147
+ const follow = async () => {
148
+ if (!agent || !subjectDID) {
149
+ throw new Error("Cannot follow: not logged in or no subject DID");
150
+ }
151
+
152
+ if (!agent.did) {
153
+ throw new Error("No user DID found, assuming not logged in");
154
+ }
155
+
156
+ setIsLoading(true);
157
+ setError(null);
158
+ const previousState = isFollowing;
159
+ setIsFollowing(true); // Optimistic
160
+
161
+ try {
162
+ const record: AppBskyGraphFollow.Record = {
163
+ $type: "app.bsky.graph.follow",
164
+ subject: subjectDID,
165
+ createdAt: new Date().toISOString(),
166
+ };
167
+ const result = await agent.com.atproto.repo.createRecord({
168
+ repo: agent.did,
169
+ collection: "app.bsky.graph.follow",
170
+ record,
171
+ });
172
+ setFollowUri(result.data.uri);
173
+ setIsFollowing(true);
174
+ } catch (err) {
175
+ setIsFollowing(previousState);
176
+ const errorMsg = `Failed to follow: ${err instanceof Error ? err.message : "Unknown error"}`;
177
+ setError(errorMsg);
178
+ throw new Error(errorMsg);
179
+ } finally {
180
+ setIsLoading(false);
181
+ }
182
+ };
183
+
184
+ const unfollow = async () => {
185
+ if (!agent || !subjectDID) {
186
+ throw new Error("Cannot unfollow: not logged in or no subject DID");
187
+ }
188
+
189
+ if (!agent.did) {
190
+ throw new Error("No user DID found, assuming not logged in");
191
+ }
192
+
193
+ if (!followUri) {
194
+ throw new Error("Cannot unfollow: no follow URI found");
195
+ }
196
+
197
+ setIsLoading(true);
198
+ setError(null);
199
+ const previousState = isFollowing;
200
+ const previousUri = followUri;
201
+ setIsFollowing(false); // Optimistic
202
+ setFollowUri(null);
203
+
204
+ try {
205
+ await agent.com.atproto.repo.deleteRecord({
206
+ repo: agent.did,
207
+ collection: "app.bsky.graph.follow",
208
+ rkey: followUri.split("/").pop()!,
209
+ });
210
+ setIsFollowing(false);
211
+ setFollowUri(null);
212
+ } catch (err) {
213
+ setIsFollowing(previousState);
214
+ setFollowUri(previousUri);
215
+ const errorMsg = `Failed to unfollow: ${err instanceof Error ? err.message : "Unknown error"}`;
216
+ setError(errorMsg);
217
+ throw new Error(errorMsg);
218
+ } finally {
219
+ setIsLoading(false);
220
+ }
221
+ };
222
+
223
+ return {
224
+ isFollowing,
225
+ followUri,
226
+ isLoading,
227
+ error,
228
+ follow,
229
+ unfollow,
230
+ refresh: fetchFollowStatus,
231
+ };
232
+ }