¡Bienvenido al paso final en la creación de su portal de Coleccionables! (para la parte 1, ver aquí )
En esta parte, nos centraremos en construir la interfaz, la última pieza del rompecabezas.
Esto es lo que lograremos:
Usaremos Next.js para construir la interfaz.
¡Empecemos!
Abra el directorio flow-collectible-portal
de su proyecto. Entonces corre
npx create-next-app@latest frontend
en la terminal y presione enter
.
Esto le proporcionará varias opciones. En este tutorial, no usaremos Typecript , ESLint o TailwindCSS , y usaremos el directorio src
y el enrutador de la aplicación al momento de escribir este artículo.
Ahora tienes lista una nueva aplicación web.
Así es como se ve tu carpeta frontend:
Para interactuar con la cadena de bloques Flow, usaremos la biblioteca Flow Client (FCL) para administrar conexiones de billetera, ejecutar scripts y enviar transacciones en nuestra aplicación. Nos permitirá escribir funciones de Cadencia completas y ejecutarlas como funciones de Javascript.
Para comenzar, instalemos FCL para nuestra aplicación ejecutando el siguiente comando:
npm install @onflow/fcl --save
Después de instalar FCL, debemos configurarlo. Esto es lo que debes hacer:
app
, cree una nueva carpeta llamada flow
y agregue un archivo llamado config.js
.
Agregue el siguiente código al archivo 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", });
Ahora ya está todo configurado para usar FCL en su aplicación.
Para verificar la identidad de un usuario en una aplicación, puede utilizar varias funciones:
fcl.logIn()
.fcl.signUp()
.fcl.unauthenticate()
.
Aprendamos cómo podemos implementar estas funciones fcl
en su interfaz.
Primero, agregaremos el siguiente código a nuestro archivo page.js
dentro del directorio de la aplicación. Esto importará algunas dependencias, configurará un useState
inicial para partes de nuestra aplicación y creará una interfaz de usuario básica.
Para asegurarse de que se vea bien, elimine el archivo page.module.css
dentro del directorio de la aplicación y, en su lugar, cree un archivo llamado page.css. Luego pegue el contenido de este archivo dentro de él. Ahora podemos escribir nuestra 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> ); }
Después de agregar este código, ejecute npm run dev
para asegurarse de que todo se cargue correctamente.
Antes de profundizar en cómo podemos usar fcl
para consultar la cadena de bloques Flow, agregue estos códigos de script de Cadence después de la función handleInput
en el archivo 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) }
Con nuestros scripts de Cadence listos para funcionar, ahora podemos declarar algunas funciones de Javascript y pasar las constantes de Cadence a las 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); }
Ahora echemos un vistazo a todas las funciones que hemos escrito. Hay dos cosas a tener en cuenta:
fcl.query
args: (arg,t) => [arg(addr,t.Address)],
línea.
Dado que los scripts son similares a las funciones view
en Solidity y no requieren ninguna tarifa de gas para ejecutarse, básicamente solo estamos consultando la cadena de bloques. Entonces usamos fcl.query
para ejecutar scripts en Flow.
Para consultar algo, necesitamos pasar un argumento. Para eso, usamos arg, que es una función que toma un valor de cadena que representa el argumento, y t
, que es un objeto que contiene todos los diferentes tipos de datos que tiene Cadence. Entonces podemos decirle a arg
cómo codificar y decodificar el argumento que estamos pasando.
Si bien nuestras funciones anteriores eran solo de “solo lectura”, las siguientes tendrán acciones que pueden mutar el estado de la cadena de bloques y escribir en ella; también conocido como "acuñar un NFT".
Para hacer esto, escribiremos otro script de 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) } }
Ahora agregue la siguiente función después del código de transacción al archivo 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); }
En cuanto a la función, la sintaxis fcl.mutate
es la misma que la fcl.query
. Sin embargo, proporcionamos varios parámetros adicionales como los siguientes:
payer: fcl.authz, proposer: fcl.authz, authorizations: [fcl.authz], limit: 50,
fcl.authz
se refiere a la cuenta actualmente conectada.limit
es como gasLimit en el mundo Ethereum, que impone un límite superior a la cantidad máxima de cálculo. Si el cálculo cruza el límite, la transacción fallará.
Necesitaremos agregar una función más que llame y maneje la función mintNFT
que acabamos de crear.
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.'); } };
Con nuestras funciones principales implementadas, ahora podemos conectarlas a nuestra interfaz de usuario.
Sin embargo, antes de hacer eso, agregaremos algunas llamadas useEffect
para ayudar a cargar el estado inicial. Puede agregarlos justo encima de la llamada useEffect
ya existente.
useEffect(() => { checkCollectionInit(); viewNFT(); }, [currentUser]); useEffect(() => { if (currentUser.loggedIn) { setCollectiblesList(collectiblesList); console.log('Setting collectibles...'); } }, [currentUser]);
Ahora, de vuelta en nuestra sección return
con la interfaz de usuario, podemos agregar nuestras funciones a las partes apropiadas de la aplicación.
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> );
Consulta el código final aquí .
Ahora que la aplicación está completa, ¡veamos cómo usarla!
Primero, conecte su billetera haciendo clic en el botón "Conectar billetera" en la parte superior derecha.
¡Ahora puedes crear un NFT! Ingrese el nombre de su NFT y pegue un enlace a la imagen que desea usar. Después de hacer clic en "mint", se le pedirá que firme una transacción con su billetera.
Es posible que la transacción tarde un poco en completarse. Una vez que se complete, debería poder hacer clic en el botón inferior para ver las ID de sus NFT. Si este es el primero, entonces el ID debe ser solo "1".
Ahora puede copiar la ID de su NFT, pegarla en la sección Ver y hacer clic en "Ver NFT".
¡Bien hecho! Has terminado la parte 2 del proyecto del portal Coleccionables. En resumen, nos centramos en construir la interfaz de nuestro portal de Coleccionables.
Hicimos esto por:
¡Que tengas un gran día!
También publicado aquí.