From 913ea98d23799f5716ece819b1493b405187c70e Mon Sep 17 00:00:00 2001 From: MananTank Date: Mon, 16 Jun 2025 17:56:26 +0000 Subject: [PATCH] [TOOL-4802] NFT Asset Page (#7332) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ## PR-Codex overview This PR focuses on enhancing the handling of NFTs and contract components within the application, including updates to types, UI components, and functionality related to NFT collections and claims. ### Detailed summary - Updated `NFTMetadata` type to include `slug`. - Added `slug` property in various components. - Introduced `supplyFormatter` for better NFT supply formatting. - Changed contract type handling to `NFTCollection`. - Enhanced `MinimalProject` type to include `slug`. - Updated UI components for better user interaction. - Improved pagination and NFT display logic. - Added new utilities for NFT claim conditions and total counts. - Refactored claim token UI to utilize new components and structures. - Implemented responsive layouts for better mobile support. > The following files were skipped due to too many changes: `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/overview/nfts-grid.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/overview/buy-nft-drop/buy-nft-drop-ui.client.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsx` > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` ## Summary by CodeRabbit - **New Features** - Introduced public NFT pages for ERC-721 and ERC-1155 contracts with responsive layouts, NFT grids, token viewer dialogs, and buy functionality. - Added components for token price display, supply claimed progress, and enhanced pagination controls. - Enabled NFT purchases from drops with dedicated UI and transaction handling for ERC-721 and ERC-1155 standards. - Added tabbed navigation for public NFT pages and improved media rendering with loading placeholders. - Introduced a responsive layout component for device-specific rendering and customizable wallet address focus behavior. - Added new components for buying NFTs and editions with form validation, pricing display, and transaction feedback. - Added a token price component with loading skeleton and strikethrough styling. - Added a supply claimed progress bar with detailed percentage display. - Introduced a token viewer dialog with dynamic data fetching and rich metadata display. - Added a paginated NFT grid with detailed claim condition info for ERC-1155 tokens. - Added buy NFT drop card components with server and client rendering support. - Added a custom media renderer component with loading state management. - **Enhancements** - Improved contract and page header components with customizable styling options. - Expanded project and team data structures to include project slugs for better navigation. - Updated wallet address and pagination components with additional props for accessibility and styling. - Refined tracking of click and keyboard events for asset cards. - Simplified purchase UI code by delegating price and supply display to shared components. - Enhanced pagination to handle two-page scenarios with simplified controls. - Updated public page rendering logic to support multiple ERC standards with unified detection. - Improved purchase flows with analytics tracking and toast notifications. - Added support for special user pricing and dynamic claim condition fetching. - **Bug Fixes** - Fixed conditional rendering and redirection logic for public NFT pages based on contract type and metadata presence. - **Documentation** - Added Storybook stories for new UI components including supply claimed progress and NFT drop purchase flows. - **Refactor** - Delegated price and supply display logic to shared components for consistency. - Updated types and props to support new features and improve maintainability. - **Chores** - Broadened NFT metadata field types for improved compatibility with diverse data formats. - Added fallback for React experimental API to prevent errors in unsupported environments. --- .changeset/red-cooks-juggle.md | 5 + .../dashboard/src/@/components/Responsive.tsx | 25 + .../@/components/blocks/media-renderer.tsx | 37 ++ .../@/components/blocks/wallet-address.tsx | 6 +- .../src/@/components/pagination-buttons.tsx | 31 +- apps/dashboard/src/@/constants/server-envs.ts | 6 +- .../[contractAddress]/_utils/newPublicPage.ts | 30 +- .../nfts/[tokenId]/shared-nfts-token-page.tsx | 18 + .../nfts/shared-nfts-page.tsx | 13 + .../public-pages/_components/PageHeader.tsx | 14 +- .../supply-claimed-progress.stories.tsx | 46 ++ .../_components/supply-claimed-progress.tsx | 50 ++ .../public-pages/_components/token-price.tsx | 39 ++ .../erc20/_components/ContractHeader.tsx | 6 +- .../claim-tokens/claim-tokens-ui.stories.tsx | 4 +- .../claim-tokens/claim-tokens-ui.tsx | 169 +++---- .../public-pages/erc20/erc20.tsx | 4 +- .../public-pages/nft/client-utils.ts | 157 +++++++ .../public-pages/nft/format.ts | 4 + .../public-pages/nft/nft-page-layout.tsx | 42 ++ .../public-pages/nft/nft-page.tsx | 175 +++++++ .../buy-edition-drop.client.tsx | 351 ++++++++++++++ .../buy-nft-drop/buy-nft-drop-card.client.tsx | 17 + .../buy-nft-drop/buy-nft-drop-card.server.tsx | 99 ++++ .../buy-nft-drop/buy-nft-drop-ui.client.tsx | 378 +++++++++++++++ .../buy-nft-drop/buy-nft-drop-ui.stories.tsx | 270 +++++++++++ .../buy-nft-drop/buy-nft-drop.client.tsx | 224 +++++++++ .../public-pages/nft/overview/nfts-grid.tsx | 363 +++++++++++++++ .../public-pages/nft/overview/tabs.tsx | 35 ++ .../nft/token-viewer/token-viewer.tsx | 431 ++++++++++++++++++ .../public-pages/nft/utils.ts | 37 ++ .../[contractAddress]/shared-layout.tsx | 1 + .../shared-overview-page.tsx | 15 + .../components/uri-based-deploy.tsx | 1 + .../[project_slug]/(sidebar)/assets/cards.tsx | 24 +- .../assets/create/nft/_common/tracking.ts | 3 +- .../add-to-project-card.stories.tsx | 1 + .../add-to-project-card.tsx | 2 +- .../contract-deploy-form/custom-contract.tsx | 12 +- packages/thirdweb/src/utils/nft/parseNft.ts | 4 +- 40 files changed, 2995 insertions(+), 154 deletions(-) create mode 100644 .changeset/red-cooks-juggle.md create mode 100644 apps/dashboard/src/@/components/Responsive.tsx create mode 100644 apps/dashboard/src/@/components/blocks/media-renderer.tsx create mode 100644 apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/_components/supply-claimed-progress.stories.tsx create mode 100644 apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/_components/supply-claimed-progress.tsx create mode 100644 apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/_components/token-price.tsx create mode 100644 apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/client-utils.ts create mode 100644 apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/format.ts create mode 100644 apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page-layout.tsx create mode 100644 apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/nft-page.tsx create mode 100644 apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/overview/buy-edition-drop/buy-edition-drop.client.tsx create mode 100644 apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/overview/buy-nft-drop/buy-nft-drop-card.client.tsx create mode 100644 apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/overview/buy-nft-drop/buy-nft-drop-card.server.tsx create mode 100644 apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/overview/buy-nft-drop/buy-nft-drop-ui.client.tsx create mode 100644 apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/overview/buy-nft-drop/buy-nft-drop-ui.stories.tsx create mode 100644 apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/overview/buy-nft-drop/buy-nft-drop.client.tsx create mode 100644 apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/overview/nfts-grid.tsx create mode 100644 apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/overview/tabs.tsx create mode 100644 apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/token-viewer/token-viewer.tsx create mode 100644 apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/nft/utils.ts diff --git a/.changeset/red-cooks-juggle.md b/.changeset/red-cooks-juggle.md new file mode 100644 index 00000000000..061f3b9c6ba --- /dev/null +++ b/.changeset/red-cooks-juggle.md @@ -0,0 +1,5 @@ +--- +"thirdweb": patch +--- + +Fix `NFTMetadata` type diff --git a/apps/dashboard/src/@/components/Responsive.tsx b/apps/dashboard/src/@/components/Responsive.tsx new file mode 100644 index 00000000000..b5a6a88d198 --- /dev/null +++ b/apps/dashboard/src/@/components/Responsive.tsx @@ -0,0 +1,25 @@ +"use client"; +import { Suspense } from "react"; +import { ClientOnly } from "../../components/ClientOnly/ClientOnly"; +import { useIsMobile } from "../hooks/use-mobile"; + +export function ResponsiveLayout(props: { + desktop: React.ReactNode; + mobile: React.ReactNode; + fallback: React.ReactNode; + debugMode?: boolean; +}) { + const isMobile = useIsMobile(); + + if (props.debugMode) { + return props.fallback; + } + + return ( + + + {isMobile ? props.mobile : props.desktop} + + + ); +} diff --git a/apps/dashboard/src/@/components/blocks/media-renderer.tsx b/apps/dashboard/src/@/components/blocks/media-renderer.tsx new file mode 100644 index 00000000000..929a21ef416 --- /dev/null +++ b/apps/dashboard/src/@/components/blocks/media-renderer.tsx @@ -0,0 +1,37 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { MediaRenderer, type MediaRendererProps } from "thirdweb/react"; +import { cn } from "../../lib/utils"; +import { Skeleton } from "../ui/skeleton"; + +export function CustomMediaRenderer(props: MediaRendererProps) { + const [loadedSrc, setLoadedSrc] = useState(undefined); + + // eslint-disable-next-line no-restricted-syntax + useEffect(() => { + if (loadedSrc && loadedSrc !== props.src) { + setLoadedSrc(undefined); + } + }, [loadedSrc, props.src]); + + return ( +
{ + if (props.src) { + setLoadedSrc(props.src); + } + }} + > + {!loadedSrc && } + div]:!bg-accent [&_a]:!text-muted-foreground [&_a]:!no-underline [&_svg]:!size-6 [&_svg]:!text-muted-foreground relative z-10", + )} + /> +
+ ); +} diff --git a/apps/dashboard/src/@/components/blocks/wallet-address.tsx b/apps/dashboard/src/@/components/blocks/wallet-address.tsx index 5993b1a6845..848d647c340 100644 --- a/apps/dashboard/src/@/components/blocks/wallet-address.tsx +++ b/apps/dashboard/src/@/components/blocks/wallet-address.tsx @@ -24,6 +24,7 @@ export function WalletAddress(props: { className?: string; iconClassName?: string; client: ThirdwebClient; + preventOpenOnFocus?: boolean; }) { // default back to zero address if no address provided const address = useMemo(() => props.address || ZERO_ADDRESS, [props.address]); @@ -64,7 +65,10 @@ export function WalletAddress(props: { return ( - +