diff --git a/packages/nextjs/components/address-vision/AddressCard.tsx b/packages/nextjs/components/address-vision/AddressCard.tsx index 32fd85b..53c2b77 100644 --- a/packages/nextjs/components/address-vision/AddressCard.tsx +++ b/packages/nextjs/components/address-vision/AddressCard.tsx @@ -1,151 +1,258 @@ -import { useEffect, useState } from "react"; +import { useState } from "react"; +import Link from "next/link"; import { CopyToClipboard } from "react-copy-to-clipboard"; -import { Address as AddressType } from "viem"; -import { useEnsName } from "wagmi"; -import { ArrowTopRightOnSquareIcon, CheckCircleIcon, DocumentDuplicateIcon } from "@heroicons/react/24/outline"; +import useSWR from "swr"; +import { isAddress } from "viem"; +import { + ArrowTopRightOnSquareIcon, + CheckCircleIcon, + DocumentDuplicateIcon, + EnvelopeIcon, + GlobeAltIcon, + LinkIcon, +} from "@heroicons/react/24/outline"; import { BlockieAvatar } from "~~/components/scaffold-eth"; import { useAddressStore } from "~~/services/store/store"; -import { getBlockExplorerAddressLink } from "~~/utils/scaffold-eth"; - -const getSize = (name: string) => { - if (name.includes("...")) return "3xl"; - if (name.length > 24) return "xl"; - if (name.length > 20) return "2xl"; - if (name.length > 12) return "3xl"; - return "4xl"; +import { + efpAccountFetcher, + efpStatsFetcher, + getBlockExplorerAddressLink, + getEfpAccountUrl, + getEfpStatsUrl, +} from "~~/utils/scaffold-eth"; + +// Simple SVG icons for social platforms (not in heroicons) +const TwitterIcon = ({ className }: { className?: string }) => ( + + + +); + +const GitHubIcon = ({ className }: { className?: string }) => ( + + + +); + +const TelegramIcon = ({ className }: { className?: string }) => ( + + + +); + +// Parse description text and convert @something.eth to clickable links +const parseDescriptionWithEnsLinks = (text: string) => { + const ensPattern = /@([\w-]+\.eth)/g; + const parts: (string | JSX.Element)[] = []; + let lastIndex = 0; + let match; + + while ((match = ensPattern.exec(text)) !== null) { + // Add text before the match + if (match.index > lastIndex) { + parts.push(text.slice(lastIndex, match.index)); + } + // Add the ENS link + const ensName = match[1]; + parts.push( + + @{ensName} + , + ); + lastIndex = match.index + match[0].length; + } + + // Add remaining text + if (lastIndex < text.length) { + parts.push(text.slice(lastIndex)); + } + + return parts.length > 0 ? parts : text; }; export const AddressCard = () => { - const [ensName, setEnsName] = useState(); - const [ensAvatar, setEnsAvatar] = useState(); const [addressCopied, setAddressCopied] = useState(false); - const [shortAddress, setShortAddress] = useState(""); - const { resolvedAddress: address } = useAddressStore(); - const { data: fetchedEnsName } = useEnsName({ - address: address as AddressType, - chainId: 1, + const shouldFetch = address && isAddress(address); + + const { data: accountData, error: accountError } = useSWR( + shouldFetch ? getEfpAccountUrl(address) : null, + efpAccountFetcher, + { revalidateOnFocus: false, revalidateOnReconnect: false, dedupingInterval: 60000 }, + ); + + const { data: statsData } = useSWR(shouldFetch ? getEfpStatsUrl(address) : null, efpStatsFetcher, { + revalidateOnFocus: false, + revalidateOnReconnect: false, + dedupingInterval: 60000, }); - useEffect(() => { - setEnsName(fetchedEnsName); - if (address) { - setShortAddress(address.slice(0, 6) + "..." + address.slice(-4)); - } - }, [fetchedEnsName, address]); - - useEffect(() => { - const fetchAvatar = async () => { - if (!fetchedEnsName) { - setEnsAvatar(null); - return; - } - - try { - const avatarURL = `https://metadata.ens.domains/mainnet/avatar/${fetchedEnsName}`; - const response = await fetch(avatarURL); - const contentType = response.headers.get("Content-Type"); - - if (contentType && contentType.includes("application/json")) { - const json = await response.json(); - if (json.message === "There is no avatar set under given address") { - setEnsAvatar(null); - } - return; - } - - const imageBlob = await response.blob(); - const imageURL = URL.createObjectURL(imageBlob); - setEnsAvatar(imageURL); - } catch (error) { - console.error("Error fetching ENS avatar:", error); - setEnsAvatar(null); - } - }; - - fetchAvatar(); - }, [fetchedEnsName]); - - if (!address) { + const isLoading = shouldFetch && accountData === undefined && !accountError; + + if (!address || isLoading) { return ( -
-
-
+
+
+
+
+
+
+
+
+
+
); } - const blockExplorerLink = getBlockExplorerAddressLink(address); - let displayName = shortAddress; + const ensName = accountData?.ens?.name || null; + const ensAvatar = accountData?.ens?.avatar || null; + const records = accountData?.ens?.records; + const description = records?.description; + const headerImage = records?.header; + const twitter = records?.["com.twitter"]; + const github = records?.["com.github"]; + const telegram = records?.["org.telegram"]; + const email = records?.email; + const website = records?.url; + const contenthash = records?.contenthash; - if (ensName) { - displayName = ensName; - } - - const size = getSize(displayName); - const textSizeClass = `text-${size}`; - const blockieSize = { - sm: 28, - base: 32, - lg: 40, - xl: 44, - "2xl": 48, - "3xl": 64, - "4xl": 64, - "5xl": 80, - }[size]; - - const copyAddressButton = (sizeClass: string) => - addressCopied ? ( -