Skip to content

Commit 2fc4689

Browse files
committed
update
1 parent 015293e commit 2fc4689

File tree

11 files changed

+504
-209
lines changed

11 files changed

+504
-209
lines changed

packages/thirdweb/src/react/web/ui/prebuilt/NFT/NFT.test.tsx

Lines changed: 0 additions & 114 deletions
This file was deleted.
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { describe, expect, it } from "vitest";
2+
import { DOODLES_CONTRACT, DROP1155_CONTRACT } from "~test/test-contracts.js";
3+
import { fetchNftDescription } from "./description.js";
4+
5+
describe.runIf(process.env.TW_SECRET_KEY)("NFTDescription", () => {
6+
it("fetchNftDescription should work with ERC721", async () => {
7+
const desc = await fetchNftDescription({
8+
contract: DOODLES_CONTRACT,
9+
tokenId: 0n,
10+
});
11+
expect(desc).toBe(
12+
"A community-driven collectibles project featuring art by Burnt Toast. Doodles come in a joyful range of colors, traits and sizes with a collection size of 10,000. Each Doodle allows its owner to vote for experiences and activations paid for by the Doodles Community Treasury. Burnt Toast is the working alias for Scott Martin, a Canadian–based illustrator, designer, animator and muralist.",
13+
);
14+
});
15+
16+
it("fetchNftDescription should work with ERC1155", async () => {
17+
const desc = await fetchNftDescription({
18+
contract: DROP1155_CONTRACT,
19+
tokenId: 0n,
20+
});
21+
expect(desc).toBe("");
22+
});
23+
24+
it("fetchNftDescription should respect descriptionResolver as a string", async () => {
25+
const desc = await fetchNftDescription({
26+
contract: DOODLES_CONTRACT,
27+
tokenId: 0n,
28+
descriptionResolver: "string",
29+
});
30+
expect(desc).toBe("string");
31+
});
32+
33+
it("fetchNftDescription should respect descriptionResolver as a non-async function", async () => {
34+
const desc = await fetchNftDescription({
35+
contract: DOODLES_CONTRACT,
36+
tokenId: 0n,
37+
descriptionResolver: () => "non-async",
38+
});
39+
expect(desc).toBe("non-async");
40+
});
41+
42+
it("fetchNftDescription should respect descriptionResolver as a async function", async () => {
43+
const desc = await fetchNftDescription({
44+
contract: DOODLES_CONTRACT,
45+
tokenId: 0n,
46+
descriptionResolver: async () => "async",
47+
});
48+
expect(desc).toBe("async");
49+
});
50+
});

packages/thirdweb/src/react/web/ui/prebuilt/NFT/description.tsx

Lines changed: 58 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
"use client";
22

3-
import type { UseQueryOptions } from "@tanstack/react-query";
3+
import { type UseQueryOptions, useQuery } from "@tanstack/react-query";
44
import type { JSX } from "react";
5-
import type { NFT } from "../../../../../utils/nft/parseNft.js";
6-
import { useNftInfo } from "./hooks.js";
5+
import type { ThirdwebContract } from "src/contract/contract.js";
76
import { useNFTContext } from "./provider.js";
7+
import { getNFTInfo } from "./utils.js";
88

99
export interface NFTDescriptionProps
1010
extends Omit<React.HTMLAttributes<HTMLSpanElement>, "children"> {
@@ -13,7 +13,12 @@ export interface NFTDescriptionProps
1313
/**
1414
* Optional `useQuery` params
1515
*/
16-
queryOptions?: Omit<UseQueryOptions<NFT>, "queryFn" | "queryKey">;
16+
queryOptions?: Omit<UseQueryOptions<string>, "queryFn" | "queryKey">;
17+
/**
18+
* This prop can be a string or a (async) function that resolves to a string, representing the description of the NFT
19+
* This is particularly useful if you already have a way to fetch the data.
20+
*/
21+
descriptionResolver?: string | (() => string) | (() => Promise<string>);
1722
}
1823

1924
/**
@@ -58,6 +63,21 @@ export interface NFTDescriptionProps
5863
* </NFTProvider>
5964
* ```
6065
*
66+
* ### Override the description with the `descriptionResolver` prop
67+
* If you already have the url, you can skip the network requests and pass it directly to the NFTDescription
68+
* ```tsx
69+
* <NFTDescription descriptionResolver="The desc of the NFT" />
70+
* ```
71+
*
72+
* You can also pass in your own custom (async) function that retrieves the description
73+
* ```tsx
74+
* const getDescription = async () => {
75+
* // ...
76+
* return description;
77+
* };
78+
*
79+
* <NFTDescription descriptionResolver={getDescription} />
80+
* ```
6181
* @component
6282
* @nft
6383
* @beta
@@ -66,22 +86,49 @@ export function NFTDescription({
6686
loadingComponent,
6787
fallbackComponent,
6888
queryOptions,
89+
descriptionResolver,
6990
...restProps
7091
}: NFTDescriptionProps) {
7192
const { contract, tokenId } = useNFTContext();
72-
const nftQuery = useNftInfo({
73-
contract,
74-
tokenId,
75-
queryOptions,
93+
const descQuery = useQuery({
94+
queryKey: ["_internal_nft_description_", contract.chain.id, tokenId],
95+
queryFn: async (): Promise<string> =>
96+
fetchNftDescription({ descriptionResolver, contract, tokenId }),
97+
...queryOptions,
7698
});
7799

78-
if (nftQuery.isLoading) {
100+
if (descQuery.isLoading) {
79101
return loadingComponent || null;
80102
}
81103

82-
if (!nftQuery.data?.metadata?.description) {
104+
if (!descQuery.data) {
83105
return fallbackComponent || null;
84106
}
85107

86-
return <span {...restProps}>{nftQuery.data.metadata.description}</span>;
108+
return <span {...restProps}>{descQuery.data}</span>;
109+
}
110+
111+
/**
112+
* @internal Exported for tests
113+
*/
114+
export async function fetchNftDescription(props: {
115+
descriptionResolver?: string | (() => string) | (() => Promise<string>);
116+
contract: ThirdwebContract;
117+
tokenId: bigint;
118+
}): Promise<string> {
119+
const { descriptionResolver, contract, tokenId } = props;
120+
if (typeof descriptionResolver === "string") {
121+
return descriptionResolver;
122+
}
123+
if (typeof descriptionResolver === "function") {
124+
return descriptionResolver();
125+
}
126+
const nft = await getNFTInfo({ contract, tokenId }).catch(() => undefined);
127+
if (!nft) {
128+
throw new Error("Failed to resolve NFT info");
129+
}
130+
if (typeof nft.metadata.description !== "string") {
131+
throw new Error("Failed to resolve NFT description");
132+
}
133+
return nft.metadata.description;
87134
}

packages/thirdweb/src/react/web/ui/prebuilt/NFT/hooks.tsx

Lines changed: 0 additions & 53 deletions
This file was deleted.
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { describe, expect, it } from "vitest";
2+
import { DOODLES_CONTRACT, DROP1155_CONTRACT } from "~test/test-contracts.js";
3+
import { fetchNftMedia } from "./media.js";
4+
5+
describe.runIf(process.env.TW_SECRET_KEY)("NFTMedia", () => {
6+
it("fetchNftMedia should work with ERC721", async () => {
7+
const desc = await fetchNftMedia({
8+
contract: DOODLES_CONTRACT,
9+
tokenId: 0n,
10+
});
11+
expect(desc).toBe("ipfs://QmUEfFfwAh4wyB5UfHCVPUxis4j4Q4kJXtm5x5p3g1fVUn");
12+
});
13+
14+
it("fetchNftMedia should work with ERC1155", async () => {
15+
const desc = await fetchNftMedia({
16+
contract: DROP1155_CONTRACT,
17+
tokenId: 0n,
18+
});
19+
expect(desc).toBe(
20+
"ipfs://QmeGCqV1mSHTZrvuFzW1XZdCRRGXB6AmSotTqHoxA2xfDo/1.mp4",
21+
);
22+
});
23+
24+
it("fetchNftMedia should respect mediaResolver as a string", async () => {
25+
const desc = await fetchNftMedia({
26+
contract: DOODLES_CONTRACT,
27+
tokenId: 0n,
28+
mediaResolver: "string",
29+
});
30+
expect(desc).toBe("string");
31+
});
32+
33+
it("fetchNftMedia should respect mediaResolver as a non-async function", async () => {
34+
const desc = await fetchNftMedia({
35+
contract: DOODLES_CONTRACT,
36+
tokenId: 0n,
37+
mediaResolver: () => "non-async",
38+
});
39+
expect(desc).toBe("non-async");
40+
});
41+
42+
it("fetchNftMedia should respect mediaResolver as a async function", async () => {
43+
const desc = await fetchNftMedia({
44+
contract: DOODLES_CONTRACT,
45+
tokenId: 0n,
46+
mediaResolver: async () => "async",
47+
});
48+
expect(desc).toBe("async");
49+
});
50+
});

0 commit comments

Comments
 (0)