-
+
{loading &&
Loading…
}
{error && (
diff --git a/components/drops/view/item/content/media/MediaDisplay.tsx b/components/drops/view/item/content/media/MediaDisplay.tsx
index 855cb035b3..e200b4647d 100644
--- a/components/drops/view/item/content/media/MediaDisplay.tsx
+++ b/components/drops/view/item/content/media/MediaDisplay.tsx
@@ -2,11 +2,11 @@
import { assertUnreachable } from "@/helpers/AllowlistToolHelpers";
import InteractiveMediaLoadGate from "@/components/drops/media/InteractiveMediaLoadGate";
-import { parseIpfsUrl } from "@/helpers/Helpers";
import dynamic from "next/dynamic";
-import { useState } from "react";
+import { useEffect, useMemo, useState } from "react";
import SandboxedExternalIframe from "@/components/common/SandboxedExternalIframe";
+import { getArweaveGatewayFallbackUrls } from "@/components/nft-image/utils/gateway-fallback";
import { ImageScale } from "@/helpers/image.helpers";
import MediaDisplayAudio from "./MediaDisplayAudio";
import MediaDisplayImage from "./MediaDisplayImage";
@@ -26,22 +26,66 @@ const MediaDisplayGLB = dynamic(() => import("./MediaDisplayGLB"), {
});
const DEFAULT_HTML_MEDIA_TITLE = "Interactive HTML media";
-
-const normalizeMediaUrl = (mediaUrl: string): string => parseIpfsUrl(mediaUrl);
+const IFRAME_FALLBACK_TIMEOUT_MS = 8000;
function InteractiveHtmlMediaDisplay({
media_url,
previewImageUrl,
imageScale = ImageScale.AUTOx1080,
+ requireInteractionToLoad = false,
}: {
readonly media_url: string;
readonly previewImageUrl?: string | null | undefined;
readonly imageScale?: ImageScale | undefined;
+ readonly requireInteractionToLoad?: boolean | undefined;
}) {
- const [isActivated, setIsActivated] = useState(false);
- const normalizedMediaUrl = normalizeMediaUrl(media_url);
+ const [isActivated, setIsActivated] = useState(!requireInteractionToLoad);
+ const urls = useMemo(
+ () => getArweaveGatewayFallbackUrls(media_url),
+ [media_url]
+ );
+ const [activeIndex, setActiveIndex] = useState(0);
+ const [didLoadCurrentUrl, setDidLoadCurrentUrl] = useState(false);
+ const activeUrl = urls[activeIndex];
+
+ useEffect(() => {
+ setIsActivated(!requireInteractionToLoad);
+ }, [media_url, requireInteractionToLoad]);
+
+ useEffect(() => {
+ setActiveIndex(0);
+ setDidLoadCurrentUrl(false);
+ }, [urls]);
- if (!isActivated) {
+ useEffect(() => {
+ setDidLoadCurrentUrl(false);
+ }, [activeUrl]);
+
+ useEffect(() => {
+ if (!activeUrl || didLoadCurrentUrl || activeIndex + 1 >= urls.length) {
+ return;
+ }
+
+ const timeoutId = globalThis.setTimeout(() => {
+ setActiveIndex((current) =>
+ current === activeIndex && current + 1 < urls.length
+ ? current + 1
+ : current
+ );
+ }, IFRAME_FALLBACK_TIMEOUT_MS);
+
+ return () => {
+ globalThis.clearTimeout(timeoutId);
+ };
+ }, [activeIndex, activeUrl, didLoadCurrentUrl, urls.length]);
+
+ const advanceToNextUrl = () => {
+ setActiveIndex((current) =>
+ current + 1 < urls.length ? current + 1 : current
+ );
+ };
+
+ if (requireInteractionToLoad && !isActivated) {
return (
setIsActivated(true)}>
{previewImageUrl ? (
@@ -51,11 +95,20 @@ function InteractiveHtmlMediaDisplay({
);
}
+ if (!activeUrl) {
+ return null;
+ }
+
return (
{
+ setDidLoadCurrentUrl(true);
+ }}
+ onError={advanceToNextUrl}
/>
);
}
@@ -87,7 +140,6 @@ export default function MediaDisplay({
readonly previewImageUrl?: string | null | undefined;
readonly requireInteractionToLoad?: boolean | undefined;
}) {
- const normalizedMediaUrl = normalizeMediaUrl(media_url);
const getMediaType = (): MediaType => {
if (media_mime_type.includes("image")) {
return MediaType.IMAGE;
@@ -114,13 +166,14 @@ export default function MediaDisplay({
const mediaType = getMediaType();
- if (mediaType === MediaType.HTML && requireInteractionToLoad) {
+ if (mediaType === MediaType.HTML) {
return (
);
}
@@ -154,14 +207,6 @@ export default function MediaDisplay({
disableMediaInteractions={disableMediaInteraction}
/>
);
- case MediaType.HTML:
- return (
-
- );
case MediaType.UNKNOWN:
return <>>;
default:
diff --git a/components/home/now-minting/NowMintingStatsGrid.tsx b/components/home/now-minting/NowMintingStatsGrid.tsx
index ea5d8fe20d..b0c2875f3f 100644
--- a/components/home/now-minting/NowMintingStatsGrid.tsx
+++ b/components/home/now-minting/NowMintingStatsGrid.tsx
@@ -37,7 +37,7 @@ export default function NowMintingStatsGrid({
const statusLabel = manifoldClaim
? formatClaimStatus(manifoldClaim)
: undefined;
- const statusTone = manifoldClaim?.isFinalized ? "ended" : status;
+ const statusTone = manifoldClaim?.isDropComplete ? "ended" : status;
const editionSize = manifoldClaim
? formatEditionSize(manifoldClaim).replace(/\s*\/\s*/, "/")
diff --git a/components/leaderboard/Leaderboard.module.scss b/components/leaderboard/Leaderboard.module.scss
index fec5ac6260..fdca165a8c 100644
--- a/components/leaderboard/Leaderboard.module.scss
+++ b/components/leaderboard/Leaderboard.module.scss
@@ -230,6 +230,14 @@
}
}
+.collectorLevel {
+ span {
+ float: none !important;
+ margin-top: 0 !important;
+ margin-bottom: 0 !important;
+ }
+}
+
@media only screen and (max-width: 1600px) {
.hodler {
white-space: break-spaces !important;
@@ -265,6 +273,12 @@
padding: 0.5rem;
}
+.leaderboardTable tbody > tr > *,
+.nftLeaderboardTable tbody > tr > * {
+ padding-top: 0.5rem;
+ padding-bottom: 0.5rem;
+}
+
.borderBottom {
border-bottom: 0.5px solid variables.$font-color !important;
padding: 0rem !important;
@@ -430,7 +444,8 @@
padding: 15px 20px;
width: fit-content;
color: white;
- box-shadow: 0.5px 0.5px 3px 0px rgba(255, 255, 255, 0.02),
+ box-shadow:
+ 0.5px 0.5px 3px 0px rgba(255, 255, 255, 0.02),
1px 3px 6px 0px rgba(0, 0, 0, 0.15),
-0.5px -1px 3px 0px rgba(255, 255, 255, 0.02),
-3px -3px 5px 0px rgba(255, 255, 255, 0.02) inset,
diff --git a/components/leaderboard/LeaderboardCollector.tsx b/components/leaderboard/LeaderboardCollector.tsx
index 9f6e5ec3f8..e7653f64e6 100644
--- a/components/leaderboard/LeaderboardCollector.tsx
+++ b/components/leaderboard/LeaderboardCollector.tsx
@@ -1,6 +1,9 @@
import { formatAddress } from "@/helpers/Helpers";
import { getScaledImageUri, ImageScale } from "@/helpers/image.helpers";
-import UserCICAndLevel from "../user/utils/UserCICAndLevel";
+import UserCICAndLevel, {
+ UserCICAndLevelSize,
+} from "@/components/user/utils/UserCICAndLevel";
+import styles from "./Leaderboard.module.scss";
export function LeaderboardCollector(
props: Readonly<{
@@ -11,6 +14,7 @@ export function LeaderboardCollector(
level: number;
}>
) {
+ const hasLevel = props.level !== undefined && props.level !== null;
const link = props.handle ?? props.consolidationKey.split("-")[0];
let display =
props.handle ??
@@ -24,7 +28,7 @@ export function LeaderboardCollector(
) : (
<>>
@@ -34,15 +38,21 @@ export function LeaderboardCollector(
diff --git a/components/manifold-minting/ManifoldMinting.module.scss b/components/manifold-minting/ManifoldMinting.module.scss
deleted file mode 100644
index 67fdb71f11..0000000000
--- a/components/manifold-minting/ManifoldMinting.module.scss
+++ /dev/null
@@ -1,34 +0,0 @@
-@use "sass:color";
-@use "../../styles/variables.scss" as variables;
-
-.claimed {
- color: variables.$project-green;
-}
-
-.spotsTable {
- tr td {
- width: 50%;
- }
- margin-bottom: 0;
-}
-
-.descriptionClamped {
- display: -webkit-box;
- -webkit-line-clamp: 3;
- line-clamp: 3;
- -webkit-box-orient: vertical;
- overflow: hidden;
- text-overflow: ellipsis;
-}
-
-.phaseBox {
- border: 3px solid rgb(23, 22, 28);
- background-color: rgb(23, 22, 28);
- padding: 18px;
-}
-
-.phaseBoxActive {
- border: 3px solid white;
- background-color: rgb(23, 22, 28);
- padding: 18px;
-}
diff --git a/components/manifold-minting/ManifoldMinting.tsx b/components/manifold-minting/ManifoldMinting.tsx
index a97e5af6da..acb011c611 100644
--- a/components/manifold-minting/ManifoldMinting.tsx
+++ b/components/manifold-minting/ManifoldMinting.tsx
@@ -1,13 +1,27 @@
"use client";
+import clsx from "clsx";
import Link from "next/link";
-import { useEffect, useMemo, useRef, useState } from "react";
-import { Col, Container, Row, Table } from "react-bootstrap";
+import {
+ type ReactNode,
+ type CSSProperties,
+ useEffect,
+ useMemo,
+ useRef,
+ useState,
+} from "react";
import { Spinner } from "@/components/dotLoader/DotLoader";
import DropForgeTestnetIndicator from "@/components/drop-forge/DropForgeTestnetIndicator";
import NowMintingCountdown from "@/components/home/now-minting/NowMintingCountdown";
+import NFTMarketplaceLinks from "@/components/nft-marketplace-links/NFTMarketplaceLinks";
import NFTAttributes from "@/components/nft-attributes/NFTAttributes";
import NFTImage from "@/components/nft-image/NFTImage";
+import DropPfp from "@/components/drops/create/utils/DropPfp";
+import UserCICAndLevel, {
+ UserCICAndLevelSize,
+} from "@/components/user/utils/UserCICAndLevel";
+import { DropAuthorBadges } from "@/components/waves/drops/DropAuthorBadges";
+import UserProfileTooltipWrapper from "@/components/utils/tooltip/UserProfileTooltipWrapper";
import {
ETHEREUM_ICON_TEXT,
MANIFOLD_LAZY_CLAIM_CONTRACT,
@@ -23,14 +37,16 @@ import {
numberWithCommas,
parseNftDescriptionToHtml,
} from "@/helpers/Helpers";
+import { getBannerColorValue } from "@/helpers/profile-banner.helpers";
import { Time } from "@/helpers/time";
import type { ManifoldClaim, MemePhase } from "@/hooks/useManifoldClaim";
import {
buildMemesPhases,
ManifoldClaimStatus,
+ ManifoldPhase,
useManifoldClaim,
} from "@/hooks/useManifoldClaim";
-import styles from "./ManifoldMinting.module.scss";
+import { useIdentity } from "@/hooks/useIdentity";
import ManifoldMintingWidget from "./ManifoldMintingWidget";
import type {
ArweaveAttribute,
@@ -38,6 +54,7 @@ import type {
ManifoldMintMetadata,
} from "./manifold-mint-metadata";
import type { Chain } from "viem";
+import WalletConnectBalance from "@/components/wallet-connect-balance/WalletConnectBalance";
interface Props {
title: string;
@@ -46,6 +63,7 @@ interface Props {
abi: any;
mint_date: Time;
mintMetadata: ManifoldMintMetadata;
+ standalone?: boolean;
}
interface MintMetadata {
@@ -80,6 +98,276 @@ function getDateTimeString(time: Time, local_timezone: boolean) {
return `${d} ${t?.slice(0, 5)}`;
}
+function getFeeLabelForPhase(phase: ManifoldPhase) {
+ if (phase === ManifoldPhase.PUBLIC) {
+ return "Manifold Fee (Public)";
+ }
+
+ return "Manifold Fee (Allowlist)";
+}
+
+enum MemePhaseCardStatus {
+ UPCOMING = "UPCOMING",
+ ACTIVE = "ACTIVE",
+ COMPLETED = "COMPLETED",
+}
+
+function getMemePhaseCardStatus(
+ claim: ManifoldClaim,
+ phase: MemePhase
+): MemePhaseCardStatus {
+ if (
+ claim.memePhase?.id === phase.id &&
+ claim.status === ManifoldClaimStatus.ACTIVE
+ ) {
+ return MemePhaseCardStatus.ACTIVE;
+ }
+
+ if (phase.end.lt(Time.now()) || claim.isDropComplete) {
+ return MemePhaseCardStatus.COMPLETED;
+ }
+
+ return MemePhaseCardStatus.UPCOMING;
+}
+
+function getEligibleMintsDetails(
+ phaseId: string,
+ eligibleSpots: number | undefined,
+ status: MemePhaseCardStatus,
+ eligibilityState: "loading" | "ready" | "unavailable"
+): { text: string; className: string } {
+ if (phaseId === "public") {
+ if (status === MemePhaseCardStatus.ACTIVE) {
+ return {
+ text: "Unlimited spots",
+ className: "tw-font-semibold tw-text-success",
+ };
+ }
+
+ if (status === MemePhaseCardStatus.UPCOMING) {
+ return {
+ text: "Unlimited spots",
+ className: "tw-font-semibold tw-text-primary-300",
+ };
+ }
+
+ return {
+ text: "Unlimited spots",
+ className: "tw-font-semibold tw-text-red/75",
+ };
+ }
+
+ if (eligibilityState === "loading") {
+ return {
+ text: "Loading eligibility...",
+ className: "tw-text-iron-300",
+ };
+ }
+
+ if (eligibilityState === "unavailable") {
+ return {
+ text: "Eligibility unavailable",
+ className: "tw-text-iron-300",
+ };
+ }
+
+ if (eligibleSpots === undefined) {
+ return {
+ text: "No eligible spots",
+ className: "tw-text-iron-300",
+ };
+ }
+
+ if (eligibleSpots === 0) {
+ return {
+ text: "No eligible spots",
+ className: "tw-text-iron-300",
+ };
+ }
+
+ return {
+ text: `${eligibleSpots} eligible spot${eligibleSpots > 1 ? "s" : ""}`,
+ className: "tw-font-semibold tw-text-success",
+ };
+}
+
+function getPhaseDateLabels(status: MemePhaseCardStatus): {
+ start: string;
+ end: string;
+} {
+ if (status === MemePhaseCardStatus.ACTIVE) {
+ return { start: "Started", end: "Ends" };
+ }
+
+ if (status === MemePhaseCardStatus.COMPLETED) {
+ return { start: "Started", end: "Ended" };
+ }
+
+ return {
+ start: "Expected start",
+ end: "Expected end",
+ };
+}
+
+function getPhaseStatusClassName(status: MemePhaseCardStatus): string {
+ if (status === MemePhaseCardStatus.ACTIVE) {
+ return "tw-text-success";
+ }
+
+ if (status === MemePhaseCardStatus.UPCOMING) {
+ return "tw-text-primary-300";
+ }
+
+ return "tw-text-red/75";
+}
+
+function getHighlightedRingClassName(status: MemePhaseCardStatus): string {
+ if (status === MemePhaseCardStatus.ACTIVE) {
+ return "tw-border-success tw-bg-iron-900/60 tw-ring-1 tw-ring-inset tw-ring-success";
+ }
+
+ return "tw-border-primary-300 tw-bg-iron-900/60 tw-ring-1 tw-ring-inset tw-ring-primary-300";
+}
+
+function MetadataRow({
+ label,
+ value,
+}: {
+ readonly label: ReactNode;
+ readonly value: ReactNode;
+}) {
+ return (
+
+ |
+ {label}
+ |
+
+ {value}
+ |
+
+ );
+}
+
+function StandaloneMintPageTopBar() {
+ return (
+
+
+

+
+ The Memes by 6529 - Mint Page
+
+
+
+
+
+
+ );
+}
+
+function ArtistInfoStrip({
+ handle,
+ name,
+ standalone = false,
+}: {
+ readonly handle: string;
+ readonly name: string | undefined;
+ readonly standalone?: boolean;
+}) {
+ const { profile } = useIdentity({
+ handleOrWallet: handle,
+ initialProfile: null,
+ });
+
+ const href = `/${handle}`;
+ const displayName = profile?.handle ?? name ?? handle;
+ const standaloneDisplayName = name ?? profile?.handle ?? handle;
+ const standaloneHref = `https://6529.io/${profile?.handle ?? handle}`;
+ const badgesProfile = useMemo(() => {
+ if (!profile) {
+ return null;
+ }
+
+ return {
+ id: profile.id,
+ handle: profile.handle,
+ pfp: profile.pfp,
+ banner1_color: getBannerColorValue(profile.banner1),
+ banner2_color: getBannerColorValue(profile.banner2),
+ cic: profile.cic,
+ rep: profile.rep,
+ tdh: profile.tdh,
+ tdh_rate: profile.tdh_rate,
+ xtdh: profile.xtdh,
+ xtdh_rate: profile.xtdh_rate,
+ level: profile.level,
+ primary_wallet: profile.primary_wallet,
+ active_main_stage_submission_ids:
+ profile.active_main_stage_submission_ids,
+ winner_main_stage_drop_ids: profile.winner_main_stage_drop_ids,
+ artist_of_prevote_cards: profile.artist_of_prevote_cards,
+ is_wave_creator: profile.is_wave_creator,
+ };
+ }, [profile]);
+
+ if (standalone) {
+ return (
+
+
+
+ {standaloneDisplayName}
+
+
+ );
+ }
+
+ return (
+
+
+
+
+
+
+ {displayName}
+
+
+ {!!profile?.level && (
+
+ )}
+ {badgesProfile && (
+
+ )}
+
+
+
+ );
+}
+
export default function ManifoldMinting(props: Readonly) {
const [isError, setIsError] = useState(false);
@@ -87,7 +375,7 @@ export default function ManifoldMinting(props: Readonly) {
const [descriptionClamped, setDescriptionClamped] = useState(true);
const [needsClamping, setNeedsClamping] = useState(false);
- const descriptionRef = useRef(null);
+ const descriptionRef = useRef(null);
const { claim: manifoldClaim, isFetching: isManifoldClaimFetching } =
useManifoldClaim({
@@ -102,7 +390,13 @@ export default function ManifoldMinting(props: Readonly) {
});
const [fee, setFee] = useState(0);
- const [mintForAddress, setMintForAddress] = useState("");
+ const [mintForAddress, setMintForAddress] = useState(null);
+ const pageContainerClassName =
+ "tw-mx-auto tw-w-full tw-max-w-7xl tw-px-4 md:tw-px-6";
+ const standalonePageContainerClassName = clsx(
+ pageContainerClassName,
+ props.standalone && "tw-pt-4"
+ );
const instance = useMemo(() => {
if (
@@ -156,6 +450,31 @@ export default function ManifoldMinting(props: Readonly) {
}
return { name, handle };
}, [instance?.asset.attributes]);
+ const artistNameLink = useMemo(() => {
+ if (!artist) {
+ return undefined;
+ }
+
+ const artistLabel = artist.name ?? artist.handle;
+
+ if (!artist.handle) {
+ return artistLabel;
+ }
+
+ if (props.standalone) {
+ return (
+
+ {artistLabel}
+
+ );
+ }
+
+ return {artistLabel};
+ }, [artist, props.standalone]);
useEffect(() => {
if (instance) {
@@ -190,6 +509,8 @@ export default function ManifoldMinting(props: Readonly) {
chain={props.chain}
abi={props.abi}
claim={manifoldClaim}
+ local_timezone={isLocalTimezone}
+ hideConnect={props.standalone ?? false}
setFee={setFee}
setMintForAddress={setMintForAddress}
/>
@@ -198,26 +519,32 @@ export default function ManifoldMinting(props: Readonly) {
function printTitle() {
return (
-
-
- Mint {props.title}
-
-
+
+
+ Mint {props.title}
+
+
);
}
function printTestnetIndicator() {
- return (
-
-
-
-
-
- );
+ return ;
}
function printDistributionLink() {
const contractPath = getPathForContract(props.contract);
+ if (props.standalone) {
+ return (
+
+ Distribution Plan
+
+ );
+ }
+
return (
) {
}
function printDescription(i: MintMetadata) {
+ const rawDescription = i.asset.description ?? "";
+
return (
-
-
-
-
-
-
- {needsClamping && (
-
-
-
-
-
+
+
+ {(needsClamping ||
+ rawDescription.trim().length > 140 ||
+ rawDescription.includes("\n")) && (
+
+
+
)}
-
+
);
}
function printActions(instance: MintMetadata, manifoldClaim: ManifoldClaim) {
return (
-
-
-
-
+
+ {props.standalone ? (
+
+ {instance.asset.name ?? props.title}
+
+ ) : (
+
-
-
{instance.asset.name ?? props.title}
-
-
-
-
- {getNameForContract(props.contract)} #
- {props.mintMetadata.tokenId}
-
-
-
{printDescription(instance)}
-
-
-
-
- | Edition Size |
-
-
- {isManifoldClaimFetching && }
-
- {numberWithCommas(manifoldClaim.total)} /{" "}
- {numberWithCommas(manifoldClaim.totalMax)}
- {manifoldClaim.remaining > 0 &&
- manifoldClaim.status !==
- ManifoldClaimStatus.ENDED && (
- <> ({manifoldClaim.remaining} remaining)>
- )}
-
-
- |
-
-
-
-
-
-
+ {instance.asset.name ?? props.title}
+
+
+ )}
+
+