Hey is a decentralized and permissionless social media app built with Lens Protocol 馃尶
at main 169 lines 4.9 kB view raw
1import { BeakerIcon, CheckBadgeIcon } from "@heroicons/react/24/solid"; 2import getAccount from "@hey/helpers/getAccount"; 3import getAvatar from "@hey/helpers/getAvatar"; 4import { type AccountStats, useFullAccountLazyQuery } from "@hey/indexer"; 5import * as HoverCard from "@radix-ui/react-hover-card"; 6import plur from "plur"; 7import type { ReactNode } from "react"; 8import Markup from "@/components/Shared/Markup"; 9import Slug from "@/components/Shared/Slug"; 10import { Card, Image } from "@/components/Shared/UI"; 11import getMentions from "@/helpers/getMentions"; 12import nFormatter from "@/helpers/nFormatter"; 13import truncateByWords from "@/helpers/truncateByWords"; 14import ENSBadge from "./ENSBadge"; 15import FollowUnfollowButton from "./FollowUnfollowButton"; 16 17interface AccountPreviewProps { 18 children: ReactNode; 19 username?: string; 20 address?: string; 21 showUserPreview?: boolean; 22} 23 24const AccountPreview = ({ 25 children, 26 username, 27 address, 28 showUserPreview = true 29}: AccountPreviewProps) => { 30 const [loadAccount, { data, loading }] = useFullAccountLazyQuery(); 31 const account = data?.account; 32 const stats = data?.accountStats as AccountStats; 33 34 const onPreviewStart = async () => { 35 if (account || loading) { 36 return; 37 } 38 39 await loadAccount({ 40 variables: { 41 accountRequest: { 42 ...(address 43 ? { address } 44 : { username: { localName: username as string } }) 45 }, 46 accountStatsRequest: { account: address } 47 } 48 }); 49 }; 50 51 if (!address && !username) { 52 return null; 53 } 54 55 if (!showUserPreview) { 56 return <span>{children}</span>; 57 } 58 59 const Preview = () => { 60 if (loading) { 61 return ( 62 <div className="flex flex-col"> 63 <div className="flex p-3"> 64 <div>{username || `#${address}`}</div> 65 </div> 66 </div> 67 ); 68 } 69 70 if (!account) { 71 return ( 72 <div className="flex h-12 items-center px-3">No account found</div> 73 ); 74 } 75 76 const UserAvatar = () => ( 77 <Image 78 alt={account.address} 79 className="size-12 rounded-full border border-gray-200 bg-gray-200 dark:border-gray-700" 80 height={48} 81 loading="lazy" 82 src={getAvatar(account)} 83 width={48} 84 /> 85 ); 86 87 const UserName = () => ( 88 <div> 89 <div className="flex max-w-sm items-center gap-1 truncate"> 90 <div>{getAccount(account).name}</div> 91 {account.hasSubscribed && ( 92 <CheckBadgeIcon className="size-4 text-brand-500" /> 93 )} 94 {account.isBeta && <BeakerIcon className="size-4 text-green-500" />} 95 <ENSBadge account={account} className="size-4" /> 96 </div> 97 <span> 98 <Slug className="text-sm" slug={getAccount(account).username} /> 99 {account.operations?.isFollowingMe && ( 100 <span className="ml-2 rounded-full bg-gray-200 px-2 py-0.5 text-xs dark:bg-gray-700"> 101 Follows you 102 </span> 103 )} 104 </span> 105 </div> 106 ); 107 108 return ( 109 <div className="space-y-3 p-4"> 110 <div className="flex items-center justify-between"> 111 <UserAvatar /> 112 <FollowUnfollowButton account={account} small /> 113 </div> 114 <UserName /> 115 {account.metadata?.bio && ( 116 <div className="linkify mt-2 break-words text-sm leading-6"> 117 <Markup mentions={getMentions(account.metadata.bio)}> 118 {truncateByWords(account.metadata.bio, 20)} 119 </Markup> 120 </div> 121 )} 122 <div className="flex items-center space-x-3"> 123 <div className="flex items-center space-x-1"> 124 <div className="text-base"> 125 {nFormatter(stats.graphFollowStats?.following)} 126 </div> 127 <div className="text-gray-500 text-sm dark:text-gray-200"> 128 Following 129 </div> 130 </div> 131 <div className="flex items-center space-x-1"> 132 <div className="text-base"> 133 {nFormatter(stats.graphFollowStats?.followers)} 134 </div> 135 <div className="text-gray-500 text-sm dark:text-gray-200"> 136 {plur("Follower", stats.graphFollowStats?.followers)} 137 </div> 138 </div> 139 </div> 140 </div> 141 ); 142 }; 143 144 return ( 145 <span onFocus={onPreviewStart} onMouseOver={onPreviewStart}> 146 <HoverCard.Root> 147 <HoverCard.Trigger asChild> 148 <span>{children}</span> 149 </HoverCard.Trigger> 150 <HoverCard.Portal> 151 <HoverCard.Content 152 asChild 153 className="z-10 w-72" 154 side="bottom" 155 sideOffset={5} 156 > 157 <div> 158 <Card forceRounded> 159 <Preview /> 160 </Card> 161 </div> 162 </HoverCard.Content> 163 </HoverCard.Portal> 164 </HoverCard.Root> 165 </span> 166 ); 167}; 168 169export default AccountPreview;