Bem-vindo à etapa final na criação do seu portal de colecionáveis! (para a parte 1, veja aqui )
Nesta parte, vamos nos concentrar na construção do frontend – a última peça do quebra-cabeça.
Aqui está o que alcançaremos:
Estaremos usando Next.js para construir o frontend.
Vamos começar!
Abra o diretório flow-collectible-portal
do seu projeto. Então corra
npx create-next-app@latest frontend
no terminal e pressione enter
.
Isso fornecerá várias opções. Neste tutorial, não usaremos Typescript , ESLint ou TailwindCSS e usaremos o diretório src
e o roteador do aplicativo no momento deste artigo.
Agora você tem um novo aplicativo da web pronto.
Esta é a aparência da sua pasta frontend:
Para interagir com o blockchain Flow, usaremos a Flow Client Library (FCL) para gerenciar conexões de carteira, executar scripts e enviar transações em nosso aplicativo. Isso nos permitirá escrever funções Cadence completas e executá-las como funções Javascript.
Para começar, vamos instalar o FCL em nosso aplicativo executando o seguinte comando:
npm install @onflow/fcl --save
Após instalar o FCL, precisamos configurá-lo. Aqui está o que você precisa fazer:
app
crie uma nova pasta chamada flow
e adicione um arquivo chamado config.js
.
Adicione o seguinte código ao arquivo 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", });
Agora você está pronto para usar o FCL em seu aplicativo.
Para verificar a identidade de um usuário em um aplicativo, você pode usar diversas funções:
fcl.logIn()
.fcl.signUp()
.fcl.unauthenticate()
.
Vamos aprender como podemos implementar essas funções fcl
em seu frontend.
Primeiro, adicionaremos o seguinte código ao nosso arquivo page.js
dentro do diretório app. Isso importará algumas dependências, configurará algum useState
inicial para partes de nosso aplicativo e construirá uma UI básica.
Para ter certeza de que está bonito, exclua o arquivo page.module.css
dentro do diretório do aplicativo e, em vez disso, crie um arquivo chamado page.css. Em seguida, cole o conteúdo deste arquivo dentro dele. Agora podemos escrever nossa página inicial.
"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> ); }
Depois de adicionar este código, execute npm run dev
para garantir que tudo carregue corretamente.
Antes de nos aprofundarmos em como podemos usar fcl
para consultar o blockchain Flow, adicione esses códigos de script Cadence após a função handleInput
no arquivo 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) }
Com nossos scripts Cadence prontos, agora podemos declarar algumas funções Javascript e passar as constantes Cadence para as consultas `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); }
Agora vamos dar uma olhada em todas as funções que escrevemos. Há duas coisas a serem observadas:
fcl.query
args: (arg,t) => [arg(addr,t.Address)],
linha.
Como os scripts são semelhantes às funções de view
no Solidity e não exigem nenhuma taxa de gás para serem executados, estamos essencialmente apenas consultando o blockchain. Portanto, usamos fcl.query
para executar scripts no Flow.
Para consultar algo, precisamos passar um argumento. Para isso, usamos arg, que é uma função que recebe um valor de string que representa o argumento, e t
, que é um objeto que contém todos os diferentes tipos de dados que o Cadence possui. Assim, podemos dizer arg
como codificar e decodificar o argumento que estamos passando.
Embora nossas funções anteriores fossem apenas “somente leitura”, nossas próximas terão ações que podem alterar o estado do blockchain e gravar nele; também conhecido como “cunhar um NFT”.
Para fazer isso, escreveremos outro script Cadence como constante.
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) } }
Agora adicione a função abaixo após o código de transação ao arquivo 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); }
Quanto à função, a sintaxe fcl.mutate
é igual a fcl.query
. No entanto, fornecemos vários parâmetros extras, como os seguintes:
payer: fcl.authz, proposer: fcl.authz, authorizations: [fcl.authz], limit: 50,
fcl.authz
refere-se à conta atualmente conectada.limit
é como gasLimit no mundo Ethereum, que coloca um limite superior na quantidade máxima de computação. Se o cálculo ultrapassar o limite, a transação falhará.
Precisaremos adicionar mais uma função que irá chamar e manipular a função mintNFT
que acabamos de criar.
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.'); } };
Com nossas funções principais implementadas, agora podemos conectá-las à nossa IU.
No entanto, antes de fazermos isso, adicionaremos algumas chamadas useEffect
para ajudar a carregar o estado inicial. Você pode adicioná-los logo acima da chamada useEffect
já existente.
useEffect(() => { checkCollectionInit(); viewNFT(); }, [currentUser]); useEffect(() => { if (currentUser.loggedIn) { setCollectiblesList(collectiblesList); console.log('Setting collectibles...'); } }, [currentUser]);
Agora, de volta à nossa seção return
com a UI, podemos adicionar nossas funções às partes apropriadas do aplicativo.
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> );
Verifique o código final aqui .
Agora com o aplicativo completo, vamos ver como usá-lo!
Primeiro, conecte sua carteira clicando no botão “Conectar carteira” no canto superior direito.
Agora você pode cunhar um NFT! Digite o nome do seu NFT e cole um link para a imagem que deseja usar. Depois de clicar em “mint”, você será solicitado a assinar uma transação com sua carteira.
Pode demorar um pouco para a transação ser concluída. Após a conclusão, você poderá clicar no botão inferior para visualizar os IDs de seus NFTs. Se este for o seu primeiro, o ID deverá ser apenas “1”.
Agora você pode copiar o ID do seu NFT, colá-lo na seção Visualizar e clicar em “Visualizar NFT”.
Bom trabalho! Você concluiu a parte 2 do projeto do portal Colecionáveis. Em resumo, nos concentramos na construção do frontend do nosso portal de colecionáveis.
Fizemos isso por:
Tenha um ótimo dia!
Também publicado aqui.