a tool for shared writing and social publishing
at update/reader 104 lines 3.2 kB view raw
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};