Добро пожаловать на последний шаг в создании портала коллекционирования! (часть 1 смотрите здесь )
В этой части мы сосредоточимся на создании внешнего интерфейса — последней части головоломки.
Вот чего мы достигнем:
Мы будем использовать Next.js для создания интерфейса.
Давайте начнем!
Откройте каталог flow-collectible-portal
вашего проекта. Затем запустите
npx create-next-app@latest frontend
в терминале и нажмите enter
.
Это предоставит вам несколько вариантов. В этом руководстве мы не будем использовать Typescript , ESLint или TailwindCSS , а будем использовать каталог src
и маршрутизатор приложений на момент написания этой статьи.
Теперь у вас готово свежее веб-приложение.
Вот как выглядит ваша папка внешнего интерфейса:
Для взаимодействия с блокчейном 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
в вашем интерфейсе.
Сначала мы добавим следующий код в наш файл page.js
внутри каталога приложения. Это позволит импортировать некоторые зависимости, настроить начальное состояние useState
для частей нашего приложения и создать базовый пользовательский интерфейс.
Чтобы убедиться, что все выглядит хорошо, удалите файл 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, добавьте эти коды сценариев Cadence после функции handleInput
в файле page.js
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)],
line.
Поскольку сценарии аналогичны функциям view
в Solidity и не требуют каких-либо комиссий за запуск, мы, по сути, просто опрашиваем блокчейн. Поэтому мы используем 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 в мире Ethereum, который устанавливает верхний предел максимального объема вычислений. Если вычисление превысит предел, транзакция завершится неудачей.
Нам нужно добавить еще одну функцию, которая будет вызывать и обрабатывать только что созданную функцию 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.'); } };
Теперь, когда наши основные функции готовы, мы можем подключить их к нашему пользовательскому интерфейсу.
Однако прежде чем мы это сделаем, мы добавим несколько вызовов useEffect
, которые помогут загрузить начальное состояние. Вы можете добавить их прямо над уже существующим вызовом useEffect
.
useEffect(() => { checkCollectionInit(); viewNFT(); }, [currentUser]); useEffect(() => { if (currentUser.loggedIn) { setCollectiblesList(collectiblesList); console.log('Setting collectibles...'); } }, [currentUser]);
Вернувшись в раздел 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 и вставьте ссылку на изображение, которое вы хотите использовать. После того, как вы нажмете «mint», вам будет предложено подписать транзакцию с вашим кошельком.
Завершение транзакции может занять некоторое время. После завершения вы сможете нажать нижнюю кнопку, чтобы просмотреть идентификаторы ваших NFT. Если это ваш первый, то идентификатор должен быть просто «1».
Теперь вы можете скопировать идентификатор вашего NFT, вставить его в раздел «Просмотр» и нажать «Просмотреть NFT».
Отличная работа! Вы завершили вторую часть проекта портала «Коллекционирование». Подводя итог, мы сосредоточились на создании интерфейса нашего портала коллекционирования.
Мы сделали это:
Хорошего дня!
Также опубликовано здесь.