diff --git a/apps/dashboard/src/@/components/ui/tabs.tsx b/apps/dashboard/src/@/components/ui/tabs.tsx index 7bf4ab362b3..c672d2589e9 100644 --- a/apps/dashboard/src/@/components/ui/tabs.tsx +++ b/apps/dashboard/src/@/components/ui/tabs.tsx @@ -7,6 +7,7 @@ import { usePathname } from "next/navigation"; import { useCallback, useRef, useState } from "react"; import { ScrollShadow } from "./ScrollShadow/ScrollShadow"; import { Button } from "./button"; +import { ToolTipLabel } from "./tooltip"; export type TabLink = { name: string; @@ -82,6 +83,7 @@ export function TabButtons(props: { isActive: boolean; isDisabled?: boolean; icon?: React.FC<{ className?: string }>; + toolTip?: string; }[]; tabClassName?: string; activeTabClassName?: string; @@ -111,26 +113,31 @@ export function TabButtons(props: { > {props.tabs.map((tab, index) => { return ( - + + ); })} diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/_layout/metadata-header.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/_layout/metadata-header.tsx index 213c2d50fc9..38fcc1e2977 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/_layout/metadata-header.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/_layout/metadata-header.tsx @@ -75,7 +75,7 @@ export const MetadataHeader: React.FC = ({ ) : (
{data?.name && ( -

+

{data.name}

)} diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/token-id.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/token-id.tsx index 578a2a69f0c..f04543bfdd7 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/token-id.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/[tokenId]/token-id.tsx @@ -4,33 +4,31 @@ import { UnexpectedValueErrorMessage } from "@/components/blocks/error-fallbacks import { WalletAddress } from "@/components/blocks/wallet-address"; import { CopyTextButton } from "@/components/ui/CopyTextButton"; import { Spinner } from "@/components/ui/Spinner/Spinner"; +import { Badge } from "@/components/ui/badge"; +import { + Breadcrumb, + BreadcrumbItem, + BreadcrumbLink, + BreadcrumbList, + BreadcrumbSeparator, +} from "@/components/ui/breadcrumb"; +import { Button } from "@/components/ui/button"; import { CodeClient } from "@/components/ui/code/code.client"; +import { TabButtons } from "@/components/ui/tabs"; +import { ToolTipLabel } from "@/components/ui/tooltip"; import { useThirdwebClient } from "@/constants/thirdweb.client"; import { useDashboardRouter } from "@/lib/DashboardRouter"; import { resolveSchemeWithErrorHandler } from "@/lib/resolveSchemeWithErrorHandler"; import type { Account } from "@3rdweb-sdk/react/hooks/useApi"; -import { - Box, - ButtonGroup, - Divider, - Flex, - GridItem, - IconButton, - SimpleGrid, - Tooltip, - useBreakpointValue, -} from "@chakra-ui/react"; import { useChainSlug } from "hooks/chains/chainSlug"; -import { ChevronLeftIcon, ExternalLinkIcon } from "lucide-react"; +import { ExternalLinkIcon } from "lucide-react"; import Link from "next/link"; import { useState } from "react"; -import type { ThirdwebContract } from "thirdweb"; +import type { NFT, ThirdwebClient, ThirdwebContract } from "thirdweb"; import { getNFT as getErc721NFT } from "thirdweb/extensions/erc721"; import { getNFT as getErc1155NFT } from "thirdweb/extensions/erc1155"; import { useReadContract } from "thirdweb/react"; -import { Button, Card } from "tw-components"; import { NFTMediaWithEmptyState } from "tw-components/nft-media"; -import { shortenString } from "utils/usedapp-external"; import { NftProperty } from "../components/nft-property"; import { useNFTDrawerTabs } from "./useNftDrawerTabs"; @@ -66,7 +64,6 @@ export const TokenIdPage: React.FC = ({ twAccount, }) => { const [tab, setTab] = useState("Details"); - const isMobile = useBreakpointValue({ base: true, md: false }); const router = useDashboardRouter(); const chainId = contract.chain.id; const chainSlug = useChainSlug(chainId || 1); @@ -89,15 +86,6 @@ export const TokenIdPage: React.FC = ({ }, ); - const tokenURIHttpLink = resolveSchemeWithErrorHandler({ - client, - uri: nft?.tokenURI, - }); - const nftImageLink = resolveSchemeWithErrorHandler({ - client, - uri: nft?.metadata.image, - }); - if (isPending) { return (
@@ -123,230 +111,218 @@ export const TokenIdPage: React.FC = ({ : undefined, }; + return ( +
+
+
+ {/* border */} +
+ {/* media */} + +
+ +
+ {/* breadcrumb + title */} +
+ + + + { + e.preventDefault(); + router.push(`/${chainSlug}/${contract.address}/nfts`); + }} + > + NFTs + + + + + #{tokenId} + + + + +
+ + {nft.metadata?.description && ( + + )} +
+
+ + setTab("Details"), + isActive: tab === "Details", + isDisabled: false, + }, + ...tabs.map((tb) => ({ + name: tb.title, + onClick: () => setTab(tb.title), + isActive: tab === tb.title, + isDisabled: tb.isDisabled, + toolTip: tb.isDisabled ? tb.disabledText : undefined, + })), + ].sort((a, b) => (a.isDisabled ? 1 : b.isDisabled ? -1 : 0))} + /> + + {/* tab contents */} + {tab === "Details" && } + + {tabs.map((tb) => { + return ( + tb.title === tab && ( +
+ {tb.children} +
+ ) + ); + })} +
+
+
+ ); +}; + +function NFTDetailsTab(props: { + nft: NFT; + client: ThirdwebClient; +}) { + const { nft, client } = props; const properties = nft.metadata.attributes || nft.metadata.properties; return ( - - - - {/* TODO - replace this with breadcrumbs */} - - - router.push(`/${chainSlug}/${contract.address}/nfts`) +
+ {/* common */} +
+
+ {/* token id */} +
+

Token ID

+ 8 + ? `${nft.id.toString().slice(0, 4)}...${nft.id.toString().slice(-4)}` + : nft.id?.toString() } - aria-label="Back" - icon={} + tooltip="Token ID" + copyIconPosition="right" + className="min-w-16 justify-between bg-card" + /> +
+ + {/* owner */} + {nft.owner && ( +
+

Owner

+ +
+ )} + + {/* type */} +
+

Token Standard

+ - Back - - - - - - - - - {nft.metadata?.description && ( - + {nft.type} + +
+ + {/* supply */} + {nft.type !== "ERC721" && ( +
+

Supply

+ +

+ {nft.supply.toLocaleString("en-US")} +

+
+
)} - - - - -
+
+ + {/* attributes */} + {properties ? ( +
+

+ Attributes + {Array.isArray(properties) && properties.length > 0 && ( + - Details - - {tabs.map((tb) => ( - -

{tb.disabledText}

- - ) : ( - "" - ) - } - > - -
- ))} - - - - - - {tab === "Details" && ( - - - - -

Token ID

-
- - 8 - ? `${nft.id.toString().slice(0, 4)}...${nft.id.toString().slice(-4)}` - : nft.id?.toString() - } - tooltip="Token ID" - copyIconPosition="right" - /> - - - {nft.owner && ( - <> - -

Owner

-
- - - - - )} - -

Token Standard

-
- {nft.type} - {nft.type !== "ERC721" && ( - <> - -

Supply

-
- -

{nft.supply.toLocaleString("en-US")}

-
- - )} - -

Token URI

-
- - - {tokenURIHttpLink && ( - - )} - - {nft.metadata.image && ( - <> - -

Media URI

-
- - - {nftImageLink && ( - - )} - - - )} -
-
- {properties ? ( - -

Attributes

- {Array.isArray(properties) && - String(properties[0]?.value) !== "undefined" ? ( - - {/* biome-ignore lint/suspicious/noExplicitAny: FIXME */} - {properties.map((property: any, idx) => ( - // biome-ignore lint/suspicious/noArrayIndexKey: FIXME - - ))} - - ) : ( - - )} -
- ) : null} -
- )} - {tabs.map((tb) => { - return ( - tb.title === tab && ( - - {tb.children} - - ) - ); - })} - - + {properties.length} +
+ )} +

+
+ {Array.isArray(properties) && + String(properties[0]?.value) !== "undefined" ? ( +
+ {/* biome-ignore lint/suspicious/noExplicitAny: FIXME */} + {properties.map((property: any, idx) => ( + // biome-ignore lint/suspicious/noArrayIndexKey: FIXME + + ))} +
+ ) : ( + + )} +
+
+ ) : null} +
); -}; +} function NFTName(props: { value: unknown; }) { if (typeof props.value === "string") { return ( -

{props.value}

+

{props.value}

); } @@ -360,11 +336,49 @@ function NFTName(props: { ); } +function IPFSLinkGroup(props: { + ipfsLink: string; + tooltip: string; + client: ThirdwebClient; + httpsLinkTooltip: string; +}) { + const httpLink = resolveSchemeWithErrorHandler({ + client: props.client, + uri: props.ipfsLink, + }); + + return ( +
+ + {httpLink && ( + + + + )} +
+ ); +} + function NFTDescription(props: { value: unknown; }) { if (typeof props.value === "string") { - return

{props.value}

; + return

{props.value}

; } return ( diff --git a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/nft-property.tsx b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/nft-property.tsx index b1649327ca8..72593ec36b5 100644 --- a/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/nft-property.tsx +++ b/apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/nfts/components/nft-property.tsx @@ -1,5 +1,3 @@ -import { Card } from "@/components/ui/card"; - interface NftPropertyProps { // biome-ignore lint/suspicious/noExplicitAny: FIXME property: any; @@ -7,17 +5,17 @@ interface NftPropertyProps { export const NftProperty: React.FC = ({ property }) => { return ( - +
{property?.trait_type && ( -

+

{property?.trait_type}

)} -

+

{typeof property?.value === "object" ? JSON.stringify(property?.value || {}) : property?.value}

- +
); };