Skip to content

Commit

Permalink
add collection page and necessary hooks
Browse files Browse the repository at this point in the history
  • Loading branch information
samepant committed Jan 19, 2024
1 parent 39f03b2 commit 1467c93
Show file tree
Hide file tree
Showing 9 changed files with 223 additions and 21 deletions.
38 changes: 35 additions & 3 deletions frontend/src/components/NFTGrid/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ import { calculateMechAddress } from "../../utils/calculateMechAddress"
import useTokenBalances from "../../hooks/useTokenBalances"
import { getNFTContext, getNFTContexts } from "../../utils/getNFTContext"
import { MoralisNFT } from "../../types/Token"
import useCollection from "../../hooks/useCollection"

interface Props {
address: string
}

const NFTGrid: React.FC<Props> = ({ address }) => {
export const AccountNftGrid: React.FC<Props> = ({ address }) => {
const chainId = useChainId()
const { data, isLoading, error } = useTokenBalances({
accountAddress: address,
Expand All @@ -40,11 +41,42 @@ const NFTGrid: React.FC<Props> = ({ address }) => {
<ul className={classes.grid}>
{nfts.map((nft, index) => (
<li key={`${index}-${nft.token_address}`}>
<NFTGridItem chainId={chainId} nft={nft} />
<NFTGridItem chainId={chainId} nft={nft} showCollectionName />
</li>
))}
</ul>
)
}

export default NFTGrid
export const CollectionNftGrid: React.FC<Props> = ({ address }) => {
const chainId = useChainId()
const { data, isLoading, error } = useCollection({
tokenAddress: address,
chainId,
})
const nftBalances = data || []

const deployedMechs = useDeployedMechs(getNFTContexts(nftBalances), chainId)

const isDeployed = (nft: MoralisNFT) =>
deployedMechs.some(
(mech) =>
mech.chainId === chainId &&
mech.address.toLowerCase() ===
calculateMechAddress(getNFTContext(nft), chainId).toLowerCase()
)

const nfts = nftBalances.map((nft) => ({ ...nft, deployed: isDeployed(nft) }))

if (isLoading) return <Spinner />

return (
<ul className={classes.grid}>
{nfts.map((nft, index) => (
<li key={`${index}-${nft.token_address}`}>
<NFTGridItem chainId={chainId} nft={nft} />
</li>
))}
</ul>
)
}
20 changes: 5 additions & 15 deletions frontend/src/components/NFTGridItem/index.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,19 @@
import { useState } from "react"
import copy from "copy-to-clipboard"
import clsx from "clsx"
import { Link } from "react-router-dom"

import classes from "./NFTItem.module.css"
import Button from "../Button"
import { shortenAddress } from "../../utils/shortenAddress"
import Spinner from "../Spinner"
import ChainIcon from "../ChainIcon"
import { calculateMechAddress } from "../../utils/calculateMechAddress"
import { CHAINS } from "../../chains"
import { useDeployMech } from "../../hooks/useDeployMech"
import { MoralisNFT } from "../../types/Token"
import { getNFTContext } from "../../utils/getNFTContext"

interface Props {
nft: { deployed: boolean } & MoralisNFT
chainId: number
showCollectionName?: boolean
}

const NFTGridItem: React.FC<Props> = ({ nft, chainId }) => {
const NFTGridItem: React.FC<Props> = ({ nft, chainId, showCollectionName }) => {
const [imageError, setImageError] = useState(false)

const chain = CHAINS[chainId]
Expand All @@ -32,13 +26,9 @@ const NFTGridItem: React.FC<Props> = ({ nft, chainId }) => {
className={classes.linkContainer}
>
<div className={classes.header}>
<p className={classes.tokenName}>
<Link
to={`/mech/${chain.prefix}:${nft.token_address}/${nft.token_id}`}
>
{name || "..."}
</Link>
</p>
{showCollectionName && (
<p className={classes.tokenName}>{name || "..."}</p>
)}
{nft.token_id.length < 7 && (
<p className={classes.tokenId}>{nft.token_id || "..."}</p>
)}
Expand Down
33 changes: 33 additions & 0 deletions frontend/src/hooks/useCollection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { useQuery } from "@tanstack/react-query"
import { MoralisApiListResponse, MoralisNFT } from "../types/Token"

interface Props {
tokenAddress: string
chainId: number
}

if (!process.env.REACT_APP_PROXY_URL) {
throw new Error("REACT_APP_PROXY_URL not set")
}

const useCollection = ({ tokenAddress, chainId }: Props) => {
return useQuery({
queryKey: ["collection", chainId, tokenAddress],
queryFn: async () => {
if (!chainId || !tokenAddress) throw new Error("No chainId or token")

// get collection metadata
const nftRes = await fetch(
`${process.env.REACT_APP_PROXY_URL}/${chainId}/moralis/nft/${tokenAddress}`
)
if (!nftRes.ok) {
throw new Error("NFT request failed")
}
const collection = (await nftRes.json()) as MoralisApiListResponse
return collection.result as MoralisNFT[]
},
enabled: !!chainId && !!tokenAddress,
})
}

export default useCollection
34 changes: 34 additions & 0 deletions frontend/src/hooks/useCollectionMetadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { useQuery } from "@tanstack/react-query"
import { MoralisCollectionMetadata } from "../types/Token"

interface Props {
tokenAddress: string
chainId: number
}

if (!process.env.REACT_APP_PROXY_URL) {
throw new Error("REACT_APP_PROXY_URL not set")
}

const useCollectionMetadata = ({ tokenAddress, chainId }: Props) => {
return useQuery({
queryKey: ["collectionMetadata", chainId, tokenAddress],
queryFn: async () => {
if (!chainId || !tokenAddress) throw new Error("No chainId or token")

// get collection metadata
const nftRes = await fetch(
`${process.env.REACT_APP_PROXY_URL}/${chainId}/moralis/nft/${tokenAddress}/metadata`
)
if (!nftRes.ok) {
throw new Error("NFT request failed")
}
const collectionMetadata =
(await nftRes.json()) as MoralisCollectionMetadata
return collectionMetadata
},
enabled: !!chainId && !!tokenAddress,
})
}

export default useCollectionMetadata
6 changes: 5 additions & 1 deletion frontend/src/router.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React from "react"
import { createBrowserRouter } from "react-router-dom"
import Mech from "./routes/Mech"
import Account from "./routes/Account"
import Landing from "./routes/Landing"
import Collection from "./routes/Collection"

export default createBrowserRouter([
{
Expand All @@ -17,4 +17,8 @@ export default createBrowserRouter([
path: "account/:address/",
element: <Account />,
},
{
path: "collection/:address/",
element: <Collection />,
},
])
4 changes: 2 additions & 2 deletions frontend/src/routes/Account/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from "react"
import { useParams } from "react-router-dom"

import Layout from "../../components/Layout"
import NFTGrid from "../../components/NFTGrid"
import { AccountNftGrid } from "../../components/NFTGrid"
import classes from "./Account.module.css"
import { getAddress } from "viem"
import Blockie from "../../components/Blockie"
Expand Down Expand Up @@ -31,7 +31,7 @@ const Landing: React.FC = () => {
</h1>
</div>
</div>
<NFTGrid address={validAddress} />
<AccountNftGrid address={validAddress} />
</div>
</Layout>
)
Expand Down
35 changes: 35 additions & 0 deletions frontend/src/routes/Collection/Collection.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
.container {
width: 100%;
background: var(--box-bg);
border-radius: 20px;
padding: 20px;
}

.accountHeader {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
margin-bottom: 20px;
background-color: var(--box-bg);
border-radius: 10px;
padding: 10px 20px;
width: 100%;
}

.account {
display: flex;
align-items: center;
gap: 10px;
}
.title {
font-size: 1.5rem;
}
.accountHeader h1 {
font-size: 1rem;
}

.blockie {
width: 25px;
aspect-ratio: 1/1;
}
57 changes: 57 additions & 0 deletions frontend/src/routes/Collection/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React from "react"
import { useParams } from "react-router-dom"

import Layout from "../../components/Layout"
import { CollectionNftGrid } from "../../components/NFTGrid"
import classes from "./Collection.module.css"
import { getAddress } from "viem"
import Blockie from "../../components/Blockie"
import useCollectionMetadata from "../../hooks/useCollectionMetadata"
import { useChainId } from "wagmi"

const Collection: React.FC = () => {
const { address } = useParams()
const chainId = useChainId()
let validAddress = ""
try {
validAddress = getAddress(address || "")
} catch (error) {
console.log(error)
}
const { data, isLoading, error } = useCollectionMetadata({
tokenAddress: validAddress,
chainId,
})

if (validAddress) {
return (
<Layout>
<div className={classes.container}>
<div className={classes.accountHeader}>
<div className={classes.title}>
{data && !isLoading ? data.name : "Collection"}
</div>
<div className={classes.account}>
<div className={classes.blockie}>
<Blockie address={validAddress} />
</div>
<h1>
<code>{validAddress}</code>
</h1>
</div>
</div>
<CollectionNftGrid address={validAddress} />
</div>
</Layout>
)
}

return (
<Layout>
<h1>
<code>{address}</code> is not a valid address
</h1>
</Layout>
)
}
export default Collection
17 changes: 17 additions & 0 deletions frontend/src/types/Token.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,20 @@ export interface MoralisFungible {
balance: string
possible_spam?: boolean
}

export interface MoralisCollectionMetadata {
token_address: string
name: string
symbol: string
contract_type: NFTType
possible_spam: boolean
verified_collection: boolean
synced_at: string
}

export interface MoralisApiListResponse {
cursor: string
page: number
page_size: number
result: MoralisNFT[] | MoralisFungible[]
}

0 comments on commit 1467c93

Please sign in to comment.