diff --git a/app/nfts/[slug]/error.tsx b/app/nfts/[slug]/error.tsx
new file mode 100644
index 0000000..fa7462e
--- /dev/null
+++ b/app/nfts/[slug]/error.tsx
@@ -0,0 +1,17 @@
+"use client";
+
+export default function Error({
+ error,
+ reset,
+}: {
+ error: Error;
+ reset: () => void;
+}) {
+ return (
+
+
Something went wrong!
+
{error.message}
+
+
+ );
+}
diff --git a/app/nfts/[slug]/layout.tsx b/app/nfts/[slug]/layout.tsx
new file mode 100644
index 0000000..0610ee2
--- /dev/null
+++ b/app/nfts/[slug]/layout.tsx
@@ -0,0 +1,14 @@
+import Header from "@/components/Header";
+
+export default function NFTsLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ return (
+
+
+ {children}
+
+ );
+}
diff --git a/app/nfts/[slug]/loading.tsx b/app/nfts/[slug]/loading.tsx
new file mode 100644
index 0000000..9384f45
--- /dev/null
+++ b/app/nfts/[slug]/loading.tsx
@@ -0,0 +1,11 @@
+import NFTCardSkeleton from "@/components/NFTCardSkeleton";
+
+export default function Loading() {
+ return (
+
+ {[...Array(8)].map((_, i) => (
+
+ ))}
+
+ );
+}
diff --git a/app/nfts/[slug]/page.tsx b/app/nfts/[slug]/page.tsx
new file mode 100644
index 0000000..b27d12d
--- /dev/null
+++ b/app/nfts/[slug]/page.tsx
@@ -0,0 +1,66 @@
+import React from "react";
+import NFTCard from "@/components/NFTCard";
+import { NFT } from "@/typings";
+import { ethers } from "ethers";
+import abi from "@/artifacts/contracts/ByteBunch.sol/ByteBunch.json";
+
+type Props = {
+ params: {
+ slug: string;
+ };
+};
+
+const ipfsToHttps = (ipfs: string) => {
+ const gateway = "https://ipfs.io/ipfs/";
+ const url = ipfs.replace("ipfs://", gateway);
+ return url;
+};
+
+const getData = async (address: string) => {
+ try {
+ const signer = new ethers.InfuraProvider(
+ "sepolia",
+ process.env.INFURA_API_KEY
+ );
+ const contract = new ethers.Contract(
+ process.env.NEXT_PUBLIC_CONTRACT_ADDRESS!,
+ abi.abi,
+ signer
+ );
+
+ const tokens = await contract.walletOfOwner(address);
+
+ const data = await Promise.all(
+ tokens.map(async (token: any) => {
+ const metadata = await contract.tokenURI(token);
+ const metadataJSON = await fetch(ipfsToHttps(metadata));
+ const metadataJSONParsed = await metadataJSON.json();
+ metadataJSONParsed.image = ipfsToHttps(metadataJSONParsed.image);
+ return metadataJSONParsed;
+ })
+ );
+
+ return data;
+ } catch (error: any) {
+ throw new Error(error.message);
+ }
+};
+
+const NFTs = async ({ params }: Props) => {
+ const address = params.slug;
+
+ const data: NFT[] = await getData(address);
+ return (
+
+ {data.length > 0 ? (
+ data.map((item) =>
)
+ ) : (
+
+ No NFTs found for address {address}
+
+ )}
+
+ );
+};
+
+export default NFTs;
diff --git a/app/nfts/page.tsx b/app/nfts/page.tsx
deleted file mode 100644
index 35b5155..0000000
--- a/app/nfts/page.tsx
+++ /dev/null
@@ -1,165 +0,0 @@
-"use client";
-import React, { use, useEffect, useState } from "react";
-import { sepolia } from "@/utils/networks";
-import { toast } from "react-hot-toast";
-import { ethers } from "ethers";
-import abi from "@/artifacts/contracts/ByteBunch.sol/ByteBunch.json";
-import Image from "next/image";
-
-type Props = {};
-
-declare let window: any;
-
-export type NFT = {
- dna: string;
- name: string;
- description: string;
- image: string;
- edition: number;
- date: number;
- attributes: Attribute[];
-};
-
-export type Attribute = {
- trait_type: string;
- value: string;
-};
-
-const NFTs = (props: Props) => {
- const [loading, setLoading] = useState(false);
- const [error, setError] = useState();
- const [data, setData] = useState([]);
-
- const switchNetwork = async () => {
- try {
- await window.ethereum.request({
- method: "wallet_switchEthereumChain",
- params: [{ chainId: sepolia.chainId }],
- });
- } catch (error) {
- toast.error("Please switch to Sepolia Network");
- }
- };
-
- const getSigner = async () => {
- try {
- const provider = new ethers.BrowserProvider(window.ethereum);
- const network = await provider.getNetwork();
- const chainId = Number(ethers.formatUnits(network.chainId)) * 10e17;
-
- if (chainId !== parseInt(sepolia.chainId)) {
- switchNetwork();
- throw new Error("Please switch to Sepolia Network and try again");
- }
-
- const signer = await provider.getSigner();
- return signer;
- } catch (error: any) {
- throw new Error(error.message.split("(")[0]);
- }
- };
-
- const getContract = async () => {
- try {
- const signer = await getSigner();
-
- const contract = new ethers.Contract(
- process.env.NEXT_PUBLIC_CONTRACT_ADDRESS!,
- abi.abi,
- signer
- );
-
- return contract;
- } catch (error: any) {
- throw new Error(error.message.split("(")[0]);
- }
- };
-
- const getTokenURI = async (tokenId: Number) => {
- try {
- const contract = await getContract();
- const tokenURI = await contract.tokenURI(tokenId);
-
- return tokenURI;
- } catch (error: any) {
- throw new Error(error.message.split("(")[0]);
- }
- };
-
- const getMetadata = async (tokenId: Number) => {
- try {
- const tokenURI = await getTokenURI(tokenId);
- const url = ipfsToHttps(tokenURI);
-
- const metadata = await fetch(url);
- const metadataJSON: NFT = await metadata.json();
- metadataJSON.image = ipfsToHttps(metadataJSON.image);
-
- return metadataJSON;
- } catch (error: any) {
- throw new Error(error.message.split("(")[0]);
- }
- };
-
- useEffect(() => {
- const loadNFTs = async () => {
- setLoading(true);
- try {
- const signer = await getSigner();
- const contract = await getContract();
- const tokens = await contract.walletOfOwner(signer.getAddress());
-
- tokens.map(async (token: any) => {
- const data = await getMetadata(token);
- setData((prev: any) => [...prev, data]);
- });
- } catch (error: any) {
- setError(error.message.split("(")[0]);
- }
- setLoading(false);
- };
- loadNFTs();
- }, []);
-
- const ipfsToHttps = (ipfs: string) => {
- const gateway = "https://ipfs.io/ipfs/";
- const url = ipfs.replace("ipfs://", gateway);
- return url;
- };
-
- if (loading) return Loading...
;
- if (error) return {error}
;
-
- return (
-
-
NFTs
-
- {data.map((item) => (
-
-
-
-
{item.name}
-
{item.description}
-
-
- {item.attributes.map((attribute) => (
-
- {attribute.trait_type}:
- {attribute.value}
-
- ))}
-
-
- ))}
-
-
- );
-};
-
-export default NFTs;
diff --git a/components/Header.tsx b/components/Header.tsx
new file mode 100644
index 0000000..8b9c953
--- /dev/null
+++ b/components/Header.tsx
@@ -0,0 +1,22 @@
+import Link from "next/link";
+
+export default function Header() {
+ return (
+
+ );
+}
diff --git a/components/NFTCard.tsx b/components/NFTCard.tsx
new file mode 100644
index 0000000..844bc3a
--- /dev/null
+++ b/components/NFTCard.tsx
@@ -0,0 +1,33 @@
+import { NFT } from "@/typings";
+import Image from "next/image";
+import Link from "next/link";
+import React from "react";
+
+type Props = {
+ data: NFT;
+};
+
+const NFTCard = ({ data }: Props) => {
+ return (
+
+
+
+
+
+
+
+ {data.name}
+
+
+
+
+ );
+};
+
+export default NFTCard;
diff --git a/components/NFTCardSkeleton.tsx b/components/NFTCardSkeleton.tsx
new file mode 100644
index 0000000..0b03ff3
--- /dev/null
+++ b/components/NFTCardSkeleton.tsx
@@ -0,0 +1,18 @@
+const NFTCardSkeleton = () => {
+ return (
+
+ );
+};
+
+export default NFTCardSkeleton;
diff --git a/typings.d.ts b/typings.d.ts
new file mode 100644
index 0000000..857eea0
--- /dev/null
+++ b/typings.d.ts
@@ -0,0 +1,14 @@
+export type NFT = {
+ dna: string;
+ name: string;
+ description: string;
+ image: string;
+ edition: number;
+ date: number;
+ attributes: Attribute[];
+};
+
+export type Attribute = {
+ trait_type: string;
+ value: string;
+};