From 1dae74b9d20f97f2f269eb4ab53548d7f0753e91 Mon Sep 17 00:00:00 2001 From: MananTank Date: Mon, 7 Apr 2025 16:38:03 +0000 Subject: [PATCH] Dashboard: Update NFT token details page styles, reduce chakra usage (#6665) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## PR-Codex overview This PR focuses on improving the UI components of the dashboard, enhancing the styling and structure of various elements, and adding new features like tooltips and breadcrumbs for better navigation. ### Detailed summary - Changed `className` from `font-semibold` to `font-bold` in the `h1` element. - Updated `NftProperty` component to use `div` instead of `Card`. - Added `toolTip` property to `TabButtons`. - Wrapped `Button` in `ToolTipLabel` for tooltips. - Introduced `IPFSLinkGroup` component for displaying IPFS links. - Enhanced `NFTName` and `NFTDescription` styling. - Removed unnecessary imports and unused variables for cleaner code. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` --- apps/dashboard/src/@/components/ui/tabs.tsx | 45 +- .../_layout/metadata-header.tsx | 2 +- .../nfts/[tokenId]/token-id.tsx | 484 +++++++++--------- .../nfts/components/nft-property.tsx | 10 +- 4 files changed, 280 insertions(+), 261 deletions(-) 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}

- +
); };