Hey is a decentralized and permissionless social media app built with Lens Protocol 🌿
at main 128 lines 4.3 kB view raw
1import { ClipboardDocumentIcon } from "@heroicons/react/24/outline"; 2import { useQuery } from "@tanstack/react-query"; 3import { type GetCoinResponse, getCoin } from "@zoralabs/coins-sdk"; 4import { useMemo, useState } from "react"; 5import type { Address } from "viem"; 6import { base } from "viem/chains"; 7import Loader from "@/components/Shared/Loader"; 8import { Button, Image, Modal } from "@/components/Shared/UI"; 9import cn from "@/helpers/cn"; 10import humanize from "@/helpers/humanize"; 11import useCopyToClipboard from "@/hooks/useCopyToClipboard"; 12import Trade from "./Trade"; 13 14interface CreatorCoinDetailsProps { 15 address: Address; 16} 17 18const CreatorCoinDetails = ({ address }: CreatorCoinDetailsProps) => { 19 const { data: coin } = useQuery<GetCoinResponse["zora20Token"] | null>({ 20 enabled: !!address, 21 queryFn: async () => { 22 const coin = await getCoin({ address, chain: base.id }); 23 return coin.data?.zora20Token ?? null; 24 }, 25 queryKey: ["coin", address], 26 refetchInterval: 5000 27 }); 28 29 const [showTrade, setShowTrade] = useState(false); 30 const marketCap = useMemo(() => Number(coin?.marketCap ?? 0), [coin]); 31 const delta24h = useMemo(() => Number(coin?.marketCapDelta24h ?? 0), [coin]); 32 const changePct = useMemo(() => { 33 const prev = marketCap - delta24h; 34 if (!prev || !Number.isFinite(prev) || prev === 0) return 0; 35 return (delta24h / prev) * 100; 36 }, [marketCap, delta24h]); 37 38 const holders = coin?.uniqueHolders ?? 0; 39 const volume24h = Number(coin?.volume24h ?? 0); 40 41 const copyAddress = useCopyToClipboard(coin?.address ?? "", "Address copied"); 42 43 if (!coin) { 44 return <Loader className="my-10" />; 45 } 46 47 return ( 48 <div className="p-5"> 49 <div className="flex items-start justify-between gap-4"> 50 <div> 51 <div className="mb-1 text-gray-700 dark:text-gray-300"> 52 ${coin.symbol} 53 </div> 54 <div className="font-extrabold text-3xl leading-none tracking-tight md:text-4xl"> 55 ${humanize(Math.round(marketCap))} 56 </div> 57 <div 58 className={cn( 59 "mt-2 inline-flex items-center gap-1 font-medium text-sm", 60 changePct >= 0 61 ? "text-emerald-600 dark:text-emerald-400" 62 : "text-red-600 dark:text-red-400" 63 )} 64 > 65 <span>{changePct >= 0 ? "▲" : "▼"}</span> 66 <span>{`${changePct >= 0 ? "" : "-"}${Math.abs(changePct).toFixed(2)}%`}</span> 67 </div> 68 </div> 69 <div className="flex items-center gap-3"> 70 <Image 71 alt={coin.name} 72 className="size-12 rounded-full ring-2 ring-gray-200 dark:ring-gray-700" 73 height={48} 74 src={coin.mediaContent?.previewImage?.medium} 75 width={48} 76 /> 77 </div> 78 </div> 79 <div className="mt-6 grid grid-cols-2 gap-6"> 80 <div className="text-center"> 81 <div className="text-gray-500 text-sm dark:text-gray-400"> 82 Holders 83 </div> 84 <div className="font-semibold text-2xl">{humanize(holders)}</div> 85 </div> 86 <div className="text-center"> 87 <div className="text-gray-500 text-sm dark:text-gray-400"> 88 24h volume 89 </div> 90 <div className="font-semibold text-2xl"> 91 ${humanize(Math.round(volume24h))} 92 </div> 93 </div> 94 </div> 95 <div className="mt-6 flex flex-wrap items-center justify-center gap-3"> 96 <Button 97 onClick={() => 98 window.open( 99 `https://basescan.org/address/${coin.address}`, 100 "_blank" 101 ) 102 } 103 outline 104 size="sm" 105 > 106 Basescan 107 </Button> 108 <Button onClick={copyAddress} outline size="sm"> 109 <ClipboardDocumentIcon className="mr-1 size-4" /> Copy address 110 </Button> 111 </div> 112 <div className="mt-6"> 113 <Button className="w-full" onClick={() => setShowTrade(true)} size="lg"> 114 Trade 115 </Button> 116 </div> 117 <Modal 118 onClose={() => setShowTrade(false)} 119 show={showTrade} 120 title={`Trade $${coin.name}`} 121 > 122 <Trade coin={coin} onClose={() => setShowTrade(false)} /> 123 </Modal> 124 </div> 125 ); 126}; 127 128export default CreatorCoinDetails;