Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
27 changes: 27 additions & 0 deletions app/src/app/marketplace/configuration/collection/content.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"use client";

import { ConfigurationContractClient } from "@/chain/client";
import { useSearchParams } from "next/navigation";
import { NftProvider } from "@/chain/client";
import { ConfigurationCards } from "@/components/ConfigurationCards/ConfigurationCards";

export function Content() {
const searchParams = useSearchParams();
const contractId = searchParams.get("id");
if (!contractId) return;

return (
<NftProvider
queryClientClass={ConfigurationContractClient}
fetchInfo={async (client, tokenId) => {
return await client.nftInfo({ tokenId });
}}
fetchNft={async (client, limit, startAfter) => {
return await client.allTokens({ limit, startAfter });
}}
contractId={contractId}
>
<ConfigurationCards id={contractId} />
</NftProvider>
);
}
18 changes: 18 additions & 0 deletions app/src/app/marketplace/configuration/collection/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Inset } from "@/components/AppSidebar";
import { Content } from "./content";
import { Metadata } from "next";

export const metadata: Metadata = {
title: "Collection Configurations",
description: "List of Collection Configurations.",
};

export default async function CollectionConfigurationsPage() {
return (
<Inset pageTitle={metadata.title as string}>
<div className="flex h-full items-center flex-col p-8 relative">
<Content />
</div>
</Inset>
);
}
14 changes: 11 additions & 3 deletions app/src/app/marketplace/configuration/content.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
"use client";

import { ContractsProvider, NftContractClient } from "@/chain/client";
import { ContractsProvider, ConfigurationContractClient } from "@/chain/client";
import { ContractsTable } from "@/components/ContractsTable";
import { useRouter } from "next/navigation";

export function Content() {
const contractCodeId = process.env.NEXT_PUBLIC_CONFIGURATION_CONTRACT_ID;
const router = useRouter();

if (!contractCodeId) return;
return (
<ContractsProvider
queryClientClass={NftContractClient}
queryClientClass={ConfigurationContractClient}
fetchInfo={async (client) => {
const info = await client.contractInfo();
return {
Expand All @@ -19,7 +21,13 @@ export function Content() {
}}
contractCodeId={contractCodeId}
>
<ContractsTable />
<ContractsTable
onRowClick={(row) => {
router.push(
`/marketplace/configuration/collection?id=${row.address}`,
);
}}
/>
</ContractsProvider>
);
}
6 changes: 3 additions & 3 deletions app/src/app/marketplace/gas/collection/content.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { NftContractClient } from "@/chain/client";
import { GasContractClient } from "@/chain/client";
import { useSearchParams } from "next/navigation";
import { NftProvider } from "@/chain/client";
import { GasCards } from "@/components/GasCards";
Expand All @@ -11,8 +11,8 @@ export function Content() {
if (!contractId) return;

return (
<NftProvider<NftContractClient>
queryClientClass={NftContractClient}
<NftProvider
queryClientClass={GasContractClient}
fetchInfo={async (client, tokenId) => {
return await client.nftInfo({ tokenId });
}}
Expand Down
4 changes: 2 additions & 2 deletions app/src/app/marketplace/gas/content.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { ContractsProvider, NftContractClient } from "@/chain/client";
import { ContractsProvider, GasContractClient } from "@/chain/client";
import { ContractsTable } from "@/components/ContractsTable";
import { useRouter } from "next/navigation";

Expand All @@ -11,7 +11,7 @@ export function Content() {
if (!contractCodeId) return;
return (
<ContractsProvider
queryClientClass={NftContractClient}
queryClientClass={GasContractClient}
fetchInfo={async (client) => {
const info = await client.contractInfo();
return {
Expand Down
40 changes: 17 additions & 23 deletions app/src/chain/client/cosmos/components/NftProvider/NftProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
"use client";

import {
import React, {
createContext,
useContext,
useEffect,
useState,
useCallback,
} from "react";
import { CosmWasmClient, JsonObject } from "@cosmjs/cosmwasm-stargate";
import { CosmWasmClient } from "@cosmjs/cosmwasm-stargate";
import { useToast } from "@/hooks/use-toast";
import { Nft } from "@/lib/types";
import {
Expand All @@ -16,14 +14,9 @@ import {
useNetwork,
} from "@/chain/client/cosmos";

const NftContext = createContext<NftContextType | undefined>(undefined);

const normalizeUri = (uri: string): string => {
if (!uri) return "";
return uri.startsWith("ipfs://")
? uri.replace("ipfs://", "https://ipfs.io/ipfs/")
: uri;
};
const NftContext = createContext<NftContextType<unknown> | undefined>(
undefined,
);

export function NftProvider<T>({
queryClientClass,
Expand Down Expand Up @@ -54,7 +47,7 @@ export function NftProvider<T>({
address: string,
startAfter?: string,
limit: number = 20,
): Promise<{ nft: Nft[]; nextStartAfter?: string }> => {
): Promise<{ nft: Nft<T>[]; nextStartAfter?: string }> => {
if (!client) return { nft: [] };

try {
Expand All @@ -69,18 +62,17 @@ export function NftProvider<T>({
const nftInfo = await fetchInfo(queryClient, tokenId);
const tokenUri = nftInfo.token_uri || "";

let metadata: JsonObject = {};
let metadata = {} as T;
if (tokenUri) {
const res = await fetch(normalizeUri(tokenUri));
if (res.ok) metadata = await res.json();
const res = await fetch(tokenUri);
if (res.ok) {
metadata = (await res.json()) as T;
}
}

return {
tokenId,
tokenUri,
name: metadata.name,
image: normalizeUri(metadata.image),
description: metadata.description,
metadata,
};
}),
);
Expand All @@ -105,8 +97,10 @@ export function NftProvider<T>({
);
}

export const useNft = () => {
const context = useContext(NftContext);
export function useNft<T>() {
const context = useContext(
NftContext as React.Context<NftContextType<T> | undefined>,
);
if (!context) throw new Error("useNft must be used within a NftProvider");
return context;
};
}
3 changes: 2 additions & 1 deletion app/src/chain/client/cosmos/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from "./components";
export * from "./lib/types";
export * from "./hooks";
export { Cw721baseQueryClient as NftContractClient } from "@/../../generated/Cw721base.client";
export { Cw721baseQueryClient as GasContractClient } from "@/../../generated/Cw721base.client";
export { Cw2981ConfigurationQueryClient as ConfigurationContractClient } from "@/../../generated/Cw2981Configuration.client";
4 changes: 2 additions & 2 deletions app/src/chain/client/cosmos/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,13 @@ export interface ContractsContextType {
loading: boolean;
}

export interface NftContextType {
export interface NftContextType<T> {
getCollectionNft: (
address: string,
startAfter?: string,
limit?: number,
) => Promise<{
nft: Nft[];
nft: Nft<T>[];
nextStartAfter?: string;
}>;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import { createContext, useContext, useCallback } from "react";
import { Nft } from "@/lib/types";
import { NftContextType, NftProviderProps } from "@/chain/client/cosmos";

const NftContext = createContext<NftContextType | undefined>(undefined);
const NftContext = createContext<NftContextType<unknown> | undefined>(
undefined,
);

export function NftProvider<T>({ children }: NftProviderProps<T>) {
const getCollectionNft = useCallback(async (): Promise<{
Expand All @@ -21,8 +23,10 @@ export function NftProvider<T>({ children }: NftProviderProps<T>) {
);
}

export const useNft = () => {
const context = useContext(NftContext);
export function useNft<T>() {
const context = useContext(
NftContext as React.Context<NftContextType<T> | undefined>,
);
if (!context) throw new Error("useNft must be used within a NftProvider");
return context;
};
}
116 changes: 116 additions & 0 deletions app/src/components/ConfigurationCards/ConfigurationCards.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { Configuration, Nft } from "@/lib/types";
import {
Card,
CardHeader,
CardTitle,
CardDescription,
CardContent,
} from "@/components/ui/card";
import { Pagination } from "@/components/Pagination";
import { useEffect, useState } from "react";
import Image from "next/image";
import { useNft } from "@/chain/client";

const NFT_CARDS_QUANTITY = 20;

export function ConfigurationCards({ id }: { id: string }) {
const { getCollectionNft } = useNft<Configuration>();
const [nft, setNft] = useState<Nft<Configuration>[]>([]);
const [pageLoading, setPageLoading] = useState(false);
const [page, setPage] = useState(0);
const [startAfterMap, setStartAfterMap] = useState<{
[key: number]: string | undefined;
}>({ 0: undefined });
const [hasNextPage, setHasNextPage] = useState(false);

useEffect(() => {
const fetch = async () => {
setPageLoading(true);
const startAfter = startAfterMap[page];

try {
const { nft: fetchedNft, nextStartAfter } = await getCollectionNft(
id,
startAfter,
NFT_CARDS_QUANTITY,
);
setNft(fetchedNft);
setHasNextPage(!!nextStartAfter);

if (nextStartAfter) {
setStartAfterMap((prev) => ({
...prev,
[page + 1]: nextStartAfter,
}));
}
} finally {
setPageLoading(false);
}
};

fetch();
}, [id, page, getCollectionNft]);

const handlePageChange = (newPage: number) => {
if (newPage < 0) return;
if (newPage > page && !hasNextPage) return;
setPage(newPage);
};

return (
<div className="max-w-[1400px] w-full">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-4">
{nft.map((n) => (
<Card key={n.tokenId} className="overflow-hidden">
{n.metadata.image ? (
<div className="relative w-full pt-[100%]">
<Image
src={n.metadata.image}
alt={n.metadata.name || n.tokenId}
fill
className="object-contain"
/>
</div>
) : (
<div className="w-full aspect-square bg-gray-200 flex items-center justify-center">
No Image
</div>
)}
<CardHeader>
<CardTitle className="text-lg truncate">
{n.metadata.name || `Token #${n.tokenId}`}
</CardTitle>
{n.metadata.description && (
<CardDescription className="line-clamp-2">
{n.metadata.description}
</CardDescription>
)}
</CardHeader>
<CardContent>
{n.metadata.crossplane_version && (
<p className="text-xs text-muted-foreground mt-2">
Crossplane Version: {n.metadata.crossplane_version}
</p>
)}
</CardContent>
</Card>
))}
</div>

{nft.length > 0 ? (
<div className="mt-6">
<Pagination
page={page}
hasNextPage={hasNextPage}
onPageChange={handlePageChange}
loading={pageLoading}
/>
</div>
) : (
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 flex">
<p>No Nft found for the selected collection.</p>
</div>
)}
</div>
);
}
1 change: 1 addition & 0 deletions app/src/components/ConfigurationCards/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { ConfigurationCards } from "./ConfigurationCards";
Loading