-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
09ac1ac
commit 68a58ff
Showing
12 changed files
with
536 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import Decimal from "decimal.js" | ||
import Image from "next/image" | ||
import { useEffect, useState } from "react" | ||
|
||
// const getImageUrl = (listing) => { | ||
// const url = listing?.nft?.metadata?.imageUrl ?? "/token_placeholder.png" | ||
// return url | ||
// } | ||
|
||
const getPaymentTokenSymbol = (listing) => { | ||
const symbol = listing?.paymentTokenInfo?.symbol ?? "UNKN" | ||
return symbol.toUpperCase() | ||
} | ||
|
||
export default function Listing(props) { | ||
const { listing, typeId } = props | ||
const symbol = getPaymentTokenSymbol(listing) | ||
|
||
return ( | ||
<div className={`w-36 h-24 bg-white rounded-2xl flex flex-col gap-y-1 pt-2 pb-2 justify-between items-center shrink-0 overflow-hidden shadow-md ring-1 ring-black ring-opacity-5`}> | ||
{/* <div className="flex justify-center w-full rounded-t-2xl aspect-square bg-drizzle-ultralight relative overflow-hidden"> | ||
<Image className={"object-contain"} src={imageUrl} fill alt="" priority sizes="5vw" /> | ||
</div> */} | ||
<label className="px-3 break-words overflow-hidden text-ellipsis font-flow font-medium text-xs text-gray-400"> | ||
{`${typeId}`} | ||
</label> | ||
<label className="px-3 break-words overflow-hidden text-ellipsis font-flow font-semibold text-sm text-black"> | ||
{`${new Decimal(listing.details.salePrice).toString()} ${symbol}`} | ||
</label> | ||
<label className="px-3 font-flow font-medium text-xs text-gray-500"> | ||
{`#${listing.details.nftID}`} | ||
</label> | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import Listing from "./Listing" | ||
|
||
const getTypeId = (listing) => { | ||
if (!listing) { | ||
return "Unknown" | ||
} | ||
|
||
const rawTypeId = listing.details.nftType.typeID | ||
const comps = rawTypeId.split(".") | ||
return comps[2] | ||
} | ||
|
||
export default function ListingGroup(props) { | ||
const { listings } = props | ||
const firstListing = listings[0] | ||
const typeId = getTypeId(firstListing) | ||
|
||
return ( | ||
<div className="flex flex-col w-full gap-y-5"> | ||
<label className="text-lg font-bold"> | ||
{`${typeId} (${listings.length})`} | ||
</label> | ||
<div className="p-1 grid grid-cols-7 gap-x-2 gap-y-3 min-w-[1076px]"> | ||
{ | ||
listings.map((listing, index) => { | ||
return ( | ||
<Listing key={`listing-${index}`} listing={listing} typeId={typeId} /> | ||
) | ||
}) | ||
} | ||
</div> | ||
|
||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import * as fcl from "@onflow/fcl" | ||
|
||
export const getListings = async (address) => { | ||
const code = await (await fetch("/scripts/storefront/get_listings.cdc")).text() | ||
|
||
const result = await fcl.query({ | ||
cadence: code, | ||
args: (arg, t) => [ | ||
arg(address, t.Address) | ||
] | ||
}) | ||
|
||
return result | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
import * as fcl from "@onflow/fcl" | ||
import { txHandler } from "./transactions" | ||
|
||
export const cleanupGhosted = async ( | ||
account, | ||
listingIds, | ||
setTransactionInProgress, | ||
setTransactionStatus | ||
) => { | ||
const txFunc = async () => { | ||
return await doCleanupGhosted(account, listingIds) | ||
} | ||
|
||
return await txHandler(txFunc, setTransactionInProgress, setTransactionStatus) | ||
} | ||
|
||
const doCleanupGhosted = async (account, listingIds) => { | ||
const code = await (await fetch("/transactions/storefront/cleanup_ghosted.cdc")).text() | ||
|
||
const transactionId = fcl.mutate({ | ||
cadence: code, | ||
args: (arg, t) => [ | ||
arg(account, t.Address), | ||
arg(listingIds, t.Array(t.UInt64)) | ||
], | ||
proposer: fcl.currentUser, | ||
payer: fcl.currentUser, | ||
limit: 9999 | ||
}) | ||
|
||
return transactionId | ||
} | ||
|
||
export const cleanupPurchased = async ( | ||
account, | ||
listingIds, | ||
setTransactionInProgress, | ||
setTransactionStatus | ||
) => { | ||
const txFunc = async () => { | ||
return await doCleanupPurchased(account, listingIds) | ||
} | ||
|
||
return await txHandler(txFunc, setTransactionInProgress, setTransactionStatus) | ||
} | ||
|
||
const doCleanupPurchased = async (account, listingIds) => { | ||
const code = await (await fetch("/transactions/storefront/cleanup_purchased.cdc")).text() | ||
|
||
const transactionId = fcl.mutate({ | ||
cadence: code, | ||
args: (arg, t) => [ | ||
arg(account, t.Address), | ||
arg(listingIds, t.Array(t.UInt64)) | ||
], | ||
proposer: fcl.currentUser, | ||
payer: fcl.currentUser, | ||
limit: 9999 | ||
}) | ||
|
||
return transactionId | ||
} | ||
|
||
|
||
export const cleanupExpired = async ( | ||
account, | ||
fromIndex, toIndex, | ||
setTransactionInProgress, | ||
setTransactionStatus | ||
) => { | ||
const txFunc = async () => { | ||
return await doCleanupExpired(account, fromIndex, toIndex) | ||
} | ||
|
||
return await txHandler(txFunc, setTransactionInProgress, setTransactionStatus) | ||
} | ||
|
||
const doCleanupExpired = async (account, fromIndex, toIndex) => { | ||
const code = await (await fetch("/transactions/storefront/cleanup_expired.cdc")).text() | ||
|
||
const transactionId = fcl.mutate({ | ||
cadence: code, | ||
args: (arg, t) => [ | ||
arg(account, t.Address), | ||
arg(fromIndex, t.UInt64), | ||
arg(toIndex, t.UInt64) | ||
], | ||
proposer: fcl.currentUser, | ||
payer: fcl.currentUser, | ||
limit: 9999 | ||
}) | ||
|
||
return transactionId | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
import { CodeIcon } from "@heroicons/react/outline" | ||
import * as fcl from "@onflow/fcl" | ||
import Image from "next/image" | ||
import { useRouter } from "next/router" | ||
import { useEffect, useState } from "react" | ||
import useSWR from "swr" | ||
import Layout from "../../../../components/common/Layout" | ||
import Spinner from "../../../../components/common/Spinner" | ||
import { isValidFlowAddress } from "../../../../lib/utils" | ||
import publicConfig from "../../../../publicConfig" | ||
import Custom404 from "../404" | ||
import { getListings } from "../../../../flow/storefront_scripts" | ||
import ListingGroup from "../../../../components/storefront/ListingGroup" | ||
import { useRecoilState } from "recoil" | ||
import { | ||
transactionStatusState, | ||
transactionInProgressState | ||
} from "../../../../lib/atoms" | ||
import { cleanupGhosted, cleanupPurchased, cleanupExpired } from "../../../../flow/storefront_transactions" | ||
import { useSWRConfig } from 'swr' | ||
|
||
const listingsFetcher = async (funcName, address) => { | ||
const listings = await getListings(address) | ||
return listings | ||
} | ||
|
||
const groupListings = (listings) => { | ||
let grouped = listings.reduce((acc, listing) => { | ||
const typeId = listing.details.nftType.typeID | ||
if (!acc[typeId]) { | ||
acc[typeId] = []; | ||
} | ||
|
||
acc[typeId].push(listing) | ||
|
||
return acc | ||
}, {}) | ||
|
||
for (let type in grouped) { | ||
grouped[type].sort((a, b) => b.listingResourceId.localeCompare(a.listingResourceId)) | ||
} | ||
|
||
let sortedGroups = Object.keys(grouped) | ||
.sort() | ||
.map(key => grouped[key]); | ||
|
||
return sortedGroups | ||
} | ||
|
||
export default function Storefront(props) { | ||
const [transactionInProgress, setTransactionInProgress] = useRecoilState(transactionInProgressState) | ||
const [, setTransactionStatus] = useRecoilState(transactionStatusState) | ||
const router = useRouter() | ||
const { account } = router.query | ||
const { mutate } = useSWRConfig() | ||
|
||
const [listings, setListings] = useState(null) | ||
const [listingGroups, setListingGroups] = useState(null) | ||
const [user, setUser] = useState({ loggedIn: null }) | ||
|
||
useEffect(() => fcl.currentUser.subscribe(setUser), []) | ||
|
||
const { data: itemsData, error: itemsError } = useSWR( | ||
account && isValidFlowAddress(account) ? ["listingsFetcher", account] : null, listingsFetcher | ||
) | ||
|
||
useEffect(() => { | ||
if (itemsData) { | ||
setListings(itemsData) | ||
const groupedListings = groupListings(itemsData.validItems) | ||
setListingGroups(groupedListings) | ||
} | ||
}, [itemsData]) | ||
|
||
if (!account) { | ||
return <div className="h-screen"></div> | ||
} | ||
|
||
if (!isValidFlowAddress(account)) { | ||
return <Custom404 title={"Account may not exist"} /> | ||
} | ||
|
||
const showItems = () => { | ||
if (!listingGroups) { | ||
return ( | ||
<div className="flex mt-10 h-[200px] justify-center"> | ||
<Spinner /> | ||
</div> | ||
) | ||
} | ||
|
||
return ( | ||
<> | ||
{listingGroups.length > 0 ? | ||
listingGroups.map((listings, index) => { | ||
return ( | ||
<ListingGroup key={`listing-groups-${index}`} listings={listings} /> | ||
) | ||
}) : | ||
<div className="flex mt-10 h-[70px] text-gray-400 text-base justify-center"> | ||
Nothing found | ||
</div> | ||
} | ||
</> | ||
) | ||
} | ||
|
||
return ( | ||
<div className="container mx-auto max-w-7xl min-w-[380px] px-2"> | ||
<Layout> | ||
<div className="flex w-full flex-col gap-y-3 overflow-auto"> | ||
<div className="flex w-full flex-col gap-y-3 overflow-auto"> | ||
|
||
<div className="p-x flex gap-x-5 justify-between w-full min-w-[1076px]"> | ||
<div className="p-2 flex flex-col gap-y-2 justify-between w-full"> | ||
<h1 className="text-xl sm:text-2xl font-bold text-gray-900"> | ||
{`Listings (${listingGroups ? listingGroups.flat().length : 0})`} | ||
</h1> | ||
{ | ||
listings && listings.invalidItems.length > 0 ? | ||
<label className="text-sm text-red-400"> | ||
{`There are ${listings.invalidItems.length} invalid listings, which are either ghosted, purchased, or expired.`} | ||
</label> : null | ||
} | ||
</div> | ||
<div className="p-x flex gap-x-2 justify-end w-full"> | ||
<button | ||
className={`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 () => { | ||
const ghostedIds = listings.invalidItems.filter(item => item.isGhosted && !item.isPurchased).map(item => item.listingResourceId) | ||
await cleanupGhosted(account, ghostedIds, setTransactionInProgress, setTransactionStatus) | ||
mutate(["listingsFetcher", account]) | ||
}} | ||
> | ||
Cleanup Ghosted | ||
</button> | ||
<button | ||
className={`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 () => { | ||
const purchasedIds = listings.invalidItems.filter(item => item.isPurchased).map(item => item.listingResourceId) | ||
await cleanupPurchased(account, purchasedIds, setTransactionInProgress, setTransactionStatus) | ||
mutate(["listingsFetcher", account]) | ||
}} | ||
> | ||
Cleanup Purchased | ||
</button> | ||
<button | ||
className={`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 () => { | ||
const itemCount = listings.validItems.length + listings.invalidItems.length - 1 | ||
await cleanupExpired(account, "0", `${itemCount}`, setTransactionInProgress, setTransactionStatus) | ||
mutate(["listingsFetcher", account]) | ||
}} | ||
> | ||
Cleanup Expired | ||
</button> | ||
</div> | ||
</div> | ||
|
||
<div className="px-2 py-2 overflow-x-auto h-screen w-full"> | ||
<div className="inline-block min-w-full"> | ||
<div className="flex flex-col gap-y-4"> | ||
{showItems()} | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
</Layout> | ||
</div> | ||
) | ||
} |
Oops, something went wrong.