欢迎来到创建收藏品门户的最后一步! (第 1 部分,请参见此处)
在这一部分中,我们将专注于构建前端——拼图的最后一块。
这是我们将实现的目标:
我们将使用Next.js构建前端。
让我们开始吧!
打开您的项目flow-collectible-portal
目录。然后,运行
在终端中npx create-next-app@latest frontend
并按enter
。
这将为您提供多种选择。在本教程中,我们不会使用Typescript 、 ESLint 或TailwindCSS ,并且在本文中我们将使用src
目录和 App 路由器。
现在您已经准备好了一个新的网络应用程序。
这是您的前端文件夹的外观:
为了与Flow 区块链交互,我们将使用 Flow 客户端库 (FCL) 来管理钱包连接、运行脚本并在应用程序中发送交易。它将允许我们编写完整的 Cadence 函数并将它们作为 Javascript 函数运行。
首先,让我们通过运行以下命令为我们的应用程序安装 FCL:
npm install @onflow/fcl --save
安装FCL后,我们需要对其进行配置。您需要执行以下操作:
app
文件夹内创建一个名为flow
的新文件夹并添加一个名为config.js
的文件。
将以下代码添加到config.js
文件中:
import { config } from "@onflow/fcl"; config({ "app.detail.title": "Flow Name Service", "app.detail.icon": "https://placekitten.com/g/200/200", "accessNode.api": "https://rest-testnet.onflow.org", "discovery.wallet": "https://fcl-discovery.onflow.org/testnet/authn", "0xCollectibles": "ADD YOUR CONTRACT ACCOUNT ADDRESS", "0xNonFungibleToken": "0x631e88ae7f1d7c20", });
现在您已准备好在应用程序中使用 FCL。
要在应用程序中验证用户身份,您可以使用以下几个函数:
fcl.logIn()
。fcl.signUp()
。fcl.unauthenticate()
。
让我们了解如何在前端实现这些fcl
函数。
首先,我们将以下代码添加到 app 目录内的page.js
文件中。这将导入一些依赖项,为我们的应用程序的某些部分设置一些初始useState
,并构建一个基本的 UI。
为了确保它看起来不错,请删除应用程序目录中的page.module.css
文件,并创建一个名为 page.css 的文件。然后将该文件的内容粘贴到其中。现在我们可以写出我们的初始页面。
"use client"; import React, { useState, useEffect, useRef } from "react"; import * as fcl from "@onflow/fcl"; import "./page.css"; import "./flow/config"; export default function Page() { const [currentUser, setCurrentUser] = useState({ loggedIn: false, addr: undefined, }); const urlInputRef = useRef(); const nameInputRef = useRef(); const idInputRef = useRef(); const [isInitialized, setIsInitialized] = useState(); const [collectiblesList, setCollectiblesList] = useState([]); const [loading, setLoading] = useState(false); const [ids, setIds] = useState([]); const [nft, setNFT] = useState({}); useEffect(() => fcl.currentUser.subscribe(setCurrentUser), []); function handleInputChange(event) { const inputValue = event.target.value; if (/^\d+$/.test(inputValue)) { idInputRef.current = +inputValue; } else { console.error("Invalid input. Please enter a valid integer."); } } return ( <div> <div className="navbar"> <h1>Flow Collectibles Portal</h1> <span>Address: {currentUser?.addr ?? "NO Address"}</span> <button onClick={currentUser.addr ? fcl.unauthenticate : fcl.logIn}> {currentUser.addr ? "Log Out" : "Connect Wallet"} </button> </div> {currentUser.loggedIn ? ( <div className="main"> <div className="mutate"> <h1>Mutate Flow Blockchain</h1> <form onSubmit={(event) => { event.preventDefault(); }} > <input type="text" placeholder="enter name of the NFT" ref={nameInputRef} /> <input type="text" placeholder="enter a url" ref={urlInputRef} /> <button type="submit">Mint</button> </form> <mark>Your Collection will be initialized while minting NFT.</mark> </div> <div className="query"> <h1>Query Flow Blockchain</h1> <mark>Click below button to check 👇</mark> <button>Check Collection</button> <p> Is your collection initialized: {isInitialized ? "Yes" : "No"} </p> <button onClick={viewIds}> View NFT IDs you hold in your collection </button> <p>NFT Id: </p> </div> <div className="view"> <h1>View Your NFT</h1> <input type="text" placeholder="enter your NFT ID" onChange={handleInputChange} /> <button>View NFT</button> <div className="nft-card"> <p>NFT id: </p> <p>NFT name: </p> <img src="" alt="" /> </div> </div> </div> ) : ( <div className="main-2"> <h1>Connect Wallet to mint NFT!!</h1> </div> )} </div> ); }
添加此代码后,运行npm run dev
以确保所有内容正确加载。
在深入研究如何使用fcl
查询 Flow 区块链之前,请在page.js
文件中的handleInput
函数后面添加这些 Cadence 脚本代码。
const CHECK_COLLECTION = ` import NonFungibleToken from 0xNonFungibleToken import Collectibles from 0xCollectibles pub fun main(address: Address): Bool? { return Collectibles.checkCollection(_addr: address) }` const GET_NFT_ID = ` import NonFungibleToken from 0xNonFungibleToken import Collectibles from 0xCollectibles pub fun main(user: Address): [UInt64] { let collectionCap = getAccount(user).capabilities.get <&{Collectibles.CollectionPublic}>(/public/NFTCollection) ?? panic("This public capability does not exist.") let collectionRef = collectionCap.borrow()! return collectionRef.getIDs() } ` const GET_NFT = ` import NonFungibleToken from 0xNonFungibleToken import Collectibles from 0xCollectibles pub fun main(user: Address, id: UInt64): &NonFungibleToken.NFT? { let collectionCap= getAccount(user).capabilities.get<&{Collectibles.CollectionPublic}>(/public/NFTCollection) ?? panic("This public capability does not exist.") let collectionRef = collectionCap.borrow()! return collectionRef.borrowNFT(id: id) }
随着我们的 Cadence 脚本准备就绪,我们现在可以声明一些 Javascript 函数并将 Cadence 常量传递到`fcl`
查询中。
async function checkCollectionInit() { const isInit = await fcl.query({ cadence: CHECK_COLLECTION, args: (arg,t) => [arg(currentUser?.addr, t.Address)], }); console.log(isInit); } async function viewNFT() { console.log(idInputRef.current); const nfts = await fcl.query({ cadence: GET_NFT, args: (arg,t) => [arg(currentUser?.addr,t.Address), arg(idInputRef.current, t.UInt64)] }); setNFT(nfts); console.log(nfts); } async function viewIds() { const ids = await fcl.query({ cadence: GET_NFT_ID, args: (arg,t) => [arg(currentUser?.addr,t.Address)] }); setIds(ids); console.log(ids); }
现在让我们看一下我们编写的所有函数。有两点需要注意:
fcl.query
args: (arg,t) => [arg(addr,t.Address)],
行。
由于脚本类似于 Solidity 中的view
函数,并且不需要任何 Gas 费用来运行,因此我们本质上只是查询区块链。因此我们使用fcl.query
在 Flow 上运行脚本。
要查询某些内容,我们需要传递一个参数。为此,我们使用 arg(一个采用表示参数的字符串值的函数)和t
(一个包含 Cadence 拥有的所有不同数据类型的对象)。因此我们可以告诉arg
如何对我们传递的参数进行编码和解码。
虽然我们之前的功能只是“只读”,但我们的下一个功能将具有可以改变区块链状态并写入它的操作;又名“铸造 NFT”。
为此,我们将编写另一个 Cadence 脚本作为常量。
const MINT_NFT = ` import NonFungibleToken from 0xNonFungibleToken import Collectibles from 0xCollectibles transaction(name:String, image:String){ let receiverCollectionRef: &{NonFungibleToken.CollectionPublic} prepare(signer:AuthAccount){ // initialise account if signer.borrow<&Collectibles.Collection>(from: Collectibles.CollectionStoragePath) == nil { let collection <- Collectibles.createEmptyCollection() signer.save(<-collection, to: Collectibles.CollectionStoragePath) let cap = signer.capabilities.storage.issue<&{Collectibles.CollectionPublic}>(Collectibles.CollectionStoragePath) signer.capabilities.publish( cap, at: Collectibles.CollectionPublicPath) } //takes the receiver collection refrence self.receiverCollectionRef = signer.borrow<&Collectibles.Collection>(from: Collectibles.CollectionStoragePath) ?? panic("could not borrow Collection reference") } execute{ let nft <- Collectibles.mintNFT(name:name, image:image) self.receiverCollectionRef.deposit(token: <-nft) } }
现在将以下函数添加到page.js
文件中的事务代码之后。
async function mint() { try{ const txnId = await fcl.mutate({ cadence: MINT_NFT, args: (arg,t) => [arg(name,t.String), arg(image, t.String)], payer: fcl.authz, proposer: fcl.authz, authorizations: [fcl.authz], limit:999,}); } catch(error){ console.error('Minting failed:' error) } console.log(txnId); }
至于函数, fcl.mutate
语法与fcl.query
相同。但是,我们确实提供了几个额外的参数,如下所示:
payer: fcl.authz, proposer: fcl.authz, authorizations: [fcl.authz], limit: 50,
fcl.authz
指当前连接的帐户。limit
就像以太坊世界中的gasLimit,对最大计算量设置了上限。如果计算超过限制,则交易将失败。
我们需要再添加一个函数来调用和处理我们刚刚创建的mintNFT
函数。
const saveCollectible = async () => { if (urlInputRef.current.value.length > 0 && nameInputRef.current.value.length > 0) { try { setLoading(true); const transaction = await mintNFT(nameInputRef.current.value, urlInputRef.current.value); console.log('transactionID:', transaction); // Handle minting success (if needed) } catch (error) { console.error('Minting failed:', error); // Handle minting failure (if needed) } finally { setLoading(false); } } else { console.log('Empty input. Try again.'); } };
主要功能就位后,我们现在可以将它们插入到我们的 UI 中。
但是,在此之前,我们将添加一些useEffect
调用来帮助加载初始状态。您可以将它们添加到现有useEffect
调用的正上方。
useEffect(() => { checkCollectionInit(); viewNFT(); }, [currentUser]); useEffect(() => { if (currentUser.loggedIn) { setCollectiblesList(collectiblesList); console.log('Setting collectibles...'); } }, [currentUser]);
现在回到 UI 的return
部分,我们可以将函数添加到应用程序的适当部分。
return ( <div> <div className="navbar"> <h1>Flow Collectibles Portal</h1> <span>Address: {currentUser?.addr ?? "NO Address"}</span> <button onClick={currentUser.addr ? fcl.unauthenticate : fcl.logIn}> {currentUser.addr ? "Log Out" : "Connect Wallet"} </button> </div> {currentUser.loggedIn ? ( <div className="main"> <div className="mutate"> <h1>Mutate Flow Blockchain</h1> <form onSubmit={(event) => { event.preventDefault(); saveCollectible(); }} > <input type="text" placeholder="enter name of the NFT" ref={nameInputRef} /> <input type="text" placeholder="enter a url" ref={urlInputRef} /> <button type="submit">Mint</button> </form> <mark>Your Collection will be initialized while minting NFT.</mark> </div> <div className="query"> <h1>Query Flow Blockchain</h1> <mark>Click below button to check 👇</mark> <button onClick={checkCollectionInit}>Check Collection</button> <p> Is your collection initialized: {isInitialized ? "Yes" : "No"} </p> <button onClick={viewIds}> View NFT IDs you hold in your collection </button> <p>NFT Id: </p> {ids.map((id) => ( <p key={id}>{id}</p> ))} </div> <div className="view"> <h1>View Your NFT</h1> <input type="text" placeholder="enter your NFT ID" onChange={handleInputChange} /> <button onClick={viewNFT}>View NFT</button> <div className="nft-card"> <p>NFT id: {nft.id}</p> <p>NFT name: {nft.name}</p> <img src={nft.image} alt={nft.name} /> </div> </div> </div> ) : ( <div className="main-2"> <h1>Connect Wallet to mint NFT!!</h1> </div> )} </div> );
在此处查看最终代码。
现在应用程序已完成,让我们逐步了解如何使用它!
首先,点击右上角的“连接钱包”按钮连接您的钱包。
现在你可以铸造 NFT 了!输入您的 NFT 名称并粘贴您要使用的图像的链接。点击“铸币”后,它会提示您用钱包签署交易。
交易可能需要一段时间才能完成。完成后,您应该能够单击底部按钮来查看您的 NFT 的 ID。如果这是您的第一个,那么 ID 应该只是“1”。
现在您可以复制 NFT 的 ID,将其粘贴到“查看”部分,然后单击“查看 NFT”。
做得好!您已完成收藏品门户项目的第 2 部分。总之,我们专注于构建收藏品门户的前端。
我们通过以下方式做到了这一点:
祝你有美好的一天!
也发布在这里。