"use client"; import { type UseQueryOptions, useQuery } from "@tanstack/react-query"; import type { JSX } from "react"; import type { ThirdwebContract } from "../../../../../contract/contract.js"; import { getFunctionId } from "../../../../../utils/function-id.js"; import { MediaRenderer } from "../../MediaRenderer/MediaRenderer.js"; import type { MediaRendererProps } from "../../MediaRenderer/types.js"; import { useNFTContext } from "./provider.js"; import { getNFTInfo } from "./utils.js"; /** * @component * @beta * @wallet */ export type NFTMediaInfo = { src: string; poster: string | undefined; }; /** * @component * @beta * @wallet * The props for the component * It is similar to the [`MediaRendererProps`](https://portal.thirdweb.com/references/typescript/v5/MediaRendererProps) * (excluding `src`, `poster` and `client`) that you can * use to style the NFTMedia */ export type NFTMediaProps = Omit< MediaRendererProps, "src" | "poster" | "client" > & { loadingComponent?: JSX.Element; fallbackComponent?: JSX.Element; /** * Optional `useQuery` params */ queryOptions?: Omit, "queryFn" | "queryKey">; /** * This prop can be a string or a (async) function that resolves to a string, representing the media url of the NFT * This is particularly useful if you already have a way to fetch the image. * In case of function, the function must resolve to an object of type `NFTMediaInfo` */ mediaResolver?: | NFTMediaInfo | (() => NFTMediaInfo) | (() => Promise); }; /** * This component fetches and displays an NFT's media. It uses thirdweb [`MediaRenderer`](https://portal.thirdweb.com/refernces/typescript/v5/MediaRenderer) under the hood * so you can style it just like how you would style a MediaRenderer. * @returns A MediaRenderer component * * @component * * @example * ### Basic usage * ```tsx * import { NFTProvider, NFTMedia } from "thirdweb/react"; * * * * * ``` * * ### Show a loading sign while the media is being fetched * ```tsx * import { NFTProvider, NFTMedia } from "thirdweb/react"; * * * } /> * * ``` * * ### Show something in case the media failed to resolve * ```tsx * import { NFTProvider, NFTMedia } from "thirdweb/react"; * * * Failed to load media} /> * * ``` * * ### Custom query options for useQuery (tanstack-query) * ```tsx * import { NFTProvider, NFTMedia } from "thirdweb/react"; * * * * * ``` * * ### Basic stylings * * You can style NFTMedia with the `style` and `className` props. * * ```tsx * * ``` * * ### Override the media with the `mediaResolver` prop * If you already have the url, you can skip the network requests and pass it directly to the NFTMedia * ```tsx * * ``` * * You can also pass in your own custom (async) function that retrieves the media url * ```tsx * const getMedia = async () => { * const url = getNFTMedia(props); * return url; * }; * * * ``` * @nft * @beta */ export function NFTMedia({ loadingComponent, fallbackComponent, queryOptions, mediaResolver, ...mediaRendererProps }: NFTMediaProps) { const { contract, tokenId } = useNFTContext(); const mediaQuery = useQuery({ queryFn: async (): Promise => fetchNftMedia({ contract, mediaResolver, tokenId }), queryKey: getQueryKey({ chainId: contract.chain.id, contractAddress: contract.address, mediaResolver, tokenId, }), ...queryOptions, }); if (mediaQuery.isLoading) { return loadingComponent || null; } if (!mediaQuery.data) { return fallbackComponent || null; } return ( ); } /** * @internal */ export function getQueryKey(props: { contractAddress: string; chainId: number; tokenId: bigint; mediaResolver?: | NFTMediaInfo | (() => NFTMediaInfo) | (() => Promise); }) { const { chainId, tokenId, mediaResolver, contractAddress } = props; return [ "_internal_nft_media_", chainId, contractAddress, tokenId.toString(), { resolver: typeof mediaResolver === "object" ? mediaResolver : typeof mediaResolver === "function" ? getFunctionId(mediaResolver) : undefined, }, ] as const; } /** * @internal Exported for tests only */ export async function fetchNftMedia(props: { mediaResolver?: | NFTMediaInfo | (() => NFTMediaInfo) | (() => Promise); contract: ThirdwebContract; tokenId: bigint; }): Promise<{ src: string; poster: string | undefined }> { const { mediaResolver, contract, tokenId } = props; if (typeof mediaResolver === "object") { return mediaResolver; } if (typeof mediaResolver === "function") { return mediaResolver(); } const nft = await getNFTInfo({ contract, tokenId }).catch(() => undefined); if (!nft) { throw new Error("Failed to resolve NFT info"); } const animation_url = nft.metadata.animation_url; const image = nft.metadata.image || nft.metadata.image_url; if (animation_url) { return { poster: image || undefined, src: animation_url, }; } if (image) { return { poster: undefined, src: image, }; } throw new Error("Failed to resolve NFT media"); }