diff --git a/components/bookmark/AccountBookmark.js b/components/bookmark/AccountBookmark.js index b633fcd..be9dd56 100644 --- a/components/bookmark/AccountBookmark.js +++ b/components/bookmark/AccountBookmark.js @@ -6,12 +6,16 @@ import { transactionStatusState, showNoteEditorState, accountBookmarkState, - transactionInProgressState + transactionInProgressState, + showBasicNotificationState, + basicNotificationContentState } from "../../lib/atoms" import { removeAccountBookmark } from "../../flow/bookmark_transactions"; -import { PencilAltIcon } from "@heroicons/react/outline"; +import { DocumentDuplicateIcon, PencilAltIcon } from "@heroicons/react/outline"; export default function AccountBookmark(props) { + const [, setShowBasicNotification] = useRecoilState(showBasicNotificationState) + const [, setBasicNotificationContent] = useRecoilState(basicNotificationContentState) const [transactionInProgress, setTransactionInProgress] = useRecoilState(transactionInProgressState) const [, setTransactionStatus] = useRecoilState(transactionStatusState) const [showNoteEditor, setShowNoteEditor] = useRecoilState(showNoteEditorState) @@ -36,14 +40,23 @@ export default function AccountBookmark(props) { }, undefined, { shallow: true }) }} >{bookmark.address} - { - if (transactionInProgress) { - return - } - await removeAccountBookmark(bookmark.address, setTransactionInProgress, setTransactionStatus) - mutate(["accountBookmarksFetcher", user.addr]) - }}/> +
+ { + await navigator.clipboard.writeText(bookmark.address) + setShowBasicNotification(true) + setBasicNotificationContent({ type: "information", title: "Copied!", detail: null }) + }} /> + { + if (transactionInProgress) { + return + } + await removeAccountBookmark(bookmark.address, setTransactionInProgress, setTransactionStatus) + mutate(["accountBookmarksFetcher", user.addr]) + }} /> +
+
{bookmark.note}
diff --git a/components/collection/NftBulkTransferModal.js b/components/collection/NftBulkTransferModal.js index 78beb3a..30a2272 100644 --- a/components/collection/NftBulkTransferModal.js +++ b/components/collection/NftBulkTransferModal.js @@ -60,7 +60,7 @@ export default function NftBulkTransferModal(props) {
{showNftBulkTransfer.mode == "NftBulkTransfer" ? - "NFT Bulk Transfer" : "Unknown"} + "NFT Bulk Transfer" : "Set Recipient"}
@@ -118,8 +118,16 @@ export default function NftBulkTransferModal(props) { storagePath, publicPath, setTransactionInProgress, setTransactionStatus ) + router.reload() + } else if (showNftBulkTransfer.mode == "SetRecipient") { + let tokens = Object.assign({}, selectedTokens) + for (const [tokenId, properties] of Object.entries(tokens)) { + if (properties.isSelected && !properties.recipient) { + properties.recipient = recipient + } + } + setSelectedTokens(tokens) } - router.reload() }} > {"Confirm"} diff --git a/components/collection/NftBulkTransferPreviewModal.js b/components/collection/NftBulkTransferPreviewModal.js new file mode 100644 index 0000000..02dd8bd --- /dev/null +++ b/components/collection/NftBulkTransferPreviewModal.js @@ -0,0 +1,176 @@ +import { Fragment, useEffect, useRef, useState } from 'react' +import { Dialog, Transition } from '@headlessui/react' +import { useRecoilState } from "recoil" +import { transactionStatusState, transactionInProgressState, showNftBulkTransferPreviewState } from '../../lib/atoms' +import * as fcl from "@onflow/fcl"; +import Image from "next/image" +import { getRarityColor, isValidFlowAddress, isValidPositiveFlowDecimals, isValidPositiveNumber, isValidUrl } from '../../lib/utils' +import { useRouter } from 'next/router' +import { useSWRConfig } from 'swr' +import { acceptOwnership, redeemAccount } from '../../flow/hc_transactions'; +import { bulkTransferNft } from '../../flow/nft_transactions'; + +export default function NftBulkTransferPreviewModal(props) { + const router = useRouter() + const { mutate } = useSWRConfig() + const { account: account, collection: collectionPath } = router.query + const { selectedTokens, setSelectedTokens, collection } = props + const [transactionInProgress, setTransactionInProgress] = useRecoilState(transactionInProgressState) + const [, setTransactionStatus] = useRecoilState(transactionStatusState) + + const [showNftBulkTransferPreview, setShowNftBulkTransferPreview] = useRecoilState(showNftBulkTransferPreviewState) + + useEffect(() => fcl.currentUser.subscribe(setUser), []) + const [user, setUser] = useState({ loggedIn: null }) + + const cancelButtonRef = useRef(null) + + const getPreviewList = () => { + const tokenIds = Object.entries(selectedTokens).filter(([tokenId, properties]) => properties.isSelected && properties.recipient).map(([tokenId, selected]) => tokenId) + const previewList = tokenIds.map((tokenId) => { + const token = selectedTokens[tokenId] + return { + tokenId: tokenId, + recipient: token.recipient, + display: token.display + } + }).sort((a, b) => a.recipient.localeCompare(b.recipient)) + return previewList + } + + const getPreviewView = () => { + const previewList = getPreviewList() + return ( +
+ { + previewList.map((token, index) => { + const display = token.display + const rarityColor = getRarityColor(display.rarity ? display.rarity.toLowerCase() : null) + return ( +
+
+ {`#${index + 1}`} +
+
+
+ + { + display.rarity ? +
+ {`${display.rarity}`.toUpperCase()} +
: null + } +
+ +
+
+ { + display.transferrable == true ? null : +
+ {"S"} +
+ } + +
+ +
+
+
+ {token.recipient} +
+
+ ) + }) + } +
+ ) + } + + return ( + + setShowNftBulkTransferPreview(prev => ({ + ...prev, show: false + }))}> + +
+ + +
+
+ + +
+
+ + NFT Bulk Transfer + +
+ {getPreviewView()} +
+
+
+
+ + +
+
+
+
+
+
+
+ ) +} diff --git a/components/common/Layout.js b/components/common/Layout.js index 9f5f252..d1af879 100644 --- a/components/common/Layout.js +++ b/components/common/Layout.js @@ -101,7 +101,7 @@ export default function Layout({ children }) { return null } if (!bookmark) { - return { if (transactionInProgress) { return @@ -122,7 +122,7 @@ export default function Layout({ children }) { /> } - return { if (transactionInProgress) { return @@ -149,7 +149,7 @@ export default function Layout({ children }) {
- { await navigator.clipboard.writeText(account) setShowBasicNotification(true) diff --git a/components/common/NFTView.js b/components/common/NFTView.js index 269e03e..c7ed9f1 100644 --- a/components/common/NFTView.js +++ b/components/common/NFTView.js @@ -12,7 +12,6 @@ export default function NFTView(props) { const router = useRouter() const rarityColor = getRarityColor(display.rarity ? display.rarity.toLowerCase() : null) const maxSelection = 20 - console.log(display) return (
{`${display.name}`} +
{ display.transferrable == true ? null : @@ -72,6 +72,14 @@ export default function NFTView(props) { {`#${display.tokenID}`}
+ { + selectMode == "Select" && selectedTokens[tokenId] && selectedTokens[tokenId].isSelected && selectedTokens[tokenId].recipient ? +
+ {selectedTokens[tokenId].recipient} +
+ : null + } +
) } \ No newline at end of file diff --git a/flow/nft_transactions.js b/flow/nft_transactions.js index a105aaf..35f065b 100644 --- a/flow/nft_transactions.js +++ b/flow/nft_transactions.js @@ -38,22 +38,18 @@ const doTransferNft = async (address, tokenId, collectionStoragePath, collection } export const bulkTransferNft = async ( - address, tokenIds, collectionStoragePath, collectionPublicPath, + recipients, tokenIds, collectionStoragePath, collectionPublicPath, setTransactionInProgress, setTransactionStatus ) => { - if (!isValidFlowAddress(address)) { - return - } - const txFunc = async () => { - return await doBulkTransferNft(address, tokenIds, collectionStoragePath, collectionPublicPath) + return await doBulkTransferNft(recipients, tokenIds, collectionStoragePath, collectionPublicPath) } return await txHandler(txFunc, setTransactionInProgress, setTransactionStatus) } -const doBulkTransferNft = async (address, tokenIds, collectionStoragePath, collectionPublicPath) => { +const doBulkTransferNft = async (recipients, tokenIds, collectionStoragePath, collectionPublicPath) => { const rawCode = await (await fetch("/transactions/collection/bulk_transfer_nft.cdc")).text() const code = rawCode.replace("__NFT_STORAGE_PATH__", collectionStoragePath) .replace("__NFT_PUBLIC_PATH__", collectionPublicPath) @@ -61,7 +57,7 @@ const doBulkTransferNft = async (address, tokenIds, collectionStoragePath, colle const transactionId = fcl.mutate({ cadence: code, args: (arg, t) => [ - arg(address, t.Address), + arg(recipients, t.Array(t.Address)), arg(tokenIds, t.Array(t.UInt64)) ], proposer: fcl.currentUser, diff --git a/flow/scripts.js b/flow/scripts.js index de6a882..88f4c7b 100644 --- a/flow/scripts.js +++ b/flow/scripts.js @@ -145,7 +145,6 @@ export const getNftViews = async (address, storagePathID, tokenIDs) => { ] }) - console.log(displays) return displays } diff --git a/lib/atoms.js b/lib/atoms.js index eeb29e7..29a7ac9 100644 --- a/lib/atoms.js +++ b/lib/atoms.js @@ -67,7 +67,12 @@ export const showRedeemAccountState = atom({ export const showNftBulkTransferState = atom({ key: "showNftBulkTransferState", - default: {show: false, mode: "NftBulkTransfer"} + default: {show: false, mode: "SetTarget"} +}) + +export const showNftBulkTransferPreviewState = atom({ + key: "showNftBulkTransferPreviewState", + default: {show: false, mode: "NftBulkTransferPreview"} }) export const showTransferOwnershipState = atom({ diff --git a/pages/account/[account]/collection/[collection]/index.js b/pages/account/[account]/collection/[collection]/index.js index 5b684d6..e529c10 100644 --- a/pages/account/[account]/collection/[collection]/index.js +++ b/pages/account/[account]/collection/[collection]/index.js @@ -7,12 +7,13 @@ import Layout from "../../../../../components/common/Layout" import NFTListView from "../../../../../components/common/NFTListView" import Spinner from "../../../../../components/common/Spinner" import { bulkGetNftCatalog, getStoredItems } from "../../../../../flow/scripts" -import { basicNotificationContentState, nftCatalogState, showBasicNotificationState, showNftBulkTransferState, transactionInProgressState } from "../../../../../lib/atoms" +import { basicNotificationContentState, nftCatalogState, showBasicNotificationState, showNftBulkTransferPreviewState, showNftBulkTransferState, transactionInProgressState } from "../../../../../lib/atoms" import { classNames, collectionsWithCatalogInfo, collectionsWithDisplayInfo, collectionsWithExtraData, getContractLink, getImageSrcFromMetadataViewsFile, isValidFlowAddress } from "../../../../../lib/utils" import publicConfig from "../../../../../publicConfig" import Custom404 from "../../404" import NftBulkTransferModal from "../../../../../components/collection/NftBulkTransferModal" import * as fcl from "@onflow/fcl" +import NftBulkTransferPreviewModal from "../../../../../components/collection/NftBulkTransferPreviewModal" export default function CollectionDetail(props) { const router = useRouter() @@ -21,6 +22,7 @@ export default function CollectionDetail(props) { const [, setShowBasicNotification] = useRecoilState(showBasicNotificationState) const [, setBasicNotificationContent] = useRecoilState(basicNotificationContentState) const [, setShowNftBulkTransfer] = useRecoilState(showNftBulkTransferState) + const [, setShowNftBulkTransferPreview] = useRecoilState(showNftBulkTransferPreviewState) const [nftCatalog, setNftCatalog] = useRecoilState(nftCatalogState) const [collection, setCollection] = useState(null) const [collectionData, setCollectionData] = useState(null) @@ -259,6 +261,10 @@ export default function CollectionDetail(props) { !collection.publicPathIdentifier || !collection.storagePathIdentifier } + const haveUnsetRecipient = () => { + return Object.values(selectedTokens).filter((t) => t.isSelected && !t.recipient).length > 0 + } + return (
@@ -304,6 +310,25 @@ export default function CollectionDetail(props) { > {selectMode == "Detail" ? "Select" : "Cancel"} + { + isCurrentUser() && selectMode == "Select" ? + + : null + } { isCurrentUser() && selectMode == "Select" ? - : null} + : null + }
) @@ -354,6 +387,7 @@ export default function CollectionDetail(props) {
+
) } \ No newline at end of file diff --git a/public/transactions/collection/bulk_transfer_nft.cdc b/public/transactions/collection/bulk_transfer_nft.cdc index 89dde02..779af52 100644 --- a/public/transactions/collection/bulk_transfer_nft.cdc +++ b/public/transactions/collection/bulk_transfer_nft.cdc @@ -1,24 +1,26 @@ import NonFungibleToken from 0xNonFungibleToken -transaction(address: Address, tokenIds: [UInt64]) { +transaction(recipients: [Address], tokenIds: [UInt64]) { let senderCollection: &NonFungibleToken.Collection - let receiverCollection: Capability<&{NonFungibleToken.CollectionPublic}> prepare(account: AuthAccount) { + assert(recipients.length == tokenIds.length, message: "invalid input") self.senderCollection = account.borrow<&NonFungibleToken.Collection>(from: __NFT_STORAGE_PATH__)! - - let receiverAccount = getAccount(address) - self.receiverCollection = receiverAccount.getCapability<&{NonFungibleToken.CollectionPublic}>(__NFT_PUBLIC_PATH__) } execute { - for tokenId in tokenIds { + for index, tokenId in tokenIds { + let recipient = recipients[index]! + let recipientAccount = getAccount(recipient) + let recipientCollection = recipientAccount.getCapability<&{NonFungibleToken.CollectionPublic}>(__NFT_PUBLIC_PATH__).borrow() + ?? panic("Could not borrow capability from recipient") + let nft <- self.senderCollection.withdraw(withdrawID: tokenId) if(nft == nil){ panic("NFT not found!") } - self.receiverCollection.borrow()!.deposit(token: <-nft) + recipientCollection.deposit(token: <-nft) } } } \ No newline at end of file