Build a Flow Collectibles Portal Using Cadence (Part 2)
Tags: webdev, react, web3, tutorial
Published: 2024-02-26 14:48:21


![Article Image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w7xk078x2mfqrgvtm86k.png)

Welcome to the final step in creating your Collectibles portal! (for part 1, see [here](https://dev.to/johnjvester/build-a-digital-collectibles-portal-using-flow-and-cadence-part-1-57af))

In this part, we'll focus on building the frontend—the last 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. View the NFT with the NFT ID you have in your Collection.
    

We will be using [Next.js](https://nextjs.org/) to build the frontend. 

Let's get started!

## **1\. Installation**

### **Setting Up**

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

This will provide you with several options. In this tutorial, we will not use Typescript, ESLint, TailwindCSS, and we will use the `src` directory and the App router at the time of this article.

Now you have a fresh web app ready. This is how your frontend folder looks:

![Image 1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1ryhi3rxqb29k513h055.png)

## **2\. Configuration**

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

To get started, let's install FCL for our app by running the following command:

```javascript
npm install @onflow/fcl --save
```

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

1. Inside the `app` folder create a new folder named `flow` and add a file named `config.js`.
    
2. In this file, set up the configuration for the FCL, such as specifying the Access Node and wallet discovery endpoint. This helps you to choose between using a testnet or a local emulator. 
    
3. You will also want to specify the Collectibles contract address we deployed in Part 1.
    

Add the following code to the `config.js` file:

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

Now you're all set up to use the FCL in your app.

## **3\. Authentication**

To verify a user's identity in an app, you can use several functions: 

1. For logging in, call `fcl.logIn()`. 
    
2. For signing up, call `fcl.signUp()`.
    
3. For logging out, call `fcl.unauthenticate()`. 
    

Let’s learn how we can implement these `fcl` functions in your frontend.

First, we will add the following code to our `page.js` file inside the app directory. This will import some dependencies, set up some initial `useState` for parts of our app, and build a basic UI. 

To make sure it looks nice, delete the `page.module.css` file inside the app directory and instead make a file called page.css. Then paste the contents of [this file](https://github.com/DappCoderr/Flow-NFT/blob/master/frontend/src/app/page.css) inside of it. Now we can write out our initial page.

```javascript
"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 `npm run dev` to make sure everything loads correctly.

## **4\. Querying the Flow blockchain**

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

```javascript
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 in the Cadence constants into the `fcl` queries.

```javascript
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 take a look at all the functions we’ve written. There are two things to notice:

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

Since scripts are similar to `view` functions in Solidity and don't require any gas fees to run, we are essentially just querying the blockchain. So we use `fcl.query` to run scripts on Flow.

In order to query something, we need to pass an argument. For that, we use arg, which is a function that takes a string value representing the argument, and `t`, which is an object that contains all the different data types that Cadence has. So we can tell `arg` how to encode and decode the argument we are passing.

## **5\. Mutating the Flow blockchain**

While our previous functions were just “read only,” our next ones will have actions that can mutate the blockchain state and write to it; aka “mint an NFT.”

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

```javascript
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 below function after the transaction code to the `page.js` file.

```javascript
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 the function, the `fcl.mutate` syntax is the same as `fcl.query`. However, we do provide several extra parameters such as the following:

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

* These are Flow-specific things that define which account will be paying for the transaction (payer), broadcasting the transaction (proposer), and the accounts from which we need authorizations. (In case an account has multiple keys attached, it can behave like a multi-sig wallet.)
    
* `fcl.authz` refers to the currently connected account. 
    
* `limit` is like gasLimit in the Ethereum world, which places an upper limit on the maximum amount of computation. If the computation crosses the limit, then the transaction will fail.
    

We’ll need to add one more function that will call and handle the `mintNFT` function we just made.

```javascript
  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 code** 

With our main functions in place, we can now plug them into our UI. 

However, before we do that, we’ll add some `useEffect` calls to help load the initial state. You can add these right above the already existing `useEffect` call.

```javascript
  useEffect(() => {
    checkCollectionInit();
    viewNFT();
  }, [currentUser]);

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

Now back in our `return` section with the UI, we can add our functions to the appropriate parts of the app.

```javascript
  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.](https://github.com/DappCoderr/Flow-NFT/blob/master/frontend/src/app/page.js)

[N](https://github.com/DappCoderr/Flow-NFT/blob/master/frontend/src/app/page.js)ow with the app complete[, le](https://github.com/DappCoderr/Flow-NFT/blob/master/frontend/src/app/page.js)t’s walk through how to use it! 

First, connect your walle[t by](https://github.com/DappCoderr/Flow-NFT/blob/master/frontend/src/app/page.js) clicking the “Connect Wallet” button in the top right.

![Image 5](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/korog091ikdgzfi2dqnh.png)

Now you can mint an NFT! Enter the name of your NFT and paste in a link to the image you want to use. After you click “mint” it will prompt you to sign a transaction with your wallet.

![Image 3](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wcgkimns6z7263bev30u.png)

It might take a little while for the transaction to complete. After it completes, you should be able to click the bottom button to view the IDs of your NFTs. If this is your first one, then the ID should be just “1”.

![Image 4](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/28hhf5eovez4cu4j8txm.png)

Now you can copy the ID of your NFT, paste it into the View section, and click “View NFT”.

![Image 2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2l3van92diw2af1aogeg.png)

## **Conclusion**

Well done! You've finished part 2 of the Collectibles portal project. In summary, we focused on building the frontend of our Collectibles portal. We did this by:

* Creating an app with Next.js 
    
* Connecting the Flow Wallet
    
* Creating our very own NFTs for minting
    
* Viewing your NFT
    

Have a really great day!