tangled
alpha
login
or
join now
whey.party
/
red-dwarf
82
fork
atom
an independent Bluesky client using Constellation, PDS Queries, and other services
reddwarf.app
frontend
spa
bluesky
reddwarf
microcosm
client
app
82
fork
atom
overview
issues
25
pulls
pipelines
clickable profile hover card
rimar1337
4 months ago
96fdf44f
b3faaa61
+61
-41
2 changed files
expand all
collapse all
unified
split
src
components
UniversalPostRenderer.tsx
routes
profile.$did
index.tsx
+1
src/components/UniversalPostRenderer.tsx
···
1542
1542
className="rounded-md p-4 w-72 bg-gray-50 dark:bg-gray-900 shadow-lg border border-gray-300 dark:border-gray-800 animate-slide-fade z-50"
1543
1543
side={"bottom"}
1544
1544
sideOffset={5}
1545
1545
+
onClick={onProfileClick}
1545
1546
>
1546
1547
<div className="flex flex-col gap-2">
1547
1548
<div className="flex flex-row">
+60
-41
src/routes/profile.$did/index.tsx
···
2
2
import { useQueryClient } from "@tanstack/react-query";
3
3
import { createFileRoute, useNavigate } from "@tanstack/react-router";
4
4
import { useAtom } from "jotai";
5
5
-
import React, { type ReactNode,useEffect, useState } from "react";
5
5
+
import React, { type ReactNode, useEffect, useState } from "react";
6
6
7
7
import { Header } from "~/components/Header";
8
8
-
import { renderTextWithFacets, UniversalPostRendererATURILoader } from "~/components/UniversalPostRenderer";
8
8
+
import {
9
9
+
renderTextWithFacets,
10
10
+
UniversalPostRendererATURILoader,
11
11
+
} from "~/components/UniversalPostRenderer";
9
12
import { useAuth } from "~/providers/UnifiedAuthProvider";
10
13
import { imgCDNAtom } from "~/utils/atoms";
11
11
-
import { toggleFollow, useGetFollowState, useGetOneToOneState } from "~/utils/followState";
14
14
+
import {
15
15
+
toggleFollow,
16
16
+
useGetFollowState,
17
17
+
useGetOneToOneState,
18
18
+
} from "~/utils/followState";
12
19
import {
13
20
useInfiniteQueryAuthorFeed,
14
21
useQueryIdentity,
···
172
179
<div className="mt-16 pb-2 px-4 text-gray-900 dark:text-gray-100">
173
180
<div className="font-bold text-2xl">{displayName}</div>
174
181
<div className="text-gray-500 dark:text-gray-400 text-base mb-3 flex flex-row gap-1">
175
175
-
<Mutual targetdidorhandle={did} />
182
182
+
<Mutual targetdidorhandle={did} />
176
183
{handle}
177
184
</div>
178
185
{description && (
179
186
<div className="text-base leading-relaxed text-gray-800 dark:text-gray-300 mb-5 whitespace-pre-wrap break-words text-[15px]">
180
187
{/* {description} */}
181
181
-
<RichTextRenderer key={did} description={description}/>
188
188
+
<RichTextRenderer key={did} description={description} />
182
189
</div>
183
190
)}
184
191
</div>
···
222
229
);
223
230
}
224
231
225
225
-
export function FollowButton({targetdidorhandle}:{targetdidorhandle: string}) {
226
226
-
const {agent} = useAuth()
227
227
-
const {data: identity} = useQueryIdentity(targetdidorhandle);
232
232
+
export function FollowButton({
233
233
+
targetdidorhandle,
234
234
+
}: {
235
235
+
targetdidorhandle: string;
236
236
+
}) {
237
237
+
const { agent } = useAuth();
238
238
+
const { data: identity } = useQueryIdentity(targetdidorhandle);
228
239
const queryClient = useQueryClient();
229
240
230
241
const followRecords = useGetFollowState({
231
242
target: identity?.did ?? targetdidorhandle,
232
243
user: agent?.did,
233
244
});
234
234
-
245
245
+
235
246
return (
236
247
<>
237
248
{identity?.did !== agent?.did ? (
238
249
<>
239
250
{!(followRecords?.length && followRecords?.length > 0) ? (
240
251
<button
241
241
-
onClick={(e) =>
242
242
-
{
252
252
+
onClick={(e) => {
243
253
e.stopPropagation();
244
254
toggleFollow({
245
255
agent: agent || undefined,
246
256
targetDid: identity?.did,
247
257
followRecords: followRecords,
248
258
queryClient: queryClient,
249
249
-
})
250
250
-
}
251
251
-
}
259
259
+
});
260
260
+
}}
252
261
className="rounded-full h-10 bg-gray-300 dark:bg-gray-600 hover:bg-gray-400 dark:hover:bg-gray-500 transition-colors px-4 py-2 text-[14px]"
253
262
>
254
263
Follow
255
264
</button>
256
265
) : (
257
266
<button
258
258
-
onClick={(e) =>
259
259
-
{
267
267
+
onClick={(e) => {
260
268
e.stopPropagation();
261
269
toggleFollow({
262
270
agent: agent || undefined,
263
271
targetDid: identity?.did,
264
272
followRecords: followRecords,
265
273
queryClient: queryClient,
266
266
-
})
267
267
-
}
268
268
-
}
274
274
+
});
275
275
+
}}
269
276
className="rounded-full h-10 bg-gray-300 dark:bg-gray-600 hover:bg-gray-400 dark:hover:bg-gray-500 transition-colors px-4 py-2 text-[14px]"
270
277
>
271
278
Unfollow
···
281
288
);
282
289
}
283
290
291
291
+
export function Mutual({ targetdidorhandle }: { targetdidorhandle: string }) {
292
292
+
const { agent } = useAuth();
293
293
+
const { data: identity } = useQueryIdentity(targetdidorhandle);
284
294
285
285
-
export function Mutual({targetdidorhandle}:{targetdidorhandle: string}) {
286
286
-
const {agent} = useAuth()
287
287
-
const {data: identity} = useQueryIdentity(targetdidorhandle);
288
288
-
289
289
-
const theyFollowYouRes = useGetOneToOneState(agent?.did ? {
290
290
-
target: agent?.did,
291
291
-
user: identity?.did ?? targetdidorhandle,
292
292
-
collection: "app.bsky.graph.follow",
293
293
-
path: ".subject"
294
294
-
}:undefined);
295
295
+
const theyFollowYouRes = useGetOneToOneState(
296
296
+
agent?.did
297
297
+
? {
298
298
+
target: agent?.did,
299
299
+
user: identity?.did ?? targetdidorhandle,
300
300
+
collection: "app.bsky.graph.follow",
301
301
+
path: ".subject",
302
302
+
}
303
303
+
: undefined
304
304
+
);
295
305
296
306
const youFollowThemRes = useGetFollowState({
297
307
target: identity?.did ?? targetdidorhandle,
298
308
user: agent?.did,
299
309
});
300
310
301
301
-
const theyFollowYou: boolean = (!!theyFollowYouRes?.length && theyFollowYouRes.length > 0)
302
302
-
const youFollowThem: boolean = (!!youFollowThemRes?.length && youFollowThemRes.length > 0)
303
303
-
311
311
+
const theyFollowYou: boolean =
312
312
+
!!theyFollowYouRes?.length && theyFollowYouRes.length > 0;
313
313
+
const youFollowThem: boolean =
314
314
+
!!youFollowThemRes?.length && youFollowThemRes.length > 0;
315
315
+
304
316
return (
305
317
<>
306
318
{/* if not self */}
307
319
{identity?.did !== agent?.did ? (
308
320
<>
309
309
-
{(theyFollowYou) ? (
321
321
+
{theyFollowYou ? (
310
322
<>
311
323
{youFollowThem ? (
312
312
-
<div className=" text-sm px-1.5 py-0.5 text-gray-500 bg-gray-200 dark:text-gray-400 dark:bg-gray-800 rounded-lg flex flex-row items-center justify-center">mutuals</div>
313
313
-
) : (
314
314
-
<div className=" text-sm px-1.5 py-0.5 text-gray-500 bg-gray-200 dark:text-gray-400 dark:bg-gray-800 rounded-lg flex flex-row items-center justify-center">follows you</div>
315
315
-
)
316
316
-
}
324
324
+
<div className=" text-sm px-1.5 py-0.5 text-gray-500 bg-gray-200 dark:text-gray-400 dark:bg-gray-800 rounded-lg flex flex-row items-center justify-center">
325
325
+
mutuals
326
326
+
</div>
327
327
+
) : (
328
328
+
<div className=" text-sm px-1.5 py-0.5 text-gray-500 bg-gray-200 dark:text-gray-400 dark:bg-gray-800 rounded-lg flex flex-row items-center justify-center">
329
329
+
follows you
330
330
+
</div>
331
331
+
)}
317
332
</>
318
333
) : (
319
334
<></>
···
328
343
}
329
344
330
345
export function RichTextRenderer({ description }: { description: string }) {
331
331
-
const [richDescription, setRichDescription] = useState<string | ReactNode[]>(description);
346
346
+
const [richDescription, setRichDescription] = useState<string | ReactNode[]>(
347
347
+
description
348
348
+
);
332
349
const { agent } = useAuth();
333
350
const navigate = useNavigate();
334
351
···
346
363
if (!mounted) return;
347
364
348
365
if (rt.facets) {
349
349
-
setRichDescription(renderTextWithFacets({ text: rt.text, facets: rt.facets, navigate }));
366
366
+
setRichDescription(
367
367
+
renderTextWithFacets({ text: rt.text, facets: rt.facets, navigate })
368
368
+
);
350
369
} else {
351
370
setRichDescription(rt.text);
352
371
}
···
366
385
}, [description, agent, navigate]);
367
386
368
387
return <>{richDescription}</>;
369
369
-
}
388
388
+
}