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
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
side={"bottom"}
1544
sideOffset={5}
0
1545
>
1546
<div className="flex flex-col gap-2">
1547
<div className="flex flex-row">
···
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
side={"bottom"}
1544
sideOffset={5}
1545
+
onClick={onProfileClick}
1546
>
1547
<div className="flex flex-col gap-2">
1548
<div className="flex flex-row">
+60
-41
src/routes/profile.$did/index.tsx
···
2
import { useQueryClient } from "@tanstack/react-query";
3
import { createFileRoute, useNavigate } from "@tanstack/react-router";
4
import { useAtom } from "jotai";
5
-
import React, { type ReactNode,useEffect, useState } from "react";
6
7
import { Header } from "~/components/Header";
8
-
import { renderTextWithFacets, UniversalPostRendererATURILoader } from "~/components/UniversalPostRenderer";
0
0
0
9
import { useAuth } from "~/providers/UnifiedAuthProvider";
10
import { imgCDNAtom } from "~/utils/atoms";
11
-
import { toggleFollow, useGetFollowState, useGetOneToOneState } from "~/utils/followState";
0
0
0
0
12
import {
13
useInfiniteQueryAuthorFeed,
14
useQueryIdentity,
···
172
<div className="mt-16 pb-2 px-4 text-gray-900 dark:text-gray-100">
173
<div className="font-bold text-2xl">{displayName}</div>
174
<div className="text-gray-500 dark:text-gray-400 text-base mb-3 flex flex-row gap-1">
175
-
<Mutual targetdidorhandle={did} />
176
{handle}
177
</div>
178
{description && (
179
<div className="text-base leading-relaxed text-gray-800 dark:text-gray-300 mb-5 whitespace-pre-wrap break-words text-[15px]">
180
{/* {description} */}
181
-
<RichTextRenderer key={did} description={description}/>
182
</div>
183
)}
184
</div>
···
222
);
223
}
224
225
-
export function FollowButton({targetdidorhandle}:{targetdidorhandle: string}) {
226
-
const {agent} = useAuth()
227
-
const {data: identity} = useQueryIdentity(targetdidorhandle);
0
0
0
0
228
const queryClient = useQueryClient();
229
230
const followRecords = useGetFollowState({
231
target: identity?.did ?? targetdidorhandle,
232
user: agent?.did,
233
});
234
-
235
return (
236
<>
237
{identity?.did !== agent?.did ? (
238
<>
239
{!(followRecords?.length && followRecords?.length > 0) ? (
240
<button
241
-
onClick={(e) =>
242
-
{
243
e.stopPropagation();
244
toggleFollow({
245
agent: agent || undefined,
246
targetDid: identity?.did,
247
followRecords: followRecords,
248
queryClient: queryClient,
249
-
})
250
-
}
251
-
}
252
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
>
254
Follow
255
</button>
256
) : (
257
<button
258
-
onClick={(e) =>
259
-
{
260
e.stopPropagation();
261
toggleFollow({
262
agent: agent || undefined,
263
targetDid: identity?.did,
264
followRecords: followRecords,
265
queryClient: queryClient,
266
-
})
267
-
}
268
-
}
269
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
>
271
Unfollow
···
281
);
282
}
283
0
0
0
284
285
-
export function Mutual({targetdidorhandle}:{targetdidorhandle: string}) {
286
-
const {agent} = useAuth()
287
-
const {data: identity} = useQueryIdentity(targetdidorhandle);
288
-
289
-
const theyFollowYouRes = useGetOneToOneState(agent?.did ? {
290
-
target: agent?.did,
291
-
user: identity?.did ?? targetdidorhandle,
292
-
collection: "app.bsky.graph.follow",
293
-
path: ".subject"
294
-
}:undefined);
295
296
const youFollowThemRes = useGetFollowState({
297
target: identity?.did ?? targetdidorhandle,
298
user: agent?.did,
299
});
300
301
-
const theyFollowYou: boolean = (!!theyFollowYouRes?.length && theyFollowYouRes.length > 0)
302
-
const youFollowThem: boolean = (!!youFollowThemRes?.length && youFollowThemRes.length > 0)
303
-
0
0
304
return (
305
<>
306
{/* if not self */}
307
{identity?.did !== agent?.did ? (
308
<>
309
-
{(theyFollowYou) ? (
310
<>
311
{youFollowThem ? (
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
-
) : (
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
-
)
316
-
}
0
0
0
317
</>
318
) : (
319
<></>
···
328
}
329
330
export function RichTextRenderer({ description }: { description: string }) {
331
-
const [richDescription, setRichDescription] = useState<string | ReactNode[]>(description);
0
0
332
const { agent } = useAuth();
333
const navigate = useNavigate();
334
···
346
if (!mounted) return;
347
348
if (rt.facets) {
349
-
setRichDescription(renderTextWithFacets({ text: rt.text, facets: rt.facets, navigate }));
0
0
350
} else {
351
setRichDescription(rt.text);
352
}
···
366
}, [description, agent, navigate]);
367
368
return <>{richDescription}</>;
369
-
}
···
2
import { useQueryClient } from "@tanstack/react-query";
3
import { createFileRoute, useNavigate } from "@tanstack/react-router";
4
import { useAtom } from "jotai";
5
+
import React, { type ReactNode, useEffect, useState } from "react";
6
7
import { Header } from "~/components/Header";
8
+
import {
9
+
renderTextWithFacets,
10
+
UniversalPostRendererATURILoader,
11
+
} from "~/components/UniversalPostRenderer";
12
import { useAuth } from "~/providers/UnifiedAuthProvider";
13
import { imgCDNAtom } from "~/utils/atoms";
14
+
import {
15
+
toggleFollow,
16
+
useGetFollowState,
17
+
useGetOneToOneState,
18
+
} from "~/utils/followState";
19
import {
20
useInfiniteQueryAuthorFeed,
21
useQueryIdentity,
···
179
<div className="mt-16 pb-2 px-4 text-gray-900 dark:text-gray-100">
180
<div className="font-bold text-2xl">{displayName}</div>
181
<div className="text-gray-500 dark:text-gray-400 text-base mb-3 flex flex-row gap-1">
182
+
<Mutual targetdidorhandle={did} />
183
{handle}
184
</div>
185
{description && (
186
<div className="text-base leading-relaxed text-gray-800 dark:text-gray-300 mb-5 whitespace-pre-wrap break-words text-[15px]">
187
{/* {description} */}
188
+
<RichTextRenderer key={did} description={description} />
189
</div>
190
)}
191
</div>
···
229
);
230
}
231
232
+
export function FollowButton({
233
+
targetdidorhandle,
234
+
}: {
235
+
targetdidorhandle: string;
236
+
}) {
237
+
const { agent } = useAuth();
238
+
const { data: identity } = useQueryIdentity(targetdidorhandle);
239
const queryClient = useQueryClient();
240
241
const followRecords = useGetFollowState({
242
target: identity?.did ?? targetdidorhandle,
243
user: agent?.did,
244
});
245
+
246
return (
247
<>
248
{identity?.did !== agent?.did ? (
249
<>
250
{!(followRecords?.length && followRecords?.length > 0) ? (
251
<button
252
+
onClick={(e) => {
0
253
e.stopPropagation();
254
toggleFollow({
255
agent: agent || undefined,
256
targetDid: identity?.did,
257
followRecords: followRecords,
258
queryClient: queryClient,
259
+
});
260
+
}}
0
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]"
262
>
263
Follow
264
</button>
265
) : (
266
<button
267
+
onClick={(e) => {
0
268
e.stopPropagation();
269
toggleFollow({
270
agent: agent || undefined,
271
targetDid: identity?.did,
272
followRecords: followRecords,
273
queryClient: queryClient,
274
+
});
275
+
}}
0
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]"
277
>
278
Unfollow
···
288
);
289
}
290
291
+
export function Mutual({ targetdidorhandle }: { targetdidorhandle: string }) {
292
+
const { agent } = useAuth();
293
+
const { data: identity } = useQueryIdentity(targetdidorhandle);
294
295
+
const theyFollowYouRes = useGetOneToOneState(
296
+
agent?.did
297
+
? {
298
+
target: agent?.did,
299
+
user: identity?.did ?? targetdidorhandle,
300
+
collection: "app.bsky.graph.follow",
301
+
path: ".subject",
302
+
}
303
+
: undefined
304
+
);
305
306
const youFollowThemRes = useGetFollowState({
307
target: identity?.did ?? targetdidorhandle,
308
user: agent?.did,
309
});
310
311
+
const theyFollowYou: boolean =
312
+
!!theyFollowYouRes?.length && theyFollowYouRes.length > 0;
313
+
const youFollowThem: boolean =
314
+
!!youFollowThemRes?.length && youFollowThemRes.length > 0;
315
+
316
return (
317
<>
318
{/* if not self */}
319
{identity?.did !== agent?.did ? (
320
<>
321
+
{theyFollowYou ? (
322
<>
323
{youFollowThem ? (
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
+
mutuals
326
+
</div>
327
+
) : (
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
+
follows you
330
+
</div>
331
+
)}
332
</>
333
) : (
334
<></>
···
343
}
344
345
export function RichTextRenderer({ description }: { description: string }) {
346
+
const [richDescription, setRichDescription] = useState<string | ReactNode[]>(
347
+
description
348
+
);
349
const { agent } = useAuth();
350
const navigate = useNavigate();
351
···
363
if (!mounted) return;
364
365
if (rt.facets) {
366
+
setRichDescription(
367
+
renderTextWithFacets({ text: rt.text, facets: rt.facets, navigate })
368
+
);
369
} else {
370
setRichDescription(rt.text);
371
}
···
385
}, [description, agent, navigate]);
386
387
return <>{richDescription}</>;
388
+
}