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:
- Connect the Flow wallet.
- Initialize your account and mint your NFT.
- Check the NFT ID in your collection.
- 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 runnpx 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:
- Within
app
folder create a new folder namedflow
and add a file namedconfig.js
. - 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.
- 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:
- To register, please call
fcl.logIn()
. - For applications, call
fcl.signUp()
. - 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:
- The
fcl.query
- 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.
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.
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”.
You can now copy your NFT ID, paste it into the View section and click “View 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!