Skip to content

Commit

Permalink
feat: integrate NFTStorefront (#6)
Browse files Browse the repository at this point in the history
* feat: integrate nftstorefront

* fix: env

* fix: error handling

* feat: setup storefront

* feat: remove item

* feat: buy button on NFT Detail Page

* feat: sell item

* feat: remove item

* feat: buy item
  • Loading branch information
LanfordCai authored Aug 5, 2023
1 parent 0f34c05 commit 50de9d0
Show file tree
Hide file tree
Showing 26 changed files with 1,393 additions and 35 deletions.
4 changes: 3 additions & 1 deletion .env.development
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ NEXT_PUBLIC_DRIZZLE_URL=https://www.drizzle33.app
NEXT_PUBLIC_INCREMENT_URL=https://app.increment.fi
NEXT_PUBLIC_LINK_URL=https://link.ecdao.org

NEXT_PUBLIC_FLOW_TOKEN_ADDRESS=0x1654653399040a61
NEXT_PUBLIC_NFTCATALOG_ADDRESS=0x49a7cda3a1eecc29
NEXT_PUBLIC_METADATAVIEWS_ADDRESS=0x1d7e57aa55817448
NEXT_PUBLIC_NONFUNGIBLE_TOKEN_ADDRESS=0x1d7e57aa55817448
NEXT_PUBLIC_FUNGIBLE_TOKEN_ADDRESS=0xf233dcee88fe0abe
NEXT_PUBLIC_FUNGIBLE_TOKEN_SWITCHBOARD_ADDRESS=0xf233dcee88fe0abe
NEXT_PUBLIC_FLOWBOX_ADDRESS=0x1b3930856571a52b
NEXT_PUBLIC_ACCOUNTBOOKMARK_ADDRESS=0x39b144ab4d348e2b
NEXT_PUBLIC_ACCOUNTBOOKMARK_ADDRESS=0x39b144ab4d348e2b
NEXT_PUBLIC_NFTSTOREFRONTV2_ADDRESS=0x4eb8a10cb9f87357
1 change: 1 addition & 0 deletions .env.development.mainnet
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ NEXT_PUBLIC_DRIZZLE_URL=https://www.drizzle33.app
NEXT_PUBLIC_INCREMENT_URL=https://app.increment.fi
NEXT_PUBLIC_LINK_URL=https://link.ecdao.org

NEXT_PUBLIC_FLOW_TOKEN_ADDRESS=0x1654653399040a61
NEXT_PUBLIC_NFTCATALOG_ADDRESS=0x49a7cda3a1eecc29
NEXT_PUBLIC_METADATAVIEWS_ADDRESS=0x1d7e57aa55817448
NEXT_PUBLIC_NONFUNGIBLE_TOKEN_ADDRESS=0x1d7e57aa55817448
Expand Down
4 changes: 3 additions & 1 deletion .env.production
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ NEXT_PUBLIC_DRIZZLE_URL=https://www.drizzle33.app
NEXT_PUBLIC_INCREMENT_URL=https://app.increment.fi
NEXT_PUBLIC_LINK_URL=https://link.ecdao.org

NEXT_PUBLIC_FLOW_TOKEN_ADDRESS=0x1654653399040a61
NEXT_PUBLIC_NFTCATALOG_ADDRESS=0x49a7cda3a1eecc29
NEXT_PUBLIC_METADATAVIEWS_ADDRESS=0x1d7e57aa55817448
NEXT_PUBLIC_NONFUNGIBLE_TOKEN_ADDRESS=0x1d7e57aa55817448
NEXT_PUBLIC_FUNGIBLE_TOKEN_ADDRESS=0xf233dcee88fe0abe
NEXT_PUBLIC_FUNGIBLE_TOKEN_SWITCHBOARD_ADDRESS=0xf233dcee88fe0abe
NEXT_PUBLIC_FLOWBOX_ADDRESS=0x1b3930856571a52b
NEXT_PUBLIC_ACCOUNTBOOKMARK_ADDRESS=0x39b144ab4d348e2b
NEXT_PUBLIC_ACCOUNTBOOKMARK_ADDRESS=0x39b144ab4d348e2b
NEXT_PUBLIC_NFTSTOREFRONTV2_ADDRESS=0x4eb8a10cb9f87357
178 changes: 154 additions & 24 deletions components/common/NFTDetailView.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,72 @@
import { GiftIcon, ShareIcon } from "@heroicons/react/outline"
import { CurrencyDollarIcon, GiftIcon, ShareIcon } from "@heroicons/react/outline"
import Decimal from "decimal.js"
import Image from "next/image"
import { useRouter } from "next/router"
import { useRecoilState } from "recoil"
import { getImageSrcFromMetadataViewsFile, getRarityColor } from "../../lib/utils"
import { getImageSrcFromMetadataViewsFile, getRarityColor, getResourceType, isValidFlowAddress } from "../../lib/utils"
import NFTTransferModal from "./NFTTransferModal"
import {
basicNotificationContentState,
showBasicNotificationState,
transactionStatusState,
transactionInProgressState,
showNftTransferState
showNftTransferState,
showCreateListingState
} from "../../lib/atoms"
import publicConfig from "../../publicConfig"
import useSWR from "swr"
import { useSWRConfig } from 'swr'
import { useEffect, useState } from "react"
import { getExistingListings } from "../../flow/storefront_scripts"
import CreateListingModal from "../storefront/CreateListingModal"
import { buyItem, removeItem } from "../../flow/storefront_transactions"

const listingInfoFetcher = async (funcName, address, contractName, contractAddress, tokenId) => {
const listings = await getExistingListings(address, contractName, contractAddress, tokenId)
const sortedListings = listings.sort((a, b) => {
return parseInt(b.listingResourceId) - parseInt(a.listingResourceId)
})
return sortedListings
}

const extractContractInfo = (metadata) => {
if (!metadata) {
return { contractName: null, contractAddress: null }
}

let collectionType = getResourceType(metadata.collectionData.providerLinkedType)
let contractAddress = `0x${collectionType.split(".")[1]}`
let contractName = collectionType.split(".")[2]
return { contractName: contractName, contractAddress: contractAddress }
}

export default function NFTDetailView(props) {
const router = useRouter()
const { collection: collectionPath, token_id: tokenID } = router.query
const { mutate } = useSWRConfig()
const [, setShowBasicNotification] = useRecoilState(showBasicNotificationState)
const [, setBasicNotificationContent] = useRecoilState(basicNotificationContentState)
const [transactionInProgress, setTransactionInProgress] = useRecoilState(transactionInProgressState)
const [, setTransactionStatus] = useRecoilState(transactionStatusState)
const [showNftTransfer, setShowNftTransfer] = useRecoilState(showNftTransferState)
const [, setShowCreateListing] = useRecoilState(showCreateListingState)

const { metadata, user, account } = props
const { contractName, contractAddress } = extractContractInfo(metadata)

const [listingInfo, setListingInfo] = useState(null)
const { data: itemsData, error: itemsError } = useSWR(
publicConfig.chainEnv == "mainnet" && account && isValidFlowAddress(account) && contractName && contractAddress ? ["listingInfoFetcher", account, contractName, contractAddress, tokenID] : null, listingInfoFetcher
)

useEffect(() => {
if (!itemsData) return
if (itemsData.length > 0) {
setListingInfo(itemsData[0])
} else {
setListingInfo(null)
}
}, [itemsData])

const getMediasView = (metadata) => {
const medias = metadata.medias
Expand Down Expand Up @@ -191,6 +235,68 @@ export default function NFTDetailView(props) {
)
}

const getListingInfo = (listingInfo) => {
if (!listingInfo) return null
if (user && user.loggedIn && user.addr == account) {
return (
<div className="flex gap-x-2 items-center">
<div className="w-[32px] h-[32px] relative">
<Image src="/flow_logo.png" alt="" fill sizes="16vw" priority={true} />
</div>
<label className="font-semibold text-black text-3xl">{`${new Decimal(listingInfo.details.salePrice)}`}</label>
<button
className={`ml-3 text-black disabled:bg-drizzle-light disabled:text-gray-500 bg-drizzle hover:bg-drizzle-dark px-3 py-2 text-sm h-9 rounded-2xl font-semibold shrink-0`}
disabled={transactionInProgress}
onClick={async () => {
if (!listingInfo) return
await removeItem(listingInfo.listingResourceId, setTransactionInProgress, setTransactionStatus)
mutate(["listingInfoFetcher", account, contractName, contractAddress, tokenID])
}}
>
Remove
</button>
</div>
)
}
return (
<div className="flex gap-x-2 items-center">
<div className="w-[32px] h-[32px] relative">
<Image src="/flow_logo.png" alt="" fill sizes="16vw" priority={true} />
</div>
<label className="font-semibold text-black text-3xl">{`${new Decimal(listingInfo.details.salePrice)}`}</label>
<button
className={`ml-3 text-black disabled:bg-drizzle-light disabled:text-gray-500 bg-drizzle hover:bg-drizzle-dark px-3 py-2 text-sm h-9 rounded-2xl font-semibold shrink-0`}
disabled={transactionInProgress}
onClick={async () => {
if (!listingInfo) return
const collectionStoragePath = getCollectionStoragePath(metadata)
const res = await buyItem(
contractName, contractAddress, collectionStoragePath,
listingInfo.listingResourceId, account, setTransactionInProgress, setTransactionStatus
)
if (res && res.status === 4) {
router.push(`/account/${account}/collection/${collectionPath}`)
}
}}
>
Buy Now
</button>
</div>
)
}

const getCollectionStoragePath = (metadata) => {
const { domain, identifier }= metadata.collectionData.storagePath
const collectionStoragePath = `/${domain}/${identifier}`
return collectionStoragePath
}

const getCollectionPublicPath = (metadata) => {
const { domain, identifier }= metadata.collectionData.publicPath
const path = `/${domain}/${identifier}`
return path
}

const getDisplayView = (metadata) => {
const display = metadata.display
if (!display) return null
Expand Down Expand Up @@ -218,16 +324,27 @@ export default function NFTDetailView(props) {
<div className="w-full flex gap-x-4 justify-between items-center">
<label className="font-bold text-black text-3xl">{display.name}</label>
<div className="flex gap-x-2 justify-between items-center">
{
user && user.loggedIn && user.addr === account && !listingInfo ?
<CurrencyDollarIcon className="shrink-0 w-[32px] h-[32px] p-2 rounded-full text-gray-700 bg-drizzle hover:bg-drizzle-dark"
onClick={async () => {
if (transactionInProgress) {
return
}

setShowCreateListing(true)
}} /> : null
}
{
user && user.loggedIn && user.addr === account ?
<GiftIcon className="shrink-0 w-[32px] h-[32px] p-2 rounded-full text-gray-700 bg-drizzle hover:bg-drizzle-dark"
onClick={async () => {
if (transactionInProgress) {
return
}
<GiftIcon className="shrink-0 w-[32px] h-[32px] p-2 rounded-full text-gray-700 bg-drizzle hover:bg-drizzle-dark"
onClick={async () => {
if (transactionInProgress) {
return
}

setShowNftTransfer(true)
}} /> : null
setShowNftTransfer(true)
}} /> : null
}
<ShareIcon className="shrink-0 w-[32px] h-[32px] p-2 rounded-full text-gray-700 bg-drizzle hover:bg-drizzle-dark"
onClick={async () => {
Expand All @@ -253,20 +370,25 @@ export default function NFTDetailView(props) {

<label className="text-black text-base">{display.description}</label>
</div>
{
externalURL && externalURL.url ?
<div className="font-semibold">
{`View on `}
<a href={externalURL.url}
target="_blank"
rel="noopener noreferrer"
className="underline font-bold decoration-drizzle decoration-2"
>
{new URL(externalURL.url).hostname}
</a>
</div>
: null
}
<div className="flex flex-col gap-y-4">
{
getListingInfo(listingInfo)
}
{
externalURL && externalURL.url ?
<div className="font-semibold h-[24px]">
{`View on `}
<a href={externalURL.url}
target="_blank"
rel="noopener noreferrer"
className="underline font-bold decoration-drizzle decoration-2"
>
{new URL(externalURL.url).hostname}
</a>
</div>
: <div className="h-[24px] invisible">Placeholder</div>
}
</div>
</div>
</div>
)
Expand Down Expand Up @@ -294,6 +416,14 @@ export default function NFTDetailView(props) {
collectionStoragePath={`/storage/${metadata.collectionData.storagePath.identifier}`}
collectionPublicPath={`/public/${metadata.collectionData.publicPath.identifier}`}
/>
<CreateListingModal
account={account}
tokenId={tokenID}
collectionStoragePath={getCollectionStoragePath(metadata)}
collectionPublicPath={getCollectionPublicPath(metadata)}
contractName={contractName}
contractAddress={contractAddress}
/>
</div>
</>
)
Expand Down
15 changes: 8 additions & 7 deletions components/common/Siderbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,16 @@ export default function Sidebar(props) {
let menuItems = [
{ id: "0", label: `Basic`, link: { pathname: "/account/[account]", query: { account: account } } },
{ id: "1", label: `Key`, link: { pathname: "/account/[account]/key", query: { account: account } } },
{ id: "2", label: `Token`, link: { pathname: "/account/[account]/fungible_token", query: { account: account } } },
{ id: "3", label: `Staking`, link: { pathname: "/account/[account]/staking", query: { account: account } } },
{ id: "2", label: `Staking`, link: { pathname: "/account/[account]/staking", query: { account: account } } },
{ id: "3", label: `Token`, link: { pathname: "/account/[account]/fungible_token", query: { account: account } } },
{ id: "4", label: `Collection`, link: { pathname: "/account/[account]/collection", query: { account: account } } },
{ id: "5", label: `Contract`, link: { pathname: "/account/[account]/contract", query: { account: account } } },
{ id: "5", label: `Storefront`, link: { pathname: "/account/[account]/storefront", query: { account: account } } },
{ id: "6", label: `Contract`, link: { pathname: "/account/[account]/contract", query: { account: account } } },
{
id: "6", label: "Storage", subItems: [
{ id: "6-0", isSubItem: true, label: "Public Items", smLabel: "Public", link: { pathname: "/account/[account]/public", query: { account: account } } },
{ id: "6-1", isSubItem: true, label: "Stored Items", smLabel: "Stored", link: { pathname: "/account/[account]/storage", query: { account: account } } },
{ id: "6-2", isSubItem: true, label: "Private Items", smLabel: "Private", link: { pathname: "/account/[account]/private", query: { account: account } } },
id: "7", label: "Storage", subItems: [
{ id: "7-0", isSubItem: true, label: "Public Items", smLabel: "Public", link: { pathname: "/account/[account]/public", query: { account: account } } },
{ id: "7-1", isSubItem: true, label: "Stored Items", smLabel: "Stored", link: { pathname: "/account/[account]/storage", query: { account: account } } },
{ id: "7-2", isSubItem: true, label: "Private Items", smLabel: "Private", link: { pathname: "/account/[account]/private", query: { account: account } } },
]
}
// { id: "5", label: `Analyzer`, link: { pathname: "/account/[account]/analyzer", query: { account: account } } },
Expand Down
Loading

1 comment on commit 50de9d0

@vercel
Copy link

@vercel vercel bot commented on 50de9d0 Aug 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.