Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
f769b32
Standalone Minting page
prxt6529 Mar 18, 2026
f36f433
WIP
prxt6529 Mar 18, 2026
155fc86
WIP - phase detection from db merkle roots
prxt6529 Mar 19, 2026
a29e767
WIP
prxt6529 Mar 19, 2026
8690601
WIP
prxt6529 Mar 19, 2026
793c31c
WIP
prxt6529 Mar 19, 2026
efc3db8
WIP
prxt6529 Mar 19, 2026
99afff0
WIP
prxt6529 Mar 19, 2026
81f7bec
WIP
prxt6529 Mar 19, 2026
eeb8ccf
WIP
prxt6529 Mar 19, 2026
dff0636
WIP
prxt6529 Mar 19, 2026
07f6ca9
WIP
prxt6529 Mar 19, 2026
4933ec9
WIP
prxt6529 Mar 19, 2026
b2b2acb
WIP
prxt6529 Mar 19, 2026
3f0a17c
WIP
prxt6529 Mar 19, 2026
5cb1ecf
WIP
prxt6529 Mar 19, 2026
5035ea9
WIP
prxt6529 Mar 19, 2026
d092b86
WIP
prxt6529 Mar 19, 2026
1166ed0
WIP
prxt6529 Mar 19, 2026
91ed875
Merge branch 'main' into minting-page-standalone
prxt6529 Mar 19, 2026
29cd8ab
WIP
prxt6529 Mar 19, 2026
3c3a8bc
WIP
prxt6529 Mar 20, 2026
8a67566
WIP
prxt6529 Mar 20, 2026
d6dbf5c
Merge branch 'main' into minting-page-standalone
prxt6529 Mar 20, 2026
ae7b95c
WIP
prxt6529 Mar 20, 2026
492c414
WIP
prxt6529 Mar 20, 2026
278cf4a
WIP
prxt6529 Mar 20, 2026
d9e5dac
WIP
prxt6529 Mar 20, 2026
28a1f7e
WIP
prxt6529 Mar 20, 2026
a5aec50
WIP
prxt6529 Mar 20, 2026
b23fe1d
WIP
prxt6529 Mar 20, 2026
926685b
WIP
prxt6529 Mar 20, 2026
31093d5
WIP
prxt6529 Mar 20, 2026
ddd7a95
WIP
prxt6529 Mar 20, 2026
48e3ec3
WIP
prxt6529 Mar 20, 2026
ca2d125
WIP
prxt6529 Mar 20, 2026
dd987e0
WIP
prxt6529 Mar 23, 2026
914de6d
Merge branch 'main' into minting-page-standalone
prxt6529 Mar 24, 2026
157627a
WIP
prxt6529 Mar 24, 2026
8c37a8f
WIP
prxt6529 Mar 24, 2026
9df0782
WIP
prxt6529 Mar 24, 2026
ca529eb
WIP
prxt6529 Mar 24, 2026
baff12f
WIP
prxt6529 Mar 24, 2026
8ac8961
Merge branch 'main' into minting-page-standalone
prxt6529 Mar 24, 2026
5c83611
WIP
prxt6529 Mar 24, 2026
5eb7b4b
WIP
prxt6529 Mar 24, 2026
c71c3c4
WIP
prxt6529 Mar 24, 2026
330f243
Merge branch 'main' into minting-page-standalone
prxt6529 Mar 24, 2026
292595b
WIP
prxt6529 Mar 24, 2026
c106d49
WIP
prxt6529 Mar 24, 2026
bef0ee8
WIP
prxt6529 Mar 24, 2026
f6dd9bc
WIP
prxt6529 Mar 24, 2026
fbbe936
WIP
prxt6529 Mar 24, 2026
b06a8c8
WIP
prxt6529 Mar 24, 2026
240d4f6
WIP
prxt6529 Mar 24, 2026
eaaa6be
WIP
prxt6529 Mar 24, 2026
d755a0c
WIP
prxt6529 Mar 24, 2026
d94e24c
WIP
prxt6529 Mar 24, 2026
00bbaf1
WIP
prxt6529 Mar 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build-upload-deploy-prod.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Build - Upload to S3 - Deploy to Elastic Beanstalk
name: Web Deploy - PROD

on: workflow_dispatch

Expand Down
9 changes: 6 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@ storageState.json
# next.js
/.next/
/out/

# production
/build
# standalone/standalone-memes-mint
/standalone/standalone-memes-mint/src/.next/
/standalone/standalone-memes-mint/src/.next-static-export/
/standalone/standalone-memes-mint/src/out/
/standalone/standalone-memes-mint/src/public/
/standalone/standalone-memes-mint/dist/

# misc
.DS_Store
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,9 @@ jest.mock("@/hooks/useManifoldClaim", () => {
});

// Mock the constants
jest.mock("@/constants", () => ({
jest.mock("@/constants/constants", () => ({
ETHEREUM_ICON_TEXT: "Ξ",
MANIFOLD_LAZY_CLAIM_CONTRACT: "0x2",
MEMES_CONTRACT: "0x33FD426905F149f8376e227d0C9D3340AaD17aF1",
}));

Expand Down Expand Up @@ -165,6 +166,7 @@ const createMockClaim = (overrides = {}) => ({
const defaultProps = {
title: "Test Meme",
contract: "0x33FD426905F149f8376e227d0C9D3340AaD17aF1",
chain: { id: 1 } as any,
proxy: "0x2",
abi: {},
token_id: 123,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,16 @@ describe("ManifoldMintingConnect", () => {
expect(screen.getByTestId("header-connect")).toBeInTheDocument();
});

it("hides connect prompt in standalone when not connected", () => {
(mockedConnect as jest.Mock).mockReturnValue({ isConnected: false });
render(
<CookieConsentProvider>
<ManifoldMintingConnect onMintFor={jest.fn()} standalone />
</CookieConsentProvider>
);
expect(screen.queryByTestId("header-connect")).not.toBeInTheDocument();
});

it("calls onMintFor with account address on mount", () => {
const { onMintFor, seizeCtx } = renderConnected();
expect(onMintFor).toHaveBeenCalledWith(seizeCtx.address);
Expand Down
315 changes: 315 additions & 0 deletions __tests__/components/manifold-minting/ManifoldMintingPhases.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,315 @@
import ManifoldMinting from "@/components/manifold-minting/ManifoldMinting";
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";

const mockUseManifoldClaim = jest.fn();

jest.mock("next/image", () => ({
__esModule: true,
default: (props: any) => <img alt={props.alt ?? ""} {...props} />,
}));

jest.mock("next/link", () => ({
__esModule: true,
default: ({ href, children, ...props }: any) => (
<a href={href} {...props}>
{children}
</a>
),
}));

jest.mock("@reown/appkit/react", () => ({
AppKitButton: () => <div data-testid="appkit-button" />,
}));

jest.mock("@/components/drop-forge/DropForgeTestnetIndicator", () => ({
__esModule: true,
default: () => <div data-testid="testnet-indicator" />,
}));

jest.mock("@/components/home/now-minting/NowMintingCountdown", () => ({
__esModule: true,
default: () => <div data-testid="countdown" />,
}));

jest.mock("@/components/manifold-minting/ManifoldMintingWidget", () => ({
__esModule: true,
default: (props: { setMintForAddress?: (address: string | null) => void }) => (
<button
data-testid="mint-widget"
onClick={() => props.setMintForAddress?.("0xabc")}
/>
),
}));

jest.mock("@/components/nft-attributes/NFTAttributes", () => ({
__esModule: true,
default: () => <div data-testid="nft-attributes" />,
}));

jest.mock("@/components/nft-image/NFTImage", () => ({
__esModule: true,
default: () => <div data-testid="nft-image" />,
}));

jest.mock("@/components/nft-marketplace-links/NFTMarketplaceLinks", () => ({
__esModule: true,
default: () => <div data-testid="marketplace-links" />,
}));

jest.mock("@/hooks/useIdentity", () => ({
useIdentity: () => ({ profile: null }),
}));

jest.mock("@/helpers/Helpers", () => ({
areEqualAddresses: jest.fn(() => true),
capitalizeEveryWord: jest.fn((value: string) => value),
fromGWEI: jest.fn(() => 0),
getNameForContract: jest.fn(() => "Memes by 6529"),
getPathForContract: jest.fn(() => "the-memes"),
numberWithCommas: jest.fn((value: number) => value.toLocaleString()),
parseNftDescriptionToHtml: jest.fn((value: string) => value),
}));

jest.mock("@/constants/constants", () => ({
ETHEREUM_ICON_TEXT: "ETH",
MANIFOLD_LAZY_CLAIM_CONTRACT: "0xproxy",
MEMES_CONTRACT: "0xmemes",
}));

jest.mock("@/helpers/time", () => ({
Time: {
now: jest.fn(() => ({
toMillis: () => Date.UTC(2026, 2, 18, 18, 25),
toSeconds: () => Date.UTC(2026, 2, 18, 18, 25) / 1000,
toDate: () => new Date(Date.UTC(2026, 2, 18, 18, 25)),
toLocaleDateTimeString: () => "2026-03-18 18:25",
toIsoDateString: () => "2026-03-18",
toIsoTimeString: () => "18:25:00 UTC",
})),
seconds: jest.fn((seconds: number) => ({
toMillis: () => seconds * 1000,
toSeconds: () => seconds,
toDate: () => new Date(seconds * 1000),
toLocaleDateTimeString: () => `date-${seconds}`,
toIsoDateString: () => "2026-03-18",
toIsoTimeString: () => "00:00:00 UTC",
})),
},
}));

jest.mock("@/hooks/useManifoldClaim", () => {
const createPhaseTime = (ms: number) => ({
toMillis: () => ms,
toSeconds: () => ms / 1000,
toDate: () => new Date(ms),
toLocaleDateTimeString: () => `time-${ms}`,
toIsoDateString: () => "2026-03-18",
toIsoTimeString: () => "00:00:00 UTC",
lt: (other: { toMillis: () => number }) => ms < other.toMillis(),
gt: (other: { toMillis: () => number }) => ms > other.toMillis(),
gte: (other: { toMillis: () => number }) => ms >= other.toMillis(),
lte: (other: { toMillis: () => number }) => ms <= other.toMillis(),
});

return {
__esModule: true,
useManifoldClaim: mockUseManifoldClaim,
buildMemesPhases: jest.fn(() => [
{
id: "0",
name: "Phase 0 (Allowlist)",
type: "Allowlist",
start: createPhaseTime(Date.UTC(2026, 2, 18, 17, 40)),
end: createPhaseTime(Date.UTC(2026, 2, 18, 18, 20)),
},
{
id: "1",
name: "Phase 1 (Allowlist)",
type: "Allowlist",
start: createPhaseTime(Date.UTC(2026, 2, 18, 18, 30)),
end: createPhaseTime(Date.UTC(2026, 2, 18, 18, 50)),
},
{
id: "2",
name: "Phase 2 (Allowlist)",
type: "Allowlist",
start: createPhaseTime(Date.UTC(2026, 2, 18, 19, 0)),
end: createPhaseTime(Date.UTC(2026, 2, 18, 19, 20)),
},
{
id: "public",
name: "Public Phase",
type: "Public Phase",
start: createPhaseTime(Date.UTC(2026, 2, 18, 19, 20)),
end: createPhaseTime(Date.UTC(2026, 2, 19, 17, 0)),
},
]),
ManifoldClaimStatus: {
ACTIVE: "active",
UPCOMING: "upcoming",
ENDED: "ended",
},
ManifoldPhase: {
ALLOWLIST: "Allowlist",
PUBLIC: "Public Phase",
},
};
});

describe("ManifoldMinting phases", () => {
beforeEach(() => {
globalThis.fetch = jest.fn();
mockUseManifoldClaim.mockReset();
mockUseManifoldClaim.mockReturnValue({
claim: {
instanceId: 1,
total: 153,
totalMax: 328,
remaining: 175,
costWei: 0n,
startDate: Date.UTC(2026, 2, 18, 17, 40) / 1000,
endDate: Date.UTC(2026, 2, 18, 18, 20) / 1000,
status: "ended",
phase: "Allowlist",
memePhase: { id: "0", name: "Phase 0 (Allowlist)" },
nextMemePhase: { id: "1", name: "Phase 1 (Allowlist)" },
isFetching: false,
isFinalized: true,
isDropComplete: false,
isSoldOut: false,
isError: false,
},
isFetching: false,
});
});

afterEach(() => {
jest.restoreAllMocks();
});

test("shows only elapsed phases as completed during the downtime between memes phases", () => {
const { container } = render(
<ManifoldMinting
title="Test Meme"
contract="0xmemes"
chain={{ id: 1 } as any}
abi={[]}
mint_date={{ toMillis: () => Date.UTC(2026, 2, 18, 17, 40) } as any}
mintMetadata={{
tokenId: 123,
metadata: {
name: "Test NFT",
description: "Test description",
attributes: [],
image_url: "test.jpg",
},
}}
/>
);

expect(screen.getByText("Phase 0 (Allowlist)")).toBeInTheDocument();
expect(screen.getByText("Phase 1 (Allowlist)")).toBeInTheDocument();
expect(screen.getByText("Phase 2 (Allowlist)")).toBeInTheDocument();
expect(screen.getByText("Public Phase")).toBeInTheDocument();
expect(screen.getAllByText("COMPLETED")).toHaveLength(1);
expect(screen.getAllByText("UPCOMING")).toHaveLength(3);
expect(screen.getByText("Unlimited spots")).toHaveClass(
"tw-text-primary-300"
);
expect(
container.querySelectorAll(".tw-ring-success, .tw-ring-primary-300")
).toHaveLength(1);
});

test("highlights only the active phase when a current phase exists", () => {
mockUseManifoldClaim.mockReturnValue({
claim: {
instanceId: 1,
total: 153,
totalMax: 328,
remaining: 175,
costWei: 0n,
startDate: Date.UTC(2026, 2, 18, 19, 0) / 1000,
endDate: Date.UTC(2026, 2, 18, 19, 20) / 1000,
status: "active",
phase: "Allowlist",
memePhase: { id: "2", name: "Phase 2 (Allowlist)" },
nextMemePhase: { id: "public", name: "Public Phase" },
isFetching: false,
isFinalized: false,
isDropComplete: false,
isSoldOut: false,
isError: false,
},
isFetching: false,
});

const { container } = render(
<ManifoldMinting
title="Test Meme"
contract="0xmemes"
chain={{ id: 1 } as any}
abi={[]}
mint_date={{ toMillis: () => Date.UTC(2026, 2, 18, 17, 40) } as any}
mintMetadata={{
tokenId: 123,
metadata: {
name: "Test NFT",
description: "Test description",
attributes: [],
image_url: "test.jpg",
},
}}
/>
);

expect(screen.getByText("ACTIVE")).toBeInTheDocument();
expect(screen.getAllByText("UPCOMING")).toHaveLength(1);
expect(screen.getByText("Unlimited spots")).toHaveClass(
"tw-text-primary-300"
);
expect(container.querySelectorAll(".tw-ring-success")).toHaveLength(1);
expect(container.querySelectorAll(".tw-ring-primary-300")).toHaveLength(0);
});

test("shows no eligible spots after distribution data resolves without an allowlist entry", async () => {
(globalThis.fetch as jest.Mock).mockResolvedValue({
ok: true,
json: async () => ({
data: [
{
airdrops: 0,
allowlist: [],
},
],
}),
});

render(
<ManifoldMinting
title="Test Meme"
contract="0xmemes"
chain={{ id: 1 } as any}
abi={[]}
mint_date={{ toMillis: () => Date.UTC(2026, 2, 18, 17, 40) } as any}
mintMetadata={{
tokenId: 123,
metadata: {
name: "Test NFT",
description: "Test description",
attributes: [],
image_url: "test.jpg",
},
}}
/>
);

await userEvent.click(screen.getByTestId("mint-widget"));

await waitFor(() =>
expect(screen.getAllByText("No eligible spots")).toHaveLength(3)
);
expect(screen.queryByText("Loading eligibility...")).not.toBeInTheDocument();
});
});
4 changes: 3 additions & 1 deletion __tests__/components/providers/AppKitAdapterManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -659,7 +659,9 @@ describe("AppKitAdapterManager", () => {
});

it("should return empty-wallets key with chains for empty array", () => {
expect(manager["getCacheKey"]([])).toBe("empty-wallets|chains:1");
expect(manager["getCacheKey"]([])).toBe(
"empty-wallets|chains:1|platform:web"
);
});

it("should generate consistent cache key for same wallets", () => {
Expand Down
Loading
Loading