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";
10import { BlueskyTiny } from "./Icons/BlueskyTiny";
11import { ArrowRightTiny } from "./Icons/ArrowRightTiny";
12
13export const ProfilePopover = (props: {
14 trigger: React.ReactNode;
15 didOrHandle: string;
16}) => {
17 const [isOpen, setIsOpen] = useState(false);
18 let [isHovered, setIsHovered] = useState(false);
19 const hoverTimeout = useRef<null | number>(null);
20
21 const { data, isLoading } = useSWR(
22 isHovered ? ["profile-data", props.didOrHandle] : null,
23 async () => {
24 const response = await callRPC("get_profile_data", {
25 didOrHandle: props.didOrHandle,
26 });
27 return response.result;
28 },
29 );
30
31 return (
32 <Popover
33 className="max-w-sm p-0! text-center"
34 trigger={
35 <div
36 className="no-underline relative"
37 onPointerEnter={(e) => {
38 if (hoverTimeout.current) {
39 window.clearTimeout(hoverTimeout.current);
40 }
41 hoverTimeout.current = window.setTimeout(async () => {
42 setIsHovered(true);
43 }, 150);
44 }}
45 onPointerLeave={() => {
46 if (isHovered) return;
47 if (hoverTimeout.current) {
48 window.clearTimeout(hoverTimeout.current);
49 hoverTimeout.current = null;
50 }
51 setIsHovered(false);
52 }}
53 >
54 {props.trigger}
55 </div>
56 }
57 onOpenChange={setIsOpen}
58 >
59 {isLoading ? (
60 <div className="text-secondary p-4">Loading...</div>
61 ) : data ? (
62 <div>
63 <ProfileHeader
64 profile={data.profile}
65 publications={data.publications}
66 popover
67 />
68
69 <ProfileLinks handle={data.profile.handle} />
70 </div>
71 ) : (
72 <div className="text-secondary py-2 px-4">No profile found...</div>
73 )}
74 </Popover>
75 );
76};
77
78const ProfileLinks = (props: { handle: string }) => {
79 let linkClassName =
80 "flex gap-1.5 text-tertiary items-center border border-transparent px-1 rounded-md hover:bg-[var(--accent-light)] hover:border-accent-contrast hover:text-accent-contrast no-underline hover:no-underline";
81 return (
82 <div className="w-full flex-col">
83 <hr className="border-border-light mt-3" />
84 <div className="flex gap-2 justify-between sm:px-4 px-3 py-2">
85 <div className="flex gap-2">
86 <a
87 href={`https://bsky.app/profile/${props.handle}`}
88 target="_blank"
89 className={linkClassName}
90 >
91 <BlueskyTiny />
92 Bluesky
93 </a>
94 </div>
95 <SpeedyLink
96 href={`https://leaflet.pub/p/${props.handle}`}
97 className={linkClassName}
98 >
99 Full profile <ArrowRightTiny />
100 </SpeedyLink>
101 </div>
102 </div>
103 );
104};