feat(explorer): add attributes tab for metaplex nfts (#24580)
* feat(nfts): add attributes tab * fix: filter attributes to keep objects matching schema * chore: rename component and format * fix: support attribute value if it's a number
This commit is contained in:
parent
a22101489c
commit
2ae911ee55
|
@ -0,0 +1,87 @@
|
||||||
|
import React from "react";
|
||||||
|
import { NFTData } from "providers/accounts";
|
||||||
|
import { LoadingCard } from "components/common/LoadingCard";
|
||||||
|
import { ErrorCard } from "components/common/ErrorCard";
|
||||||
|
|
||||||
|
interface Attribute {
|
||||||
|
trait_type: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MetaplexNFTAttributesCard({ nftData }: { nftData: NFTData }) {
|
||||||
|
const [attributes, setAttributes] = React.useState<Attribute[]>([]);
|
||||||
|
const [status, setStatus] = React.useState<"loading" | "success" | "error">(
|
||||||
|
"loading"
|
||||||
|
);
|
||||||
|
|
||||||
|
async function fetchMetadataAttributes() {
|
||||||
|
try {
|
||||||
|
const response = await fetch(nftData.metadata.data.uri);
|
||||||
|
const metadata = await response.json();
|
||||||
|
|
||||||
|
// Verify if the attributes value is an array
|
||||||
|
if (Array.isArray(metadata.attributes)) {
|
||||||
|
// Filter attributes to keep objects matching schema
|
||||||
|
const filteredAttributes = metadata.attributes.filter(
|
||||||
|
(attribute: any) => {
|
||||||
|
return (
|
||||||
|
typeof attribute === "object" &&
|
||||||
|
typeof attribute.trait_type === "string" &&
|
||||||
|
(typeof attribute.value === "string" ||
|
||||||
|
typeof attribute.value === "number")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
setAttributes(filteredAttributes);
|
||||||
|
setStatus("success");
|
||||||
|
} else {
|
||||||
|
throw new Error("Attributes is not an array");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
setStatus("error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
fetchMetadataAttributes();
|
||||||
|
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
|
if (status === "loading") {
|
||||||
|
return <LoadingCard />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status === "error") {
|
||||||
|
return <ErrorCard text="Failed to fetch attributes" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const attributesList: React.ReactNode[] = attributes.map(
|
||||||
|
({ trait_type, value }) => {
|
||||||
|
return (
|
||||||
|
<tr key={`${trait_type}:${value}`}>
|
||||||
|
<td>{trait_type}</td>
|
||||||
|
<td>{value}</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="card">
|
||||||
|
<div className="card-header align-items-center">
|
||||||
|
<h3 className="card-header-title">Attributes</h3>
|
||||||
|
</div>
|
||||||
|
<div className="table-responsive mb-0">
|
||||||
|
<table className="table table-sm table-nowrap card-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th className="text-muted w-1">Trait type</th>
|
||||||
|
<th className="text-muted w-1">Value</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody className="list">{attributesList}</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -36,6 +36,7 @@ import { TokenTransfersCard } from "components/account/history/TokenTransfersCar
|
||||||
import { TokenInstructionsCard } from "components/account/history/TokenInstructionsCard";
|
import { TokenInstructionsCard } from "components/account/history/TokenInstructionsCard";
|
||||||
import { RewardsCard } from "components/account/RewardsCard";
|
import { RewardsCard } from "components/account/RewardsCard";
|
||||||
import { MetaplexMetadataCard } from "components/account/MetaplexMetadataCard";
|
import { MetaplexMetadataCard } from "components/account/MetaplexMetadataCard";
|
||||||
|
import { MetaplexNFTAttributesCard } from "components/account/MetaplexNFTAttributesCard";
|
||||||
import { NFTHeader } from "components/account/MetaplexNFTHeader";
|
import { NFTHeader } from "components/account/MetaplexNFTHeader";
|
||||||
import { DomainsCard } from "components/account/DomainsCard";
|
import { DomainsCard } from "components/account/DomainsCard";
|
||||||
import isMetaplexNFT from "providers/accounts/utils/isMetaplexNFT";
|
import isMetaplexNFT from "providers/accounts/utils/isMetaplexNFT";
|
||||||
|
@ -70,6 +71,11 @@ const TABS_LOOKUP: { [id: string]: Tab[] } = {
|
||||||
title: "Metadata",
|
title: "Metadata",
|
||||||
path: "/metadata",
|
path: "/metadata",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
slug: "attributes",
|
||||||
|
title: "Attributes",
|
||||||
|
path: "/attributes",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
stake: [
|
stake: [
|
||||||
{
|
{
|
||||||
|
@ -360,6 +366,7 @@ export type MoreTabs =
|
||||||
| "instructions"
|
| "instructions"
|
||||||
| "rewards"
|
| "rewards"
|
||||||
| "metadata"
|
| "metadata"
|
||||||
|
| "attributes"
|
||||||
| "domains"
|
| "domains"
|
||||||
| "security"
|
| "security"
|
||||||
| "anchor-program"
|
| "anchor-program"
|
||||||
|
@ -420,6 +427,11 @@ function MoreSection({
|
||||||
nftData={(account.details?.data as TokenProgramData).nftData!}
|
nftData={(account.details?.data as TokenProgramData).nftData!}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{tab === "attributes" && (
|
||||||
|
<MetaplexNFTAttributesCard
|
||||||
|
nftData={(account.details?.data as TokenProgramData).nftData!}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{tab === "domains" && <DomainsCard pubkey={pubkey} />}
|
{tab === "domains" && <DomainsCard pubkey={pubkey} />}
|
||||||
{tab === "security" && data?.program === "bpf-upgradeable-loader" && (
|
{tab === "security" && data?.program === "bpf-upgradeable-loader" && (
|
||||||
<SecurityCard data={data} />
|
<SecurityCard data={data} />
|
||||||
|
|
Loading…
Reference in New Issue