Bienvenue dans la dernière étape de la création de votre portail d'objets de collection ! (pour la première partie, voir ici )
Dans cette partie, nous nous concentrerons sur la construction du frontend, la dernière pièce du puzzle.
Voici ce que nous allons réaliser :
Nous utiliserons Next.js pour créer le frontend.
Commençons!
Ouvrez le répertoire flow-collectible-portal
de votre projet. Ensuite, courez
npx create-next-app@latest frontend
dans le terminal et appuyez sur enter
.
Cela vous offrira plusieurs options. Dans ce tutoriel, nous n'utiliserons pas Typescript , ESLint ou TailwindCSS , et nous utiliserons le répertoire src
et le routeur App au moment de cet article.
Vous avez maintenant une nouvelle application Web prête.
Voici à quoi ressemble votre dossier frontend :
Pour interagir avec la blockchain Flow, nous utiliserons la bibliothèque client Flow (FCL) pour gérer les connexions du portefeuille, exécuter des scripts et envoyer des transactions dans notre application. Cela nous permettra d'écrire des fonctions Cadence complètes et de les exécuter en tant que fonctions Javascript.
Pour commencer, installons FCL pour notre application en exécutant la commande suivante :
npm install @onflow/fcl --save
Après avoir installé FCL, nous devons le configurer. Voici ce que vous devez faire :
app
, créez un nouveau dossier nommé flow
et ajoutez un fichier nommé config.js
.
Ajoutez le code suivant au fichier 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", });
Vous êtes maintenant prêt à utiliser le FCL dans votre application.
Pour vérifier l'identité d'un utilisateur dans une application, vous pouvez utiliser plusieurs fonctions :
fcl.logIn()
.fcl.signUp()
.fcl.unauthenticate()
.
Apprenons comment nous pouvons implémenter ces fonctions fcl
dans votre frontend.
Tout d’abord, nous ajouterons le code suivant à notre fichier page.js
dans le répertoire de l’application. Cela importera certaines dépendances, configurera un useState
initial pour certaines parties de notre application et créera une interface utilisateur de base.
Pour vous assurer qu'il est joli, supprimez le fichier page.module.css
dans le répertoire de l'application et créez à la place un fichier appelé page.css. Collez ensuite le contenu de ce fichier à l'intérieur. Nous pouvons maintenant rédiger notre première page.
"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> ); }
Après avoir ajouté ce code, exécutez npm run dev
pour vous assurer que tout se charge correctement.
Avant d'approfondir la façon dont nous pouvons utiliser fcl
pour interroger la blockchain Flow, ajoutez ces codes de script Cadence après la fonction handleInput
dans le fichier 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) }
Avec nos scripts Cadence prêts à l'emploi, nous pouvons maintenant déclarer certaines fonctions Javascript et transmettre les constantes Cadence dans les requêtes `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); }
Jetons maintenant un œil à toutes les fonctions que nous avons écrites. Il y a deux choses à remarquer :
fcl.query
args: (arg,t) => [arg(addr,t.Address)],
ligne.
Étant donné que les scripts sont similaires aux fonctions view
dans Solidity et ne nécessitent aucun frais de gaz pour s'exécuter, nous interrogeons essentiellement simplement la blockchain. Nous utilisons donc fcl.query
pour exécuter des scripts sur Flow.
Pour interroger quelque chose, nous devons passer un argument. Pour cela, nous utilisons arg, qui est une fonction qui prend une valeur de chaîne représentant l'argument, et t
, qui est un objet qui contient tous les différents types de données de Cadence. Nous pouvons donc dire à arg
comment encoder et décoder l’argument que nous transmettons.
Alors que nos fonctions précédentes étaient simplement « en lecture seule », nos prochaines auront des actions qui peuvent muter l'état de la blockchain et y écrire ; alias « créer un NFT ».
Pour ce faire, nous allons écrire un autre script Cadence comme 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) } }
Ajoutez maintenant la fonction ci-dessous après le code de transaction au fichier 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); }
Quant à la fonction, la syntaxe fcl.mutate
est la même que fcl.query
. Cependant, nous fournissons plusieurs paramètres supplémentaires tels que les suivants :
payer: fcl.authz, proposer: fcl.authz, authorizations: [fcl.authz], limit: 50,
fcl.authz
fait référence au compte actuellement connecté.limit
est comme gasLimit dans le monde Ethereum, qui impose une limite supérieure à la quantité maximale de calcul. Si le calcul dépasse la limite, la transaction échouera.
Nous devrons ajouter une fonction supplémentaire qui appellera et gérera la fonction mintNFT
que nous venons de créer.
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.'); } };
Une fois nos principales fonctions en place, nous pouvons désormais les intégrer à notre interface utilisateur.
Cependant, avant de faire cela, nous ajouterons quelques appels useEffect
pour aider à charger l'état initial. Vous pouvez les ajouter juste au-dessus de l'appel useEffect
déjà existant.
useEffect(() => { checkCollectionInit(); viewNFT(); }, [currentUser]); useEffect(() => { if (currentUser.loggedIn) { setCollectiblesList(collectiblesList); console.log('Setting collectibles...'); } }, [currentUser]);
De retour dans notre section return
avec l'interface utilisateur, nous pouvons ajouter nos fonctions aux parties appropriées de l'application.
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> );
Vérifiez le code final ici .
Maintenant que l'application est terminée, voyons comment l'utiliser !
Tout d’abord, connectez votre portefeuille en cliquant sur le bouton « Connecter le portefeuille » en haut à droite.
Vous pouvez désormais créer un NFT ! Entrez le nom de votre NFT et collez un lien vers l'image que vous souhaitez utiliser. Après avoir cliqué sur « menthe », il vous sera demandé de signer une transaction avec votre portefeuille.
La finalisation de la transaction peut prendre un peu de temps. Une fois l'opération terminée, vous devriez pouvoir cliquer sur le bouton du bas pour afficher les identifiants de vos NFT. S'il s'agit de votre premier, l'ID ne doit être que « 1 ».
Vous pouvez maintenant copier l'ID de votre NFT, le coller dans la section Affichage et cliquer sur « Afficher NFT ».
Bien joué! Vous avez terminé la partie 2 du projet de portail Objets de collection. En résumé, nous nous sommes concentrés sur la création de l’interface de notre portail Objets de collection.
Nous l'avons fait en :
Passez une très bonne journée !
Également publié ici.