From a55a3b39d92e36f4ee0b7489fbe10796cdc5d66f Mon Sep 17 00:00:00 2001 From: samepant Date: Fri, 19 Jan 2024 18:45:34 -0500 Subject: [PATCH] upgrade nft media getting/rendering --- frontend/src/components/NFTGridItem/index.tsx | 18 +---- .../src/components/NFTItem/NFTItem.module.css | 32 -------- .../components/NFTMedia/NFTMedia.module.css | 28 +++++++ frontend/src/components/NFTMedia/index.tsx | 74 +++++++++++++++++++ frontend/src/hooks/useCollection.ts | 2 +- frontend/src/hooks/useTokenBalances.ts | 2 +- frontend/src/hooks/useTokenMetadata.ts | 2 +- frontend/src/types/Token.d.ts | 20 +++++ 8 files changed, 127 insertions(+), 51 deletions(-) create mode 100644 frontend/src/components/NFTMedia/NFTMedia.module.css create mode 100644 frontend/src/components/NFTMedia/index.tsx diff --git a/frontend/src/components/NFTGridItem/index.tsx b/frontend/src/components/NFTGridItem/index.tsx index f5d6fde..bc19db9 100644 --- a/frontend/src/components/NFTGridItem/index.tsx +++ b/frontend/src/components/NFTGridItem/index.tsx @@ -1,4 +1,3 @@ -import { useState } from "react" import clsx from "clsx" import { Link } from "react-router-dom" @@ -6,6 +5,7 @@ import classes from "./NFTItem.module.css" import ChainIcon from "../ChainIcon" import { CHAINS } from "../../chains" import { MoralisNFT } from "../../types/Token" +import NFTMedia from "../NFTMedia" interface Props { nft: { deployed: boolean } & MoralisNFT @@ -14,8 +14,6 @@ interface Props { } const NFTGridItem: React.FC = ({ nft, chainId, showCollectionName }) => { - const [imageError, setImageError] = useState(false) - const chain = CHAINS[chainId] const metadata = JSON.parse(nft.metadata || "{}") @@ -48,19 +46,7 @@ const NFTGridItem: React.FC = ({ nft, chainId, showCollectionName }) => {
- {(imageError || !metadata.image) && ( -
- )} - {!imageError && metadata.image && ( -
- {name} setImageError(true)} - /> -
- )} +

⬈⬈⬈⬈⬈⬈⬈⬈⬈

diff --git a/frontend/src/components/NFTItem/NFTItem.module.css b/frontend/src/components/NFTItem/NFTItem.module.css index 2ceef78..8473ead 100644 --- a/frontend/src/components/NFTItem/NFTItem.module.css +++ b/frontend/src/components/NFTItem/NFTItem.module.css @@ -40,38 +40,6 @@ display: flex; justify-content: space-between; } - -.imageContainer { - width: 50%; - display: flex; - justify-content: flex-start; -} - -.main .image { - object-fit: contain; - object-position: top; - height: min-content; - max-height: 100%; - border-radius: 10px; -} - -.noImage { - width: 50%; - height: 100%; - background-color: var(--box-bg); - border-radius: 10px; -} - -.noImage::after { - content: "?"; - height: 100%; - width: 100%; - font-size: 5em; - opacity: 0.2; - display: flex; - justify-content: center; - align-items: center; -} .info { width: 45%; display: flex; diff --git a/frontend/src/components/NFTMedia/NFTMedia.module.css b/frontend/src/components/NFTMedia/NFTMedia.module.css new file mode 100644 index 0000000..f9f43f4 --- /dev/null +++ b/frontend/src/components/NFTMedia/NFTMedia.module.css @@ -0,0 +1,28 @@ +.noImage { + height: 100%; + background-color: var(--box-bg); + border-radius: 10px; +} + +.noImage::after { + content: "?"; + height: 100%; + width: 100%; + font-size: 5em; + opacity: 0.2; + display: flex; + justify-content: center; + align-items: center; +} + +.imageContainer { + aspect-ratio: 1; + display: flex; + justify-content: center; +} + +.image { + object-fit: cover; + max-height: 100%; + border-radius: 10px; +} diff --git a/frontend/src/components/NFTMedia/index.tsx b/frontend/src/components/NFTMedia/index.tsx new file mode 100644 index 0000000..8d80239 --- /dev/null +++ b/frontend/src/components/NFTMedia/index.tsx @@ -0,0 +1,74 @@ +import { useState } from "react" +import { MoralisNFT } from "../../types/Token" + +import classes from "./NFTMedia.module.css" + +const getIPFSHashFromURL = (url: string) => { + const match = url.match( + /^ipfs:\/\/(Qm[1-9A-HJ-NP-Za-km-z]{44,}|b[A-Za-z2-7]{58,}|B[A-Z2-7]{58,}|z[1-9A-HJ-NP-Za-km-z]{48,}|F[0-9A-F]{50,})(\/(\d|\w|\.)+)*$/ + ) + return match ? match[1] : null +} + +const NFTMedia = ({ nft }: { nft: MoralisNFT }) => { + const [imageError, setImageError] = useState(false) + const metadata = JSON.parse(nft.metadata || "{}") + const alt = metadata.name || "NFT image" + + let src = "" + let type = "image/*" + + if (!nft.media) { + return
+ } + + switch (nft.media.status) { + case "success": + // use media url + src = nft.media.media_collection.high.url + type = nft.media.mimetype + break + + case "host_unavailable": + // could be an ipfs url or an unreachable url + const ipfsHash = getIPFSHashFromURL(nft.media.original_media_url) + if (ipfsHash) { + src = `https://cloudflare-ipfs.com/ipfs/${ipfsHash}` + } else { + src = nft.media.original_media_url + } + break + + default: + src = nft.media.original_media_url + break + } + return ( + <> + {(imageError || !src) &&
} + {!imageError && src && ( +
+ {type.includes("image") && ( + {alt} setImageError(true)} + /> + )} + {type.includes("video") && ( +
+ )} + + ) +} + +export default NFTMedia diff --git a/frontend/src/hooks/useCollection.ts b/frontend/src/hooks/useCollection.ts index 840e26c..bf095f1 100644 --- a/frontend/src/hooks/useCollection.ts +++ b/frontend/src/hooks/useCollection.ts @@ -19,7 +19,7 @@ const useCollection = ({ tokenAddress, chainId, page = 0 }: Props) => { // get collection metadata const nftRes = await fetch( - `${process.env.REACT_APP_PROXY_URL}/${chainId}/moralis/nft/${tokenAddress}` + `${process.env.REACT_APP_PROXY_URL}/${chainId}/moralis/nft/${tokenAddress}?media_items=true` ) if (!nftRes.ok) { throw new Error("NFT request failed") diff --git a/frontend/src/hooks/useTokenBalances.ts b/frontend/src/hooks/useTokenBalances.ts index 0867f5b..4b318d1 100644 --- a/frontend/src/hooks/useTokenBalances.ts +++ b/frontend/src/hooks/useTokenBalances.ts @@ -20,7 +20,7 @@ const useTokenBalances = ({ accountAddress, chainId }: Props) => { try { // get nfts const nftRes = await fetch( - `${process.env.REACT_APP_PROXY_URL}/${chainId}/moralis/${accountAddress}/nft` + `${process.env.REACT_APP_PROXY_URL}/${chainId}/moralis/${accountAddress}/nft?media_items=true` ) if (!nftRes.ok) { throw new Error("NFT request failed") diff --git a/frontend/src/hooks/useTokenMetadata.ts b/frontend/src/hooks/useTokenMetadata.ts index 0a9b664..e9ad1f6 100644 --- a/frontend/src/hooks/useTokenMetadata.ts +++ b/frontend/src/hooks/useTokenMetadata.ts @@ -20,7 +20,7 @@ const useNFTMetadata = ({ tokenAddress, tokenId, chainId }: Props) => { // get nfts const nftRes = await fetch( - `${process.env.REACT_APP_PROXY_URL}/${chainId}/moralis/nft/${tokenAddress}/${tokenId}` + `${process.env.REACT_APP_PROXY_URL}/${chainId}/moralis/nft/${tokenAddress}/${tokenId}?media_items=true` ) if (!nftRes.ok) { throw new Error("NFT request failed") diff --git a/frontend/src/types/Token.d.ts b/frontend/src/types/Token.d.ts index a06be21..4a2c351 100644 --- a/frontend/src/types/Token.d.ts +++ b/frontend/src/types/Token.d.ts @@ -6,6 +6,25 @@ export interface NFTContext { export type NFTType = "ERC721" | "ERC1155" +interface MoralisMediaItem { + width: number + height: number + url: string +} + +export interface MoralisMediaCollection { + status: string + updateAt: string + mimetype: string + parent_hash: string + media_collection: { + low: MoralisMediaItem + medium: MoralisMediaItem + high: MoralisMediaItem + } + original_media_url: string +} + export interface MoralisNFT { amount: string token_id: string @@ -22,6 +41,7 @@ export interface MoralisNFT { minter_address: string verified_collection: boolean possible_spam: boolean + media?: MoralisMediaCollection } export interface MoralisFungible {