a tool for shared writing and social publishing
1"use client";
2import { Popover } from "./Popover";
3import useSWR from "swr";
4import { callRPC } from "app/api/rpc/client";
5import { useRef, useState } from "react";
6import { ProfileHeader } from "app/(home-pages)/p/[didOrHandle]/ProfileHeader";
7import { SpeedyLink } from "./SpeedyLink";
8import { Tooltip } from "./Tooltip";
9import { ProfileViewDetailed } from "@atproto/api/dist/client/types/app/bsky/actor/defs";
10
11export const ProfilePopover = (props: {
12 trigger: React.ReactNode;
13 didOrHandle: string;
14}) => {
15 const [isOpen, setIsOpen] = useState(false);
16 let [isHovered, setIsHovered] = useState(false);
17 const hoverTimeout = useRef<null | number>(null);
18
19 const { data, isLoading } = useSWR(
20 isHovered ? ["profile-data", props.didOrHandle] : null,
21 async () => {
22 const response = await callRPC("get_profile_data", {
23 didOrHandle: props.didOrHandle,
24 });
25 return response.result;
26 },
27 );
28
29 return (
30 <Tooltip
31 className="max-w-sm p-0! text-center"
32 asChild
33 trigger={
34 <a
35 className="no-underline"
36 href={`https://leaflet.pub/p/${props.didOrHandle}`}
37 target="_blank"
38 onPointerEnter={(e) => {
39 if (hoverTimeout.current) {
40 window.clearTimeout(hoverTimeout.current);
41 }
42 hoverTimeout.current = window.setTimeout(async () => {
43 setIsHovered(true);
44 }, 150);
45 }}
46 onPointerLeave={() => {
47 if (isHovered) return;
48 if (hoverTimeout.current) {
49 window.clearTimeout(hoverTimeout.current);
50 hoverTimeout.current = null;
51 }
52 setIsHovered(false);
53 }}
54 >
55 {props.trigger}
56 </a>
57 }
58 onOpenChange={setIsOpen}
59 >
60 {isLoading ? (
61 <div className="text-secondary p-4">Loading...</div>
62 ) : data ? (
63 <div>
64 <ProfileHeader
65 profile={data.profile}
66 publications={data.publications}
67 popover
68 />
69 <KnownFollowers viewer={data.profile.viewer} did={data.profile.did} />
70 </div>
71 ) : (
72 <div className="text-secondary py-2 px-4">Profile not found</div>
73 )}
74 </Tooltip>
75 );
76};
77
78let KnownFollowers = (props: {
79 viewer: ProfileViewDetailed["viewer"];
80 did: string;
81}) => {
82 if (!props.viewer?.knownFollowers) return null;
83 let count = props.viewer.knownFollowers.count;
84 return (
85 <>
86 <hr className="border-border" />
87 Followed by{" "}
88 <a
89 className="hover:underline"
90 href={`https://bsky.social/profile/${props.did}/known-followers`}
91 target="_blank"
92 >
93 {props.viewer?.knownFollowers?.followers[0]?.displayName}{" "}
94 {count > 1 ? `and ${count - 1} other${count > 2 ? "s" : ""}` : ""}
95 </a>
96 </>
97 );
98};