diff --git a/components/home/now-minting/LatestDropSection.tsx b/components/home/now-minting/LatestDropSection.tsx
index 50cf23935b..724f961818 100644
--- a/components/home/now-minting/LatestDropSection.tsx
+++ b/components/home/now-minting/LatestDropSection.tsx
@@ -1,6 +1,5 @@
"use client";
-import { ManifoldClaimStatus } from "@/hooks/useManifoldClaim";
import { useNextMintDrop } from "@/hooks/useNextMintDrop";
import { useNowMintingStatus } from "@/hooks/useNowMintingStatus";
import { shouldShowNextMintInLatestDrop } from "@/helpers/mint-visibility.helpers";
@@ -8,7 +7,8 @@ import LatestDropNextMintSection from "./LatestDropNextMintSection";
import NowMintingSection from "./NowMintingSection";
export default function LatestDropSection() {
- const { nft, isFetching, status, isStatusLoading } = useNowMintingStatus();
+ const { nft, isFetching, isDropComplete, isStatusLoading } =
+ useNowMintingStatus();
const {
nextMint,
waveId,
@@ -24,7 +24,7 @@ export default function LatestDropSection() {
}
const shouldShowNextMint = shouldShowNextMintInLatestDrop({
- isMintEnded: status === ManifoldClaimStatus.ENDED,
+ isMintEnded: isDropComplete,
nextMintExists: !!nextMint,
});
diff --git a/components/home/now-minting/NowMintingDetails.tsx b/components/home/now-minting/NowMintingDetails.tsx
index 4d001f21d9..a630bbad3f 100644
--- a/components/home/now-minting/NowMintingDetails.tsx
+++ b/components/home/now-minting/NowMintingDetails.tsx
@@ -3,6 +3,7 @@ import { MEMES_CONTRACT } from "@/constants/constants";
import type { NFTWithMemesExtendedData } from "@/entities/INFT";
import {
getDimensionsFromMetadata,
+ getFileMimeTypeFromMetadata,
getFileTypeFromMetadata,
} from "@/helpers/nft.helpers";
import NowMintingCountdown from "./NowMintingCountdown";
@@ -20,6 +21,7 @@ export default function NowMintingDetails({ nft }: NowMintingDetailsProps) {
return `${Number.parseFloat(value.toFixed(5))} ETH`;
};
const floorPrice = formatEth(nft.floor_price);
+ const fileMimeType = getFileMimeTypeFromMetadata(nft.metadata);
return (
@@ -29,6 +31,7 @@ export default function NowMintingDetails({ nft }: NowMintingDetailsProps) {
title={nft.name}
artistHandle={nft.artist_seize_handle}
artistName={nft.artist}
+ mediaMimeType={fileMimeType}
/>
+ {mediaMimeType && (
+
+ )}
Card #{cardNumber}
diff --git a/components/memelab/MemeLab.tsx b/components/memelab/MemeLab.tsx
index 0914b0c11b..1390f1b0b6 100644
--- a/components/memelab/MemeLab.tsx
+++ b/components/memelab/MemeLab.tsx
@@ -2,6 +2,7 @@
import { AuthContext } from "@/components/auth/Auth";
import CollectionsDropdown from "@/components/collections-dropdown/CollectionsDropdown";
+import MediaTypeBadge from "@/components/drops/media/MediaTypeBadge";
import DotLoader from "@/components/dotLoader/DotLoader";
import { LFGButton } from "@/components/lfg-slideshow/LFGSlideshow";
import styles from "@/components/memelab/MemeLab.module.scss";
@@ -22,6 +23,7 @@ import {
numberWithCommas,
printMintDate,
} from "@/helpers/Helpers";
+import { getNftMimeType } from "@/helpers/nft.helpers";
import { fetchAllPages } from "@/services/6529api";
import { MemeLabSort } from "@/types/enums";
import {
@@ -536,6 +538,8 @@ export default function MemeLabComponent() {
}, [sort, sortDir, nftsLoaded, volumeType]);
function printNft(nft: LabNFT) {
+ const mediaMimeType = getNftMimeType(nft);
+
return (
- #{nft.id} - {nft.name}
+
+ {mediaMimeType && (
+
+ )}
+ #{nft.id} - {nft.name}
+
diff --git a/components/memelab/MemeLabPage.tsx b/components/memelab/MemeLabPage.tsx
index 3fddf41384..f538e66e24 100644
--- a/components/memelab/MemeLabPage.tsx
+++ b/components/memelab/MemeLabPage.tsx
@@ -7,6 +7,7 @@ import { useCookieConsent } from "@/components/cookies/CookieConsentContext";
import CircleLoader, {
CircleLoaderSize,
} from "@/components/distribution-plan-tool/common/CircleLoader";
+import MediaTypeBadge from "@/components/drops/media/MediaTypeBadge";
import { ActivityTypeItems } from "@/components/latest-activity/ActivityFilters";
import LatestActivityRow from "@/components/latest-activity/LatestActivityRow";
import MemeLabLeaderboard from "@/components/leaderboard/MemeLabLeaderboard";
@@ -52,10 +53,13 @@ import {
printMintDate,
} from "@/helpers/Helpers";
import {
+ getAnimationMimeTypeFromMetadata,
getAnimationDimensionsFromMetadata,
getAnimationFileTypeFromMetadata,
+ getImageMimeTypeFromMetadata,
getImageDimensionsFromMetadata,
getImageFileTypeFromMetadata,
+ getMimeTypeFromFormat,
} from "@/helpers/nft.helpers";
import { TypeFilter } from "@/hooks/useActivityData";
import useCapacitor from "@/hooks/useCapacitor";
@@ -77,6 +81,9 @@ const isAbortError = (error: unknown): boolean => {
return error instanceof Error && error.name === "AbortError";
};
+const trimToEmpty = (value: unknown): string =>
+ typeof value === "string" ? value.trim() : "";
+
export default function MemeLabPageComponent({
nftId,
}: {
@@ -616,10 +623,10 @@ export default function MemeLabPageComponent({
| Edition Size |
-
+ |
{numberWithCommas(nftMeta.edition_size)}
|
-
+ |
{nftMeta.edition_size_rank}/{nftMeta.collection_size}
|
@@ -635,16 +642,16 @@ export default function MemeLabPageComponent({
/>
-
+ |
{numberWithCommas(nftMeta.burnt)}
|
| Edition Size ex. Burnt |
-
+ |
{numberWithCommas(nftMeta.edition_size_not_burnt)}
|
-
+ |
{nftMeta.edition_size_not_burnt_rank}/
{nftMeta.collection_size}
|
@@ -653,10 +660,10 @@ export default function MemeLabPageComponent({
)}
| 6529 Museum |
-
+ |
{numberWithCommas(nftMeta.museum_holdings)}
|
-
+ |
{nftMeta.museum_holdings_rank}/{nftMeta.collection_size}
|
@@ -665,42 +672,42 @@ export default function MemeLabPageComponent({
Edition Size ex.
{nftMeta.burnt > 0 && " Burnt and"} 6529 Museum
-
+ |
{numberWithCommas(nftMeta.edition_size_cleaned)}
|
-
+ |
{nftMeta.edition_size_cleaned_rank}/
{nftMeta.collection_size}
|
| Collectors |
-
+ |
{numberWithCommas(nftMeta.hodlers)}
|
-
+ |
{nftMeta.hodlers_rank}/{nftMeta.collection_size}
|
| % Unique |
-
+ |
{Math.round(nftMeta.percent_unique * 100 * 10) / 10}%
|
-
+ |
{nftMeta.percent_unique_rank}/{nftMeta.collection_size}
|
{nftMeta.burnt > 0 && (
| % Unique ex. Burnt |
-
+ |
{Math.round(
nftMeta.percent_unique_not_burnt * 100 * 10
) / 10}
%
|
-
+ |
{nftMeta.percent_unique_not_burnt_rank}/
{nftMeta.collection_size}
|
@@ -711,12 +718,12 @@ export default function MemeLabPageComponent({
% Unique ex.{nftMeta.burnt > 0 && " Burnt and"} 6529
Museum
-
+ |
{Math.round(nftMeta.percent_unique_cleaned * 100 * 10) /
10}
%
|
-
+ |
{nftMeta.percent_unique_cleaned_rank}/
{nftMeta.collection_size}
|
@@ -983,7 +990,7 @@ export default function MemeLabPageComponent({
const imageDimensions = getImageDimensionsFromMetadata(nft?.metadata);
const animationDimensions = getAnimationDimensionsFromMetadata(nft?.metadata);
const imageHref = getResolvedImageSrc(nft);
- const metadataHref = nft?.uri.trim() ?? "";
+ const metadataHref = trimToEmpty(nft?.uri);
const metadata =
nft?.metadata !== null && typeof nft?.metadata === "object"
? (nft.metadata as {
@@ -993,22 +1000,21 @@ export default function MemeLabPageComponent({
})
: undefined;
const artImageHref =
- (typeof metadata?.image === "string" ? metadata.image.trim() : "") ||
- nft?.image.trim() ||
- "";
+ trimToEmpty(metadata?.image) || trimToEmpty(nft?.image) || "";
const artAnimationHref =
- (typeof metadata?.animation_url === "string"
- ? metadata.animation_url.trim()
- : "") ||
- (typeof metadata?.animation === "string"
- ? metadata.animation.trim()
- : "") ||
- nft?.animation.trim() ||
+ trimToEmpty(metadata?.animation_url) ||
+ trimToEmpty(metadata?.animation) ||
+ trimToEmpty(nft?.animation) ||
"";
const hasImage = Boolean(imageHref);
const isShowingAnimation = hasAnimation && (currentSlide === 0 || !hasImage);
const fileType = isShowingAnimation ? animationFormat : imageFormat;
const dimensions = isShowingAnimation ? animationDimensions : imageDimensions;
+ const fileMimeType = isShowingAnimation
+ ? (getAnimationMimeTypeFromMetadata(nft?.metadata) ??
+ getMimeTypeFromFormat(animationFormat))
+ : (getImageMimeTypeFromMetadata(nft?.metadata) ??
+ getMimeTypeFromFormat(imageFormat));
const currentFormat = fileType ?? "";
const arweaveRows = [
metadataHref
@@ -1173,36 +1179,52 @@ export default function MemeLabPageComponent({
| Edition Size |
- {nft.supply} |
+ {nft.supply} |
| Collection |
- {nft.collection} |
+ {nft.collection} |
+
+
+ | Mint Date |
+
+ {printMintDate(nft.mint_date)}
+ |
| Artist Name |
- {nft.artist} |
+ {nft.artist} |
| Artist Profile |
-
+ |
|
-
- | Mint Date |
- {printMintDate(nft.mint_date)} |
-
{fileType && (
| File Type |
- {fileType} |
+
+ {fileMimeType ? (
+
+ ) : (
+ fileType
+ )}
+ |
)}
{dimensions && (
| Dimensions |
- {dimensions} |
+ {dimensions} |
)}
@@ -1245,7 +1267,7 @@ export default function MemeLabPageComponent({
.map((a: any) => (
| {a.trait_type} |
- {a.value} |
+ {a.value} |
))}
diff --git a/components/nft-attributes/ArweaveLinksTable.tsx b/components/nft-attributes/ArweaveLinksTable.tsx
index d4b3b82cca..dd3bd62b51 100644
--- a/components/nft-attributes/ArweaveLinksTable.tsx
+++ b/components/nft-attributes/ArweaveLinksTable.tsx
@@ -26,13 +26,11 @@ export function ArweaveLinksTable(props: {
{props.rows.map((row) => (
-
- {row.label}
-
+
{row.label}
-
+
View
)}
+ {props.afterMetadata}
{hasHodlRate && (
{props.label} |
-
+
{value > 0 ? `${numberWithCommas(value)} ${props.unit ?? ""}` : `N/A`}
-
+
|
);
diff --git a/components/nft-image/NFTImage.tsx b/components/nft-image/NFTImage.tsx
index e9a7856433..8f8a26bbdd 100644
--- a/components/nft-image/NFTImage.tsx
+++ b/components/nft-image/NFTImage.tsx
@@ -1,10 +1,10 @@
import type { BaseNFT, NFTLite } from "@/entities/INFT";
-import styles from "./NFTImage.module.scss";
-import NFTHTMLRenderer from "./renderers/NFTHTMLRenderer";
-import NFTImageRenderer from "./renderers/NFTImageRenderer";
-import NFTModelRenderer from "./renderers/NFTModelRenderer";
-import NFTVideoRenderer from "./renderers/NFTVideoRenderer";
-import { getMediaType } from "./utils/media-type";
+import styles from "@/components/nft-image/NFTImage.module.scss";
+import NFTHTMLRenderer from "@/components/nft-image/renderers/NFTHTMLRenderer";
+import NFTImageRenderer from "@/components/nft-image/renderers/NFTImageRenderer";
+import NFTModelRenderer from "@/components/nft-image/renderers/NFTModelRenderer";
+import NFTVideoRenderer from "@/components/nft-image/renderers/NFTVideoRenderer";
+import { getMediaType } from "@/components/nft-image/utils/media-type";
interface Props {
nft: BaseNFT | NFTLite;
diff --git a/components/nft-image/NFTImageBalance.tsx b/components/nft-image/NFTImageBalance.tsx
index a0fccef6c1..676befc571 100644
--- a/components/nft-image/NFTImageBalance.tsx
+++ b/components/nft-image/NFTImageBalance.tsx
@@ -1,8 +1,8 @@
"use client";
import { useAuth } from "@/components/auth/Auth";
+import styles from "@/components/nft-image/NFTImage.module.scss";
import { useNftBalance } from "@/hooks/useNftBalance";
-import styles from "./NFTImage.module.scss";
interface Props {
readonly contract: string;
@@ -34,9 +34,10 @@ export default function NFTImageBalance({ contract, tokenId, height }: Props) {
const printBalanceSpan = (b: string) => {
return (
+ } `}
+ >
{b}
);
diff --git a/components/nft-image/NFTModel.tsx b/components/nft-image/NFTModel.tsx
index 839d955ed9..3c23660bee 100644
--- a/components/nft-image/NFTModel.tsx
+++ b/components/nft-image/NFTModel.tsx
@@ -2,8 +2,8 @@ import "@google/model-viewer";
import { useEffect, useMemo, useRef } from "react";
import type { SyntheticEvent } from "react";
import type { BaseNFT } from "@/entities/INFT";
-import { getResolvedAnimationSrc } from "./utils/animation-source";
-import { withArweaveFallback } from "./utils/gateway-fallback";
+import { getResolvedAnimationSrc } from "@/components/nft-image/utils/animation-source";
+import { withArweaveFallback } from "@/components/nft-image/utils/gateway-fallback";
type ModelViewerElement = HTMLElement & {
src: string;
diff --git a/components/nft-image/RememeImage.tsx b/components/nft-image/RememeImage.tsx
index c5e285cc35..79f91a38ee 100644
--- a/components/nft-image/RememeImage.tsx
+++ b/components/nft-image/RememeImage.tsx
@@ -1,4 +1,4 @@
-import styles from "./NFTImage.module.scss";
+import styles from "@/components/nft-image/NFTImage.module.scss";
import { Col } from "react-bootstrap";
import type { Rememe } from "@/entities/INFT";
import Image from "next/image";
diff --git a/components/nft-image/renderers/NFTHTMLRenderer.tsx b/components/nft-image/renderers/NFTHTMLRenderer.tsx
index 7bb3964a4d..b6508040c8 100644
--- a/components/nft-image/renderers/NFTHTMLRenderer.tsx
+++ b/components/nft-image/renderers/NFTHTMLRenderer.tsx
@@ -1,12 +1,15 @@
"use client";
+import NFTImageBalance from "@/components/nft-image/NFTImageBalance";
+import styles from "@/components/nft-image/NFTImage.module.scss";
+import type { BaseRendererProps } from "@/components/nft-image/types/renderer-props";
+import { getResolvedAnimationSrc } from "@/components/nft-image/utils/animation-source";
import { useEffect, useMemo, useState } from "react";
import { Col } from "react-bootstrap";
-import styles from "../NFTImage.module.scss";
-import NFTImageBalance from "../NFTImageBalance";
-import type { BaseRendererProps } from "../types/renderer-props";
-import { getResolvedAnimationSrc } from "../utils/animation-source";
-import { getArweaveGatewayFallbackUrls } from "../utils/gateway-fallback";
+import {
+ getArweaveGatewayFallbackUrls,
+ shouldUseIframeFallbackTimeout,
+} from "@/components/nft-image/utils/gateway-fallback";
const IFRAME_FALLBACK_TIMEOUT_MS = 8000;
@@ -40,7 +43,12 @@ export default function NFTHTMLRenderer(props: Readonly) {
}, [activeUrl]);
useEffect(() => {
- if (!activeUrl || didLoadCurrentUrl || activeIndex + 1 >= urls.length) {
+ if (
+ !activeUrl ||
+ didLoadCurrentUrl ||
+ activeIndex + 1 >= urls.length ||
+ !shouldUseIframeFallbackTimeout(activeUrl)
+ ) {
return;
}
diff --git a/components/nft-image/renderers/NFTImageRenderer.tsx b/components/nft-image/renderers/NFTImageRenderer.tsx
index db831768c5..41a265cc4e 100644
--- a/components/nft-image/renderers/NFTImageRenderer.tsx
+++ b/components/nft-image/renderers/NFTImageRenderer.tsx
@@ -1,11 +1,11 @@
"use client";
+import NFTImageBalance from "@/components/nft-image/NFTImageBalance";
+import styles from "@/components/nft-image/NFTImage.module.scss";
+import type { BaseRendererProps } from "@/components/nft-image/types/renderer-props";
+import { withArweaveFallback } from "@/components/nft-image/utils/gateway-fallback";
import Image from "next/image";
import { Col } from "react-bootstrap";
-import styles from "../NFTImage.module.scss";
-import NFTImageBalance from "../NFTImageBalance";
-import type { BaseRendererProps } from "../types/renderer-props";
-import { withArweaveFallback } from "../utils/gateway-fallback";
function getSrc(
nft: BaseRendererProps["nft"],
diff --git a/components/nft-image/renderers/NFTModelRenderer.tsx b/components/nft-image/renderers/NFTModelRenderer.tsx
index d1a76de1e9..5c34d4c619 100644
--- a/components/nft-image/renderers/NFTModelRenderer.tsx
+++ b/components/nft-image/renderers/NFTModelRenderer.tsx
@@ -1,12 +1,11 @@
"use client";
+import NFTImageBalance from "@/components/nft-image/NFTImageBalance";
+import styles from "@/components/nft-image/NFTImage.module.scss";
+import NFTModel from "@/components/nft-image/NFTModel";
+import type { BaseRendererProps } from "@/components/nft-image/types/renderer-props";
import { Col } from "react-bootstrap";
-import styles from "../NFTImage.module.scss";
-import NFTImageBalance from "../NFTImageBalance";
-import NFTModel from "../NFTModel";
-import type { BaseRendererProps } from "../types/renderer-props";
-
export default function NFTModelRenderer(props: Readonly) {
// Only render if NFT has metadata (i.e., it's a BaseNFT, not NFTLite)
if (!("metadata" in props.nft)) {
diff --git a/components/nft-image/renderers/NFTVideoRenderer.tsx b/components/nft-image/renderers/NFTVideoRenderer.tsx
index cedf6109ac..0d2bc94a67 100644
--- a/components/nft-image/renderers/NFTVideoRenderer.tsx
+++ b/components/nft-image/renderers/NFTVideoRenderer.tsx
@@ -1,11 +1,11 @@
"use client";
+import NFTImageBalance from "@/components/nft-image/NFTImageBalance";
+import styles from "@/components/nft-image/NFTImage.module.scss";
+import type { BaseRendererProps } from "@/components/nft-image/types/renderer-props";
+import { getResolvedAnimationSrc } from "@/components/nft-image/utils/animation-source";
+import { withArweaveFallback } from "@/components/nft-image/utils/gateway-fallback";
import { Col } from "react-bootstrap";
-import styles from "../NFTImage.module.scss";
-import NFTImageBalance from "../NFTImageBalance";
-import type { BaseRendererProps } from "../types/renderer-props";
-import { getResolvedAnimationSrc } from "../utils/animation-source";
-import { withArweaveFallback } from "../utils/gateway-fallback";
const globalScope = globalThis as typeof globalThis & {
window?: Window | undefined;
diff --git a/components/nft-image/utils/gateway-fallback.ts b/components/nft-image/utils/gateway-fallback.ts
index 26d406f0e0..c8b4a84786 100644
--- a/components/nft-image/utils/gateway-fallback.ts
+++ b/components/nft-image/utils/gateway-fallback.ts
@@ -100,22 +100,51 @@ type MediaErrorEvent = React.SyntheticEvent<
const DS_ORIGINAL = "arweaveOriginalSrc";
const DS_LAST_HOST = "arweaveLastGatewayHost";
-export function getArweaveGatewayFallbackUrls(url: string): string[] {
+function classifyGatewayAssetUrl(url: string):
+ | { kind: "empty" }
+ | { kind: "ipfs"; sourceUrl: string }
+ | { kind: "arweave"; sourceUrl: string }
+ | { kind: "other"; sourceUrl: string } {
const trimmed = url.trim();
if (!trimmed) {
- return [];
+ return { kind: "empty" };
}
+
if (isIpfsProtocolUrl(trimmed)) {
- return getIpfsFallbackUrls(trimmed);
+ return { kind: "ipfs", sourceUrl: trimmed };
}
+
const ipfsProtocolUrl = getIpfsProtocolUrlFromGatewayUrl(trimmed);
if (ipfsProtocolUrl) {
- return getIpfsFallbackUrls(ipfsProtocolUrl);
+ return { kind: "ipfs", sourceUrl: ipfsProtocolUrl };
+ }
+
+ if (isArweaveUrl(trimmed)) {
+ return { kind: "arweave", sourceUrl: trimmed };
+ }
+
+ return { kind: "other", sourceUrl: trimmed };
+}
+
+export function getArweaveGatewayFallbackUrls(url: string): string[] {
+ const assetUrl = classifyGatewayAssetUrl(url);
+ if (assetUrl.kind === "empty") {
+ return [];
}
- if (!isArweaveUrl(trimmed)) {
- return [trimmed];
+
+ if (assetUrl.kind === "ipfs") {
+ return getIpfsFallbackUrls(assetUrl.sourceUrl);
}
- return getTryList(trimmed, trimmed);
+
+ if (assetUrl.kind === "other") {
+ return [assetUrl.sourceUrl];
+ }
+
+ return getTryList(assetUrl.sourceUrl, assetUrl.sourceUrl);
+}
+
+export function shouldUseIframeFallbackTimeout(url: string): boolean {
+ return classifyGatewayAssetUrl(url).kind === "arweave";
}
function getTryList(currentSrc: string, originalSrc: string): string[] {
diff --git a/components/nft-image/utils/media-type.ts b/components/nft-image/utils/media-type.ts
index cc7dd8a6bc..46a24a968e 100644
--- a/components/nft-image/utils/media-type.ts
+++ b/components/nft-image/utils/media-type.ts
@@ -1,5 +1,5 @@
import type { BaseNFT, NFTLite } from "@/entities/INFT";
-import { getResolvedAnimationSrc } from "./animation-source";
+import { getResolvedAnimationSrc } from "@/components/nft-image/utils/animation-source";
type MediaType = "html" | "glb" | "video" | "image";
diff --git a/components/the-memes/MemePageArt.tsx b/components/the-memes/MemePageArt.tsx
index 95116f08e4..34ba27739e 100644
--- a/components/the-memes/MemePageArt.tsx
+++ b/components/the-memes/MemePageArt.tsx
@@ -1,5 +1,6 @@
"use client";
+import MediaTypeBadge from "@/components/drops/media/MediaTypeBadge";
import { ArweaveLinksTable } from "@/components/nft-attributes/ArweaveLinksTable";
import NFTAttributes from "@/components/nft-attributes/NFTAttributes";
import NFTImage from "@/components/nft-image/NFTImage";
@@ -16,8 +17,10 @@ import {
import {
getAnimationDimensionsFromMetadata,
getAnimationFileTypeFromMetadata,
+ getAnimationMimeTypeFromMetadata,
getImageDimensionsFromMetadata,
getImageFileTypeFromMetadata,
+ getImageMimeTypeFromMetadata,
} from "@/helpers/nft.helpers";
import { faExpandAlt } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@@ -33,6 +36,14 @@ const PROPERTY_EXCLUDED_TRAITS = new Set([
"Type - Card",
]);
+function formatTdhRate(value: number | undefined): string {
+ if (!value || value <= 0) {
+ return "N/A";
+ }
+
+ return numberWithCommas(Math.round(value * 100) / 100);
+}
+
function shouldShowPropertyAttribute(attribute: IAttribute): boolean {
const displayType = attribute.display_type?.trim().toLowerCase();
@@ -93,6 +104,9 @@ export function MemePageArt(props: {
fullscreenElementId = "the-art-fullscreen-img";
}
const fileType = isShowingAnimation ? animationFormat : imageFormat;
+ const fileTypeMimeType = isShowingAnimation
+ ? getAnimationMimeTypeFromMetadata(props.nft?.metadata)
+ : getImageMimeTypeFromMetadata(props.nft?.metadata);
const dimensions = isShowingAnimation ? animationDimensions : imageDimensions;
const arweaveRows = [
metadataHref
@@ -137,6 +151,128 @@ export function MemePageArt(props: {
return `https://github.com/6529-Collections/thememecards/tree/main/card1-3`;
})();
+ const detailRows = [
+ {
+ key: "edition-size",
+ label: "Edition Size",
+ value: {props.nft?.supply},
+ },
+ {
+ key: "collection",
+ label: "Collection",
+ value: {props.nft?.collection},
+ },
+ {
+ key: "season",
+ label: "Season",
+ value: {props.nftMeta?.season},
+ },
+ {
+ key: "meme",
+ label: "Meme",
+ value: {props.nftMeta?.meme_name},
+ },
+ {
+ key: "mint-date",
+ label: "Mint Date",
+ value: (
+
+ {printMintDate(props.nft?.mint_date)}
+
+ ),
+ },
+ {
+ key: "artist-name",
+ label: "Artist Name",
+ value: {props.nft?.artist},
+ },
+ {
+ key: "artist-profile",
+ label: "Artist Profile",
+ value: (
+
+ {props.nft ? : null}
+
+ ),
+ },
+ ...(fileType
+ ? [
+ {
+ key: "file-type",
+ label: "File Type",
+ value: (
+
+
+
+ ),
+ },
+ ]
+ : []),
+ ...(distributionPlanLink
+ ? [
+ {
+ key: "minting-approach",
+ label: "Minting Approach",
+ value: (
+
+
+ Distribution Plan
+
+
+ ),
+ },
+ ]
+ : []),
+ {
+ key: "mint-price",
+ label: "Mint Price",
+ value: (
+
+ {props.nft && props.nft.mint_price > 0
+ ? `${numberWithCommas(
+ Math.round(props.nft.mint_price * 100000) / 100000
+ )} ETH`
+ : `N/A`}
+
+ ),
+ },
+ {
+ key: "tdh-rate",
+ label: "TDH Rate",
+ value: (
+
+ {formatTdhRate(props.nft?.hodl_rate)}
+
+ ),
+ },
+ {
+ key: "dimensions",
+ label: "Dimensions",
+ value: {dimensions || "N/A"},
+ },
+ ];
+ const detailSplitIndex = Math.ceil(detailRows.length / 2);
+ const detailColumns = [
+ detailRows.slice(0, detailSplitIndex),
+ detailRows.slice(detailSplitIndex),
+ ];
+
useEffect(() => {
setIsFullScreenSupported(fullScreenSupported());
}, []);
@@ -265,12 +401,7 @@ export function MemePageArt(props: {
-
+
@@ -278,96 +409,37 @@ export function MemePageArt(props: {
-
+
-
- | Edition Size |
- {props.nft.supply} |
-
-
- | Collection |
- {props.nft.collection} |
-
-
- | Season |
- {props.nftMeta.season} |
-
-
- | Meme |
- {props.nftMeta.meme_name} |
-
-
- | Artist Name |
- {props.nft.artist} |
-
-
- | Artist Profile |
-
-
- |
-
-
- | Mint Date |
- {printMintDate(props.nft.mint_date)} |
-
- {fileType && (
-
- | File Type |
- {fileType} |
-
- )}
- {dimensions && (
-
- | Dimensions |
- {dimensions} |
+ {detailRows.map((row) => (
+
+ | {row.label} |
+ {row.value} |
- )}
+ ))}
-
-
-
-
-
-
-
- Minting Approach
-
-
- {distributionPlanLink && (
-
-
-
- Distribution Plan
-
+ {detailColumns.map((columnRows) => (
+ row.key).join("-")}
+ xs={{ span: 12 }}
+ lg={{ span: 6 }}
+ className="d-none d-lg-block"
+ >
+
+
+ {columnRows.map((row) => (
+
+ | {row.label} |
+ {row.value} |
+
+ ))}
+
+
-
- )}
-
-
- Mint price:{" "}
- {props.nft.mint_price > 0
- ? `${numberWithCommas(
- Math.round(props.nft.mint_price * 100000) / 100000
- )} ETH`
- : `N/A`}
-
+ ))}
diff --git a/components/the-memes/MemePageLive.tsx b/components/the-memes/MemePageLive.tsx
index 0df059ec23..50f6f3a25e 100644
--- a/components/the-memes/MemePageLive.tsx
+++ b/components/the-memes/MemePageLive.tsx
@@ -1,6 +1,7 @@
"use client";
import { useCookieConsent } from "@/components/cookies/CookieConsentContext";
+import MediaTypeBadge from "@/components/drops/media/MediaTypeBadge";
import { NftPageStats } from "@/components/nft-attributes/NftStats";
import RememeImage from "@/components/nft-image/RememeImage";
import NFTMarketplaceLinks from "@/components/nft-marketplace-links/NFTMarketplaceLinks";
@@ -17,6 +18,7 @@ import {
numberWithCommas,
printMintDate,
} from "@/helpers/Helpers";
+import { getFileMimeTypeFromMetadata } from "@/helpers/nft.helpers";
import useCapacitor from "@/hooks/useCapacitor";
import { fetchUrl } from "@/services/6529api";
import { faFire, faRefresh } from "@fortawesome/free-solid-svg-icons";
@@ -49,6 +51,7 @@ export function MemePageLiveRightMenu(props: {
return `https://github.com/6529-Collections/thememecards/tree/main/card${id}`;
return `https://github.com/6529-Collections/thememecards/tree/main/card1-3`;
})();
+ const fileMimeType = getFileMimeTypeFromMetadata(props.nft?.metadata);
if (props.show && props.nft && props.nftMeta) {
return (
@@ -76,10 +79,10 @@ export function MemePageLiveRightMenu(props: {
| Edition Size |
-
+ |
{numberWithCommas(props.nftMeta.edition_size)}
|
-
+ |
{props.nftMeta.edition_size_rank}/
{props.nftMeta.collection_size}
|
@@ -96,18 +99,18 @@ export function MemePageLiveRightMenu(props: {
/>
-
+ |
{numberWithCommas(props.nftMeta.burnt)}
|
| Edition Size ex. Burnt |
-
+ |
{numberWithCommas(
props.nftMeta.edition_size_not_burnt
)}
|
-
+ |
{props.nftMeta.edition_size_not_burnt_rank}/
{props.nftMeta.collection_size}
|
@@ -116,10 +119,10 @@ export function MemePageLiveRightMenu(props: {
)}
| 6529 Museum |
-
+ |
{numberWithCommas(props.nftMeta.museum_holdings)}
|
-
+ |
{props.nftMeta.museum_holdings_rank}/
{props.nftMeta.collection_size}
|
@@ -129,31 +132,31 @@ export function MemePageLiveRightMenu(props: {
Edition Size ex.{props.nftMeta.burnt > 0 && " Burnt and"}{" "}
6529 Museum
-
+ |
{numberWithCommas(props.nftMeta.edition_size_cleaned)}
|
-
+ |
{props.nftMeta.edition_size_cleaned_rank}/
{props.nftMeta.collection_size}
|
| Collectors |
-
+ |
{numberWithCommas(props.nftMeta.hodlers)}
|
-
+ |
{props.nftMeta.hodlers_rank}/
{props.nftMeta.collection_size}
|
| % Unique |
-
+ |
{Math.round(props.nftMeta.percent_unique * 100 * 10) / 10}
%
|
-
+ |
{props.nftMeta.percent_unique_rank}/
{props.nftMeta.collection_size}
|
@@ -161,13 +164,13 @@ export function MemePageLiveRightMenu(props: {
{props.nftMeta.burnt > 0 && (
| % Unique ex. Burnt |
-
+ |
{Math.round(
props.nftMeta.percent_unique_not_burnt * 100 * 10
) / 10}
%
|
-
+ |
{props.nftMeta.percent_unique_not_burnt_rank}/
{props.nftMeta.collection_size}
|
@@ -178,13 +181,13 @@ export function MemePageLiveRightMenu(props: {
% Unique ex.{props.nftMeta.burnt > 0 && " Burnt and"} 6529
Museum
-
+ |
{Math.round(
props.nftMeta.percent_unique_cleaned * 100 * 10
) / 10}
%
|
-
+ |
{props.nftMeta.percent_unique_cleaned_rank}/
{props.nftMeta.collection_size}
|
@@ -204,19 +207,40 @@ export function MemePageLiveRightMenu(props: {
| Artist Name |
- {props.nft.artist} |
+ {props.nft.artist} |
| Artist Profile |
-
+ |
|
| Mint Date |
- {printMintDate(props.nft.mint_date)} |
+
+ {printMintDate(props.nft.mint_date)}
+ |
-
+
+ File Type |
+
+
+ |
+
+ ) : null
+ }
+ />
diff --git a/components/the-memes/TheMemes.tsx b/components/the-memes/TheMemes.tsx
index 0e7d384695..10b32526b1 100644
--- a/components/the-memes/TheMemes.tsx
+++ b/components/the-memes/TheMemes.tsx
@@ -2,6 +2,7 @@
import { AuthContext } from "@/components/auth/Auth";
import CollectionsDropdown from "@/components/collections-dropdown/CollectionsDropdown";
+import MediaTypeBadge from "@/components/drops/media/MediaTypeBadge";
import DotLoader from "@/components/dotLoader/DotLoader";
import { LFGButton } from "@/components/lfg-slideshow/LFGSlideshow";
import NFTImage from "@/components/nft-image/NFTImage";
@@ -17,6 +18,7 @@ import { VolumeType } from "@/entities/INFT";
import type { MemeSeason } from "@/entities/ISeason";
import { SortDirection } from "@/entities/ISort";
import { numberWithCommas, printMintDate } from "@/helpers/Helpers";
+import { getNftMimeType } from "@/helpers/nft.helpers";
import { fetchUrl } from "@/services/6529api";
import type { MemeLabSort } from "@/types/enums";
import { MEMES_EXTENDED_SORT, MemesSort } from "@/types/enums";
@@ -321,6 +323,8 @@ export default function TheMemesComponent() {
}
function printNft(nft: NFTWithMemesExtendedData) {
+ const mediaMimeType = getNftMimeType(nft);
+
return (
- #{nft.id} - {nft.name}
+
+ {mediaMimeType && (
+
+ )}
+ #{nft.id} - {nft.name}
+
diff --git a/helpers/nft.helpers.ts b/helpers/nft.helpers.ts
index 6a4d7c52ad..c8101a5aba 100644
--- a/helpers/nft.helpers.ts
+++ b/helpers/nft.helpers.ts
@@ -4,6 +4,9 @@ import {
MEMES_CONTRACT,
NEXTGEN_CONTRACT,
} from "@/constants/constants";
+import type { BaseNFT, NFTLite } from "@/entities/INFT";
+import { getResolvedAnimationSrc } from "@/components/nft-image/utils/animation-source";
+import { getResolvedImageSrc } from "@/components/nft-image/utils/image-source";
import { numberWithCommas } from "./Helpers";
interface NFTMediaDetails {
@@ -32,6 +35,33 @@ const getMediaFormat = (
return format.length > 0 ? format : null;
};
+const getFormatMimeKey = (format: string | null | undefined): string | null => {
+ if (typeof format !== "string") {
+ return null;
+ }
+
+ const trimmed = format.trim();
+ return trimmed.length > 0 ? trimmed.toUpperCase() : null;
+};
+
+const FORMAT_TO_MIME_TYPE: Record = {
+ PNG: "image/png",
+ JPEG: "image/jpeg",
+ JPG: "image/jpeg",
+ GIF: "image/gif",
+ WEBP: "image/webp",
+ SVG: "image/svg+xml",
+ MP4: "video/mp4",
+ WEBM: "video/webm",
+ MOV: "video/quicktime",
+ QT: "video/quicktime",
+ M4V: "video/x-m4v",
+ GLB: "model/gltf-binary",
+ GLTF: "model/gltf+json",
+ HTML: "text/html",
+ HTM: "text/html",
+};
+
const isValidDimension = (
value: number | null | undefined
): value is number => {
@@ -82,6 +112,95 @@ export function getFileTypeFromMetadata(metadata: NFTMediaMetadata) {
);
}
+export function getMimeTypeFromFormat(
+ format: string | null | undefined
+): string | null {
+ const normalizedFormat = getFormatMimeKey(format);
+ if (!normalizedFormat) {
+ return null;
+ }
+
+ return FORMAT_TO_MIME_TYPE[normalizedFormat] ?? null;
+}
+
+function getUrlExtension(url: string | null | undefined): string | null {
+ if (!url) {
+ return null;
+ }
+
+ const clean = url.split("?")[0]?.split("#")[0] ?? "";
+ const parts = clean.split(".");
+ if (parts.length < 2) {
+ return null;
+ }
+
+ const ext = parts.at(-1)?.trim().toUpperCase();
+ return ext || null;
+}
+
+export function getAnimationMimeTypeFromMetadata(
+ metadata: NFTMediaMetadata
+): string | null {
+ return getMimeTypeFromFormat(getAnimationFileTypeFromMetadata(metadata));
+}
+
+export function getImageMimeTypeFromMetadata(
+ metadata: NFTMediaMetadata
+): string | null {
+ return getMimeTypeFromFormat(getImageFileTypeFromMetadata(metadata));
+}
+
+export function getFileMimeTypeFromMetadata(metadata: NFTMediaMetadata) {
+ return (
+ getAnimationMimeTypeFromMetadata(metadata) ??
+ getImageMimeTypeFromMetadata(metadata)
+ );
+}
+
+export function getNftMimeType(
+ nft: BaseNFT | NFTLite | undefined
+): string | null {
+ if (!nft) {
+ return null;
+ }
+
+ const metadata =
+ "metadata" in nft &&
+ nft.metadata !== null &&
+ typeof nft.metadata === "object"
+ ? (nft.metadata as NFTMediaMetadata)
+ : undefined;
+
+ const metadataMimeType = getFileMimeTypeFromMetadata(metadata);
+ if (metadataMimeType) {
+ return metadataMimeType;
+ }
+
+ const animationSrc = getResolvedAnimationSrc(nft);
+ const animationMimeType = getMimeTypeFromFormat(
+ getUrlExtension(animationSrc)
+ );
+ if (animationMimeType) {
+ return animationMimeType;
+ }
+
+ if (animationSrc) {
+ return "video/mp4";
+ }
+
+ const imageSrc = getResolvedImageSrc(nft);
+ const imageMimeType = getMimeTypeFromFormat(getUrlExtension(imageSrc));
+ if (imageMimeType) {
+ return imageMimeType;
+ }
+
+ if (imageSrc) {
+ return "image/jpeg";
+ }
+
+ return null;
+}
+
export function getDimensionsFromMetadata(metadata: NFTMediaMetadata) {
return (
getAnimationDimensionsFromMetadata(metadata) ??
diff --git a/hooks/useNowMintingStatus.ts b/hooks/useNowMintingStatus.ts
index d3c0c0c5fc..d744428026 100644
--- a/hooks/useNowMintingStatus.ts
+++ b/hooks/useNowMintingStatus.ts
@@ -7,6 +7,7 @@ type NowMintingStatus = {
readonly nft: NFTWithMemesExtendedData | undefined;
readonly isFetching: boolean;
readonly status: ManifoldClaimStatus | null;
+ readonly isDropComplete: boolean;
readonly isStatusLoading: boolean;
readonly error: unknown;
};
@@ -19,6 +20,7 @@ export const useNowMintingStatus = (): NowMintingStatus => {
nft,
isFetching,
status: claim?.status ?? null,
+ isDropComplete: claim?.isDropComplete ?? false,
isStatusLoading: !!nft && !claim,
error,
};