Build a Flow Collectibles Portal Using Cadence (Part 2)

Welcome to the final step in creating a collecting portal! (for part 1 see here).

In this part, we’ll focus on building the front end—the final piece of the puzzle. Here’s what we’ll achieve:

  1. Connect the Flow wallet.
  2. Initialize your account and mint your NFT.
  3. Check the NFT ID in your collection.
  4. Browse NFTs with the NFT ID you have in your collection.

We will use Next.js to create the front end.

Let’s start!

1. Installation

Setting

Open your project flow-collectible-portal directory. Then run
npx create-next-app@latest frontend in the terminal and press enter .

This will give you several options. In this tutorial we won’t be using Typescript, ESLint or TailwindCSS, we’ll be using src directory and application router at the time of writing this article.

Now you have a new web application ready. This is what your frontend map looks like:

2. Configuration

To interact with the Flow blockchain, we will use the Flow Client Library (FCL) to manage wallet connections, run scripts and send transactions in our application. This will allow us to write complete Cadence functions and run them as Javascript functions.

To begin, let’s install FCL for our application by running the following command:

npm install @onflow/fcl --save

After installing FCL, we need to configure it. Here’s what you need to do:

  1. Within app folder create a new folder named flow and add a file named config.js.
  2. In this file, set the configuration for the FCL, such as specifying the access node and the wallet discovery endpoint. This helps you choose between using the testnet or the local emulator.
  3. You’ll also want to provide the collection agreement address we set up in Part 1.

Add the following code to the config.js file:

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",
);

You are now ready to use FCL in your application.

3. Authentication

You can use several functions to verify the user’s identity in the application:

  1. To register, please call fcl.logIn().
  2. For applications, call fcl.signUp().
  3. To check out, please call fcl.unauthenticate().

Let’s learn how we can implement them fcl it works in your front end.

First, we’ll add the following code to our page.js file inside the application directory. This will introduce some dependencies, set some initial ones useState for parts of our application and build a basic user interface.

To make sure it looks nice, delete it page.module.css file inside the application directory and create a file called page.css instead. Then paste the contents of this file inside it. Now we can print our home 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>
  );

After adding this code, run it npm run dev to make sure everything loads correctly.

4. Query Flow Blockchain

Before we dive deep into how we can use fcl to query the Flow blockchain, add these Cadence script codes after handleInput function in page.js file.

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)

With our Cadence scripts ready to go, we can now declare some JavaScript functions and pass Cadence constants to the `fcl` inquiries.

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);

Now, let’s look at all the functions we wrote. Two things should be noted:

  1. The fcl.query
  2. and args: (arg,t) => [arg(addr,t.Address)], line.

Since the scripts are similar view it works in Solidity and doesn’t require any fuel fees to run, we’re essentially just testing the blockchain. So we use it fcl.query to run scripts on Flow.

To query something, we need to pass an argument. For this we use arg, which is a function that takes a string value representing argument i t, which is an object that contains all the different types of data that Cadence has. So we can say arg how to encode and decode the argument we pass.

5. Mutation of Flow Blockchain

While our previous functions were only “read only”, our next ones will have actions that can mutate the state of the blockchain and write to it, i.e. “mint NFTs”.

To do this, we’ll write another Cadence script as a constant.

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)
    

Now add the function below after the transaction code in the page.js file.

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);

As for function, fcl.mutate the syntax is the same as fcl.query. However, we offer several additional parameters, such as the following:

payer: fcl.authz,
proposer: fcl.authz,
authorizations: [fcl.authz],
limit: 50,

  • These are Flow-specific things that define which account will pay for the transaction (the payer), broadcast the transaction (the proposer), and the accounts we need authorizations from. (In case the account has multiple keys attached, it can act as a multi-sig wallet.)
  • fcl.authz refers to the currently linked account.
  • limit is like gasLimit in the Ethereum world, which sets an upper limit on the maximum amount of calculations. If the calculation exceeds the limit, then the transaction will fail.

We will need to add another function to call and handle mintNFT the function we just made.

  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.');
    
  ;

6. Final Codex

With the main functions set, we can now include them in our user interface.

Before we do that, however, we’ll add some useEffect calls for help loading the initial state. You can add them right above the already existing ones useEffect call.

  useEffect(() => 
    checkCollectionInit();
    viewNFT();
  , [currentUser]);

  useEffect(() => 
    if (currentUser.loggedIn) 
      setCollectiblesList(collectiblesList);
      console.log('Setting collectibles...');
    
  , [currentUser]);

Now, back to ours return section with the user interface, we can add our functions to the appropriate parts of the 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>
  );

Check the final code here.

Now that the app is done, let’s walk through how to use it!

First, link your wallet by clicking the “Link Wallet” button in the top right corner.

connect your wallet Now you can mint NFT! Enter the name of your NFT and paste the link to the image you want to use. After clicking “mint”, you will be asked to sign the transaction with your wallet.

flow collection portal

It may take some time to complete the transaction. Once it’s done, you should be able to click the button below to view your NFT IDs. If this is your first, the ID should only be “1”.

see NFT

You can now copy your NFT ID, paste it into the View section and click “View NFT”.

your NFT

Conclusion

Well done! You have completed the second part of the Collectibles portal project. In short, we focused on building the front end of our collectibles portal. We did this as follows:

  • Creating an application with Next.js
  • Linking the Flow wallet
  • Creating your own NFTs to mint
  • Viewing your NFT

Have a nice day!

Source link

Leave a Reply

Your email address will not be published. Required fields are marked *