# CELO FD 101
Todo
- Change to typescript
- Adopt style from https://github.com/dacadeorg/celo-development-201/blob/main/content/celo201_03_build_an_nft_minter_with_hardhat_and_react.md
- Go through notes in this document
- Dont show whole code just explain what it does and link to the code on github (make it public).
- Make it testable in between
- Only show code that is relavant for the celo blockchain integration.
Outline
1. Project Setup
1.1 How to install
1.2 Explaianition of Celo composer structure and current project.
1.3 ...
2. UI Elements (Mockdata) (Dont show the code, just link to the files where it is in and add comments inside the files in the tutorial only write a paragraph of what the componen is doing)
2.1. Product list
2.2. Product (identicon)
2.3. Add products
2.4. Add prodcut modal
3.3. Alerts
3. Contract + Logic (Hooks) (Show code).
4. Combinging everything
## Prerequisites
* Node
* Git (v2.38 or higher)
3.0 Smart Contract Overview
We will be making use of this smart contract to interact with the Celo blockchain.
```solidity
// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0 <0.9.0;
interface IERC20Token {
function transfer(address, uint256) external returns (bool);
function approve(address, uint256) external returns (bool);
function transferFrom(address, address, uint256) external returns (bool);
function totalSupply() external view returns (uint256);
function balanceOf(address) external view returns (uint256);
function allowance(address, address) external view returns (uint256);
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
contract Marketplace {
uint internal productsLength = 0;
address internal cUsdTokenAddress = 0x874069Fa1Eb16D44d622F2e0Ca25eeA172369bC1;
struct Product {
address payable owner;
string name;
string image;
string description;
string location;
uint price;
uint sold;
}
mapping (uint => Product) internal products;
function writeProduct(
string memory _name,
string memory _image,
string memory _description,
string memory _location,
uint _price
) public {
uint _sold = 0;
products[productsLength] = Product(
payable(msg.sender),
_name,
_image,
_description,
_location,
_price,
_sold
);
productsLength++;
}
function readProduct(uint _index) public view returns (
address payable,
string memory,
string memory,
string memory,
string memory,
uint,
uint
) {
return (
products[_index].owner,
products[_index].name,
products[_index].image,
products[_index].description,
products[_index].location,
products[_index].price,
products[_index].sold
);
}
function buyProduct(uint _index) public payable {
require(
IERC20Token(cUsdTokenAddress).transferFrom(
msg.sender,
products[_index].owner,
products[_index].price
),
"Transfer failed."
);
products[_index].sold++;
}
function getProductsLength() public view returns (uint) {
return (productsLength);
}
}
```
This is a marketplace smart contract which enables users to list their products for sale and buy products from other users. The smart contract is written in Solidity, a programming language for writing smart contracts. The smart contract is deployed on the Celo blockchain and is accessible to anyone who wants to use it.
3.1 Initializing your project (3 min)
First, you are going to initialize your project from our boilerplate repo, where you will already have the build process and necessary libraries available. You should have installed node.js 10 or higher.
Open your command-line interface.
We will be creating our boiler-plate code using Celo Composer(https://github.com/celo-org/celo-composer#how-to-use-celo-composer).
This is a CLI tool enables you to quickly start building dApps on Celo for multiple frameworks including React, React Native (w/o Expo), Flutter and Angular. You can create the dApp using default Composer templates provided by Celo. To get started.
In your terminal, paste in the following code
```
npx @celo/celo-composer create
```
This will prompt you to select the framework and the template you want to use

Fig.1.0
Select the option that says "React" as shown above
Next you will be prompted to select the web3 library for react app. For the sake of this tutorial we will be using Rainbowkkit-celo:

Fig.1.1
Next you will be prompted to select the smart contract framework. For the sake of this tutorial we will be using Hardhat:

Fig.1.1
Next we are asked to create a subgraph. Simply select No:

Fig.1.2
Next let us select a project name. We would name this project Celo-101. You can use whatever name you desire.

Fig.1.3
This will then generate the required boilerplate we need to develop our DApp on the Celo blockchain.
3.2 Boilerplate Structure
The boilerplate generated by the Celo composer is structured in the following way:
- Celo-101
- Packages
- hardhat
- react-app
The hardhat folder contains hardhat generated code which enables us write our smart contracts, test them and deploy them to the Celo testnet or mainnet and provide us the deployment address.
The react-app contains React(NextJS) code which we will use to build the frontend that interacts with our smart contract.
We will dive deeper into each of these sections
### 3.3 Installing NPM Packages
Now our codebase has been created, let us go ahead and remove uneccessary code that comes with the boilerplate which we would not be using in the creation of this DApp.
First let us navigate into our newly created project directory and install the npm packages for our boilerplate code.
Navigate into your project folder and then run the command
```
npm install
```
or
```
yarn install
```
This will install all the required npm packages used for the hardhat and react frontend implementation.
Now we have all our packages installed and we can begin writing our smart contracts
3.4 Implementing Our Frontend
At this point, our celo composer boilerplate has been setup and generated with all the initial configuration we would need for our Marketplace decentralized application.
Go into your terminal, navigate to your project root folder and run the command
```
npm run react-app:dev
```
or
```
yarn react-app:dev
```
Give it a second to run your server and if you set things up correctly, open http://localhost:3000/ on your browser and you should see :

fig 1.4
Great job on setting up your boilerplate🔥
### 3.4 Implementing Frontend Design
For our frontend implementation, we would be using tailwind css https://tailwindcss.com/ which is a utility first css framework to make our design process easier to implement.
#### 3.4.1 Implement toast notifications
In your code editor, navigate into the file _app.tsx. You should see:

fig 1.5
This is a part of the boilerplate codebase generated by celo composer. In this we are using a couple of tools and packages to implement this martetplace dapp:
* Wagmi(https://wagmi.sh/): a collection of React Hooks containing everything you need to start working with Celo. wagmi makes it easy to "Connect Wallet," display ENS and balance information, sign messages, interact with contracts, and much more.
* RainbowKit(https://www.rainbowkit.com/): a fast, easy and highly customizable way for developers to add a great wallet experience to their application. This will aid with users being able to connect their web3 wallets to our marketplace.
This code in our _app.js is boilerplate generated and you do not need to go deep into its implementation in this course.
Next, let us install a library called "react-toastify" which we would use to display beautiful notification messages to our users.
Navigate into the path *packages/react-app* in your terminal and then run the command:
```
npm install react-toastify
```
or
```
yarn add react-toastify
```
We then import the plugin in our _app.js file so that our toast notifications will appear across the entire application
```
import { ToastContainer } from 'react-toastify';
```
We will then import the css file for the toast notifications at the top of our _app.js file
```
import 'react-toastify/dist/ReactToastify.css';
import "../styles/globals.css";
...
```
Next, we will render it just above our layout and set our toasts to always show at the bottom center of our screen.
```
...
return (
<WagmiConfig client={wagmiClient}>
<RainbowKitProvider chains={chains} coolMode={true}>
<ToastContainer position={'bottom-center'} />
<Layout>
<Component {...pageProps} />
```
At the end of that, our _app.js should look like:
```typescript
import 'react-toastify/dist/ReactToastify.css';
import "../styles/globals.css";
import "@rainbow-me/rainbowkit/styles.css";
import type { AppProps } from "next/app";
import {
connectorsForWallets,
RainbowKitProvider
} from "@rainbow-me/rainbowkit";
import {
metaMaskWallet,
omniWallet,
walletConnectWallet
} from "@rainbow-me/rainbowkit/wallets";
import { configureChains, createClient, WagmiConfig } from "wagmi";
import { jsonRpcProvider } from "wagmi/providers/jsonRpc";
// Import known recommended wallets
import { Valora, CeloWallet, CeloDance } from "@celo/rainbowkit-celo/wallets";
// Import CELO chain information
import { Alfajores, Celo } from "@celo/rainbowkit-celo/chains";
import Layout from "../components/Layout";
import { ToastContainer } from 'react-toastify';
const { chains, provider } = configureChains(
[Alfajores, Celo],
// @ts-ignore
[jsonRpcProvider({ rpc: (chain) => ({ http: chain.rpcUrls.default }) })]
);
const connectors = connectorsForWallets([
{
groupName: "Recommended with CELO",
wallets: [
Valora({ chains }),
CeloWallet({ chains }),
CeloDance({ chains }),
metaMaskWallet({ chains }),
omniWallet({ chains }),
walletConnectWallet({ chains }),
],
},
]);
const wagmiClient = createClient({
autoConnect: true,
connectors,
provider,
});
function App({ Component, pageProps }: AppProps) {
return (
<WagmiConfig client={wagmiClient}>
<RainbowKitProvider chains={chains} coolMode={true}>
<ToastContainer position={'bottom-center'} />
<Layout>
<Component {...pageProps} />
</Layout>
</RainbowKitProvider>
</WagmiConfig>
)
}
export default App;
```
fig 1.7
#### 3.4.1 Implementing our Contract ABI
We will now implement our contract ABI into our frontend so that we can interact with our smart contract.
Navigate into the path *packages/react-app* and create a new folder called abi.
Inside the abi folder, create a new file called *Marketplace.json* and copy the ABI below contract file into it.
```json
{
"address": "0xdb9Ac5897dfE46e9F1183934F24BfCbe8e46aD57",
"abi": [
{
"inputs": [
{
"internalType": "uint256",
"name": "_index",
"type": "uint256"
}
],
"name": "buyProduct",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "getProductsLength",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_index",
"type": "uint256"
}
],
"name": "readProduct",
"outputs": [
{
"internalType": "address payable",
"name": "",
"type": "address"
},
{
"internalType": "string",
"name": "",
"type": "string"
},
{
"internalType": "string",
"name": "",
"type": "string"
},
{
"internalType": "string",
"name": "",
"type": "string"
},
{
"internalType": "string",
"name": "",
"type": "string"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "_name",
"type": "string"
},
{
"internalType": "string",
"name": "_image",
"type": "string"
},
{
"internalType": "string",
"name": "_description",
"type": "string"
},
{
"internalType": "string",
"name": "_location",
"type": "string"
},
{
"internalType": "uint256",
"name": "_price",
"type": "uint256"
}
],
"name": "writeProduct",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
],
"transactionHash": "0x5a9b51dcb17a8cbd66de7ec8a062fb5d65f4139c0751fef5ef2c21f9ceedb22b",
"receipt": {
"to": null,
"from": "0xBC3cB6681fEE5A95562DA936B1475A47c239F00F",
"contractAddress": "0xdb9Ac5897dfE46e9F1183934F24BfCbe8e46aD57",
"transactionIndex": 0,
"gasUsed": "946461",
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"blockHash": "0xe22e246509f45953ee0f40733313ecead1cfd5aaf71af21d76977057bc143287",
"transactionHash": "0x5a9b51dcb17a8cbd66de7ec8a062fb5d65f4139c0751fef5ef2c21f9ceedb22b",
"logs": [],
"blockNumber": 15929010,
"cumulativeGasUsed": "946461",
"status": 1,
"byzantium": true
},
"args": [],
"numDeployments": 1,
"solcInputHash": "c61272093650503e7cb6626bac486992",
"metadata": "{\"compiler\":{\"version\":\"0.8.17+commit.8df45f5f\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_index\",\"type\":\"uint256\"}],\"name\":\"buyProduct\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getProductsLength\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_index\",\"type\":\"uint256\"}],\"name\":\"readProduct\",\"outputs\":[{\"internalType\":\"address payable\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"_name\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"_image\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"_description\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"_location\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"_price\",\"type\":\"uint256\"}],\"name\":\"writeProduct\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/Marketplace.sol\":\"Marketplace\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":false,\"runs\":200},\"remappings\":[]},\"sources\":{\"contracts/Marketplace.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity >=0.7.0 <0.9.0;\\n\\ninterface IERC20Token {\\n function transfer(address, uint256) external returns (bool);\\n function approve(address, uint256) external returns (bool);\\n function transferFrom(address, address, uint256) external returns (bool);\\n function totalSupply() external view returns (uint256);\\n function balanceOf(address) external view returns (uint256);\\n function allowance(address, address) external view returns (uint256);\\n\\n event Transfer(address indexed from, address indexed to, uint256 value);\\n event Approval(address indexed owner, address indexed spender, uint256 value);\\n}\\n\\ncontract Marketplace {\\n\\n uint internal productsLength = 0;\\n address internal cUsdTokenAddress = 0x874069Fa1Eb16D44d622F2e0Ca25eeA172369bC1;\\n\\n struct Product {\\n address payable owner;\\n string name;\\n string image;\\n string description;\\n string location;\\n uint price;\\n uint sold;\\n }\\n\\n mapping (uint => Product) internal products;\\n\\n function writeProduct(\\n string memory _name,\\n string memory _image,\\n string memory _description,\\n string memory _location,\\n uint _price\\n ) public {\\n uint _sold = 0;\\n products[productsLength] = Product(\\n payable(msg.sender),\\n _name,\\n _image,\\n _description,\\n _location,\\n _price,\\n _sold\\n );\\n productsLength++;\\n }\\n\\n function readProduct(uint _index) public view returns (\\n address payable,\\n string memory,\\n string memory,\\n string memory,\\n string memory,\\n uint,\\n uint\\n ) {\\n return (\\n products[_index].owner,\\n products[_index].name,\\n products[_index].image,\\n products[_index].description,\\n products[_index].location,\\n products[_index].price,\\n products[_index].sold\\n );\\n }\\n\\n function buyProduct(uint _index) public payable {\\n require(\\n IERC20Token(cUsdTokenAddress).transferFrom(\\n msg.sender,\\n products[_index].owner,\\n products[_index].price\\n ),\\n \\\"Transfer failed.\\\"\\n );\\n products[_index].sold++;\\n }\\n\\n function getProductsLength() public view returns (uint) {\\n return (productsLength);\\n }\\n}\\n\",\"keccak256\":\"0x37dd4538c5a770aed4d2113768d16fa325118fcbfe0724308a08fbb112db109e\",\"license\":\"MIT\"}},\"version\":1}",
"bytecode": "0x60806040526000805573874069fa1eb16d44d622f2e0ca25eea172369bc1600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561006957600080fd5b50610fc7806100796000396000f3fe60806040526004361061003f5760003560e01c80631155dfe5146100445780638642269e1461006f5780638da8f27b1461008b578063fffa7e2e146100b4575b600080fd5b34801561005057600080fd5b506100596100f7565b60405161006691906106ba565b60405180910390f35b61008960048036038101906100849190610715565b610100565b005b34801561009757600080fd5b506100b260048036038101906100ad9190610888565b61025c565b005b3480156100c057600080fd5b506100db60048036038101906100d69190610715565b610390565b6040516100ee9796959493929190610a33565b60405180910390f35b60008054905090565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166323b872dd336002600085815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1660026000868152602001908152602001600020600501546040518463ffffffff1660e01b81526004016101ab93929190610b3e565b6020604051808303816000875af11580156101ca573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101ee9190610bad565b61022d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161022490610c26565b60405180910390fd5b60026000828152602001908152602001600020600601600081548092919061025490610c75565b919050555050565b60006040518060e001604052803373ffffffffffffffffffffffffffffffffffffffff16815260200187815260200186815260200185815260200184815260200183815260200182815250600260008054815260200190815260200160002060008201518160000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060208201518160010190816103179190610ebf565b50604082015181600201908161032d9190610ebf565b5060608201518160030190816103439190610ebf565b5060808201518160040190816103599190610ebf565b5060a0820151816005015560c0820151816006015590505060008081548092919061038390610c75565b9190505550505050505050565b60006060806060806000806002600089815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600260008a8152602001908152602001600020600101600260008b8152602001908152602001600020600201600260008c8152602001908152602001600020600301600260008d8152602001908152602001600020600401600260008e815260200190815260200160002060050154600260008f81526020019081526020016000206006015485805461046490610cec565b80601f016020809104026020016040519081016040528092919081815260200182805461049090610cec565b80156104dd5780601f106104b2576101008083540402835291602001916104dd565b820191906000526020600020905b8154815290600101906020018083116104c057829003601f168201915b505050505095508480546104f090610cec565b80601f016020809104026020016040519081016040528092919081815260200182805461051c90610cec565b80156105695780601f1061053e57610100808354040283529160200191610569565b820191906000526020600020905b81548152906001019060200180831161054c57829003601f168201915b5050505050945083805461057c90610cec565b80601f01602080910402602001604051908101604052809291908181526020018280546105a890610cec565b80156105f55780601f106105ca576101008083540402835291602001916105f5565b820191906000526020600020905b8154815290600101906020018083116105d857829003601f168201915b5050505050935082805461060890610cec565b80601f016020809104026020016040519081016040528092919081815260200182805461063490610cec565b80156106815780601f1061065657610100808354040283529160200191610681565b820191906000526020600020905b81548152906001019060200180831161066457829003601f168201915b505050505092509650965096509650965096509650919395979092949650565b6000819050919050565b6106b4816106a1565b82525050565b60006020820190506106cf60008301846106ab565b92915050565b6000604051905090565b600080fd5b600080fd5b6106f2816106a1565b81146106fd57600080fd5b50565b60008135905061070f816106e9565b92915050565b60006020828403121561072b5761072a6106df565b5b600061073984828501610700565b91505092915050565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6107958261074c565b810181811067ffffffffffffffff821117156107b4576107b361075d565b5b80604052505050565b60006107c76106d5565b90506107d3828261078c565b919050565b600067ffffffffffffffff8211156107f3576107f261075d565b5b6107fc8261074c565b9050602081019050919050565b82818337600083830152505050565b600061082b610826846107d8565b6107bd565b90508281526020810184848401111561084757610846610747565b5b610852848285610809565b509392505050565b600082601f83011261086f5761086e610742565b5b813561087f848260208601610818565b91505092915050565b600080600080600060a086880312156108a4576108a36106df565b5b600086013567ffffffffffffffff8111156108c2576108c16106e4565b5b6108ce8882890161085a565b955050602086013567ffffffffffffffff8111156108ef576108ee6106e4565b5b6108fb8882890161085a565b945050604086013567ffffffffffffffff81111561091c5761091b6106e4565b5b6109288882890161085a565b935050606086013567ffffffffffffffff811115610949576109486106e4565b5b6109558882890161085a565b925050608061096688828901610700565b9150509295509295909350565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061099e82610973565b9050919050565b6109ae81610993565b82525050565b600081519050919050565b600082825260208201905092915050565b60005b838110156109ee5780820151818401526020810190506109d3565b60008484015250505050565b6000610a05826109b4565b610a0f81856109bf565b9350610a1f8185602086016109d0565b610a288161074c565b840191505092915050565b600060e082019050610a48600083018a6109a5565b8181036020830152610a5a81896109fa565b90508181036040830152610a6e81886109fa565b90508181036060830152610a8281876109fa565b90508181036080830152610a9681866109fa565b9050610aa560a08301856106ab565b610ab260c08301846106ab565b98975050505050505050565b6000610ac982610973565b9050919050565b610ad981610abe565b82525050565b6000819050919050565b6000610b04610aff610afa84610973565b610adf565b610973565b9050919050565b6000610b1682610ae9565b9050919050565b6000610b2882610b0b565b9050919050565b610b3881610b1d565b82525050565b6000606082019050610b536000830186610ad0565b610b606020830185610b2f565b610b6d60408301846106ab565b949350505050565b60008115159050919050565b610b8a81610b75565b8114610b9557600080fd5b50565b600081519050610ba781610b81565b92915050565b600060208284031215610bc357610bc26106df565b5b6000610bd184828501610b98565b91505092915050565b7f5472616e73666572206661696c65642e00000000000000000000000000000000600082015250565b6000610c106010836109bf565b9150610c1b82610bda565b602082019050919050565b60006020820190508181036000830152610c3f81610c03565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000610c80826106a1565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203610cb257610cb1610c46565b5b600182019050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b60006002820490506001821680610d0457607f821691505b602082108103610d1757610d16610cbd565b5b50919050565b60008190508160005260206000209050919050565b60006020601f8301049050919050565b600082821b905092915050565b600060088302610d7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82610d42565b610d898683610d42565b95508019841693508086168417925050509392505050565b6000610dbc610db7610db2846106a1565b610adf565b6106a1565b9050919050565b6000819050919050565b610dd683610da1565b610dea610de282610dc3565b848454610d4f565b825550505050565b600090565b610dff610df2565b610e0a818484610dcd565b505050565b5b81811015610e2e57610e23600082610df7565b600181019050610e10565b5050565b601f821115610e7357610e4481610d1d565b610e4d84610d32565b81016020851015610e5c578190505b610e70610e6885610d32565b830182610e0f565b50505b505050565b600082821c905092915050565b6000610e9660001984600802610e78565b1980831691505092915050565b6000610eaf8383610e85565b9150826002028217905092915050565b610ec8826109b4565b67ffffffffffffffff811115610ee157610ee061075d565b5b610eeb8254610cec565b610ef6828285610e32565b600060209050601f831160018114610f295760008415610f17578287015190505b610f218582610ea3565b865550610f89565b601f198416610f3786610d1d565b60005b82811015610f5f57848901518255600182019150602085019450602081019050610f3a565b86831015610f7c5784890151610f78601f891682610e85565b8355505b6001600288020188555050505b50505050505056fea264697066735822122012de6446fc262186277d98c8d47bc3934c2ab3b79c4da30e4c2b903148b9da9064736f6c63430008110033",
"deployedBytecode": "0x60806040526004361061003f5760003560e01c80631155dfe5146100445780638642269e1461006f5780638da8f27b1461008b578063fffa7e2e146100b4575b600080fd5b34801561005057600080fd5b506100596100f7565b60405161006691906106ba565b60405180910390f35b61008960048036038101906100849190610715565b610100565b005b34801561009757600080fd5b506100b260048036038101906100ad9190610888565b61025c565b005b3480156100c057600080fd5b506100db60048036038101906100d69190610715565b610390565b6040516100ee9796959493929190610a33565b60405180910390f35b60008054905090565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166323b872dd336002600085815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1660026000868152602001908152602001600020600501546040518463ffffffff1660e01b81526004016101ab93929190610b3e565b6020604051808303816000875af11580156101ca573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101ee9190610bad565b61022d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161022490610c26565b60405180910390fd5b60026000828152602001908152602001600020600601600081548092919061025490610c75565b919050555050565b60006040518060e001604052803373ffffffffffffffffffffffffffffffffffffffff16815260200187815260200186815260200185815260200184815260200183815260200182815250600260008054815260200190815260200160002060008201518160000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060208201518160010190816103179190610ebf565b50604082015181600201908161032d9190610ebf565b5060608201518160030190816103439190610ebf565b5060808201518160040190816103599190610ebf565b5060a0820151816005015560c0820151816006015590505060008081548092919061038390610c75565b9190505550505050505050565b60006060806060806000806002600089815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600260008a8152602001908152602001600020600101600260008b8152602001908152602001600020600201600260008c8152602001908152602001600020600301600260008d8152602001908152602001600020600401600260008e815260200190815260200160002060050154600260008f81526020019081526020016000206006015485805461046490610cec565b80601f016020809104026020016040519081016040528092919081815260200182805461049090610cec565b80156104dd5780601f106104b2576101008083540402835291602001916104dd565b820191906000526020600020905b8154815290600101906020018083116104c057829003601f168201915b505050505095508480546104f090610cec565b80601f016020809104026020016040519081016040528092919081815260200182805461051c90610cec565b80156105695780601f1061053e57610100808354040283529160200191610569565b820191906000526020600020905b81548152906001019060200180831161054c57829003601f168201915b5050505050945083805461057c90610cec565b80601f01602080910402602001604051908101604052809291908181526020018280546105a890610cec565b80156105f55780601f106105ca576101008083540402835291602001916105f5565b820191906000526020600020905b8154815290600101906020018083116105d857829003601f168201915b5050505050935082805461060890610cec565b80601f016020809104026020016040519081016040528092919081815260200182805461063490610cec565b80156106815780601f1061065657610100808354040283529160200191610681565b820191906000526020600020905b81548152906001019060200180831161066457829003601f168201915b505050505092509650965096509650965096509650919395979092949650565b6000819050919050565b6106b4816106a1565b82525050565b60006020820190506106cf60008301846106ab565b92915050565b6000604051905090565b600080fd5b600080fd5b6106f2816106a1565b81146106fd57600080fd5b50565b60008135905061070f816106e9565b92915050565b60006020828403121561072b5761072a6106df565b5b600061073984828501610700565b91505092915050565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6107958261074c565b810181811067ffffffffffffffff821117156107b4576107b361075d565b5b80604052505050565b60006107c76106d5565b90506107d3828261078c565b919050565b600067ffffffffffffffff8211156107f3576107f261075d565b5b6107fc8261074c565b9050602081019050919050565b82818337600083830152505050565b600061082b610826846107d8565b6107bd565b90508281526020810184848401111561084757610846610747565b5b610852848285610809565b509392505050565b600082601f83011261086f5761086e610742565b5b813561087f848260208601610818565b91505092915050565b600080600080600060a086880312156108a4576108a36106df565b5b600086013567ffffffffffffffff8111156108c2576108c16106e4565b5b6108ce8882890161085a565b955050602086013567ffffffffffffffff8111156108ef576108ee6106e4565b5b6108fb8882890161085a565b945050604086013567ffffffffffffffff81111561091c5761091b6106e4565b5b6109288882890161085a565b935050606086013567ffffffffffffffff811115610949576109486106e4565b5b6109558882890161085a565b925050608061096688828901610700565b9150509295509295909350565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061099e82610973565b9050919050565b6109ae81610993565b82525050565b600081519050919050565b600082825260208201905092915050565b60005b838110156109ee5780820151818401526020810190506109d3565b60008484015250505050565b6000610a05826109b4565b610a0f81856109bf565b9350610a1f8185602086016109d0565b610a288161074c565b840191505092915050565b600060e082019050610a48600083018a6109a5565b8181036020830152610a5a81896109fa565b90508181036040830152610a6e81886109fa565b90508181036060830152610a8281876109fa565b90508181036080830152610a9681866109fa565b9050610aa560a08301856106ab565b610ab260c08301846106ab565b98975050505050505050565b6000610ac982610973565b9050919050565b610ad981610abe565b82525050565b6000819050919050565b6000610b04610aff610afa84610973565b610adf565b610973565b9050919050565b6000610b1682610ae9565b9050919050565b6000610b2882610b0b565b9050919050565b610b3881610b1d565b82525050565b6000606082019050610b536000830186610ad0565b610b606020830185610b2f565b610b6d60408301846106ab565b949350505050565b60008115159050919050565b610b8a81610b75565b8114610b9557600080fd5b50565b600081519050610ba781610b81565b92915050565b600060208284031215610bc357610bc26106df565b5b6000610bd184828501610b98565b91505092915050565b7f5472616e73666572206661696c65642e00000000000000000000000000000000600082015250565b6000610c106010836109bf565b9150610c1b82610bda565b602082019050919050565b60006020820190508181036000830152610c3f81610c03565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000610c80826106a1565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203610cb257610cb1610c46565b5b600182019050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b60006002820490506001821680610d0457607f821691505b602082108103610d1757610d16610cbd565b5b50919050565b60008190508160005260206000209050919050565b60006020601f8301049050919050565b600082821b905092915050565b600060088302610d7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82610d42565b610d898683610d42565b95508019841693508086168417925050509392505050565b6000610dbc610db7610db2846106a1565b610adf565b6106a1565b9050919050565b6000819050919050565b610dd683610da1565b610dea610de282610dc3565b848454610d4f565b825550505050565b600090565b610dff610df2565b610e0a818484610dcd565b505050565b5b81811015610e2e57610e23600082610df7565b600181019050610e10565b5050565b601f821115610e7357610e4481610d1d565b610e4d84610d32565b81016020851015610e5c578190505b610e70610e6885610d32565b830182610e0f565b50505b505050565b600082821c905092915050565b6000610e9660001984600802610e78565b1980831691505092915050565b6000610eaf8383610e85565b9150826002028217905092915050565b610ec8826109b4565b67ffffffffffffffff811115610ee157610ee061075d565b5b610eeb8254610cec565b610ef6828285610e32565b600060209050601f831160018114610f295760008415610f17578287015190505b610f218582610ea3565b865550610f89565b601f198416610f3786610d1d565b60005b82811015610f5f57848901518255600182019150602085019450602081019050610f3a565b86831015610f7c5784890151610f78601f891682610e85565b8355505b6001600288020188555050505b50505050505056fea264697066735822122012de6446fc262186277d98c8d47bc3934c2ab3b79c4da30e4c2b903148b9da9064736f6c63430008110033",
"devdoc": {
"kind": "dev",
"methods": {},
"version": 1
},
"userdoc": {
"kind": "user",
"methods": {},
"version": 1
},
"storageLayout": {
"storage": [
{
"astId": 71,
"contract": "contracts/Marketplace.sol:Marketplace",
"label": "productsLength",
"offset": 0,
"slot": "0",
"type": "t_uint256"
},
{
"astId": 74,
"contract": "contracts/Marketplace.sol:Marketplace",
"label": "cUsdTokenAddress",
"offset": 0,
"slot": "1",
"type": "t_address"
},
{
"astId": 94,
"contract": "contracts/Marketplace.sol:Marketplace",
"label": "products",
"offset": 0,
"slot": "2",
"type": "t_mapping(t_uint256,t_struct(Product)89_storage)"
}
],
"types": {
"t_address": {
"encoding": "inplace",
"label": "address",
"numberOfBytes": "20"
},
"t_address_payable": {
"encoding": "inplace",
"label": "address payable",
"numberOfBytes": "20"
},
"t_mapping(t_uint256,t_struct(Product)89_storage)": {
"encoding": "mapping",
"key": "t_uint256",
"label": "mapping(uint256 => struct Marketplace.Product)",
"numberOfBytes": "32",
"value": "t_struct(Product)89_storage"
},
"t_string_storage": {
"encoding": "bytes",
"label": "string",
"numberOfBytes": "32"
},
"t_struct(Product)89_storage": {
"encoding": "inplace",
"label": "struct Marketplace.Product",
"members": [
{
"astId": 76,
"contract": "contracts/Marketplace.sol:Marketplace",
"label": "owner",
"offset": 0,
"slot": "0",
"type": "t_address_payable"
},
{
"astId": 78,
"contract": "contracts/Marketplace.sol:Marketplace",
"label": "name",
"offset": 0,
"slot": "1",
"type": "t_string_storage"
},
{
"astId": 80,
"contract": "contracts/Marketplace.sol:Marketplace",
"label": "image",
"offset": 0,
"slot": "2",
"type": "t_string_storage"
},
{
"astId": 82,
"contract": "contracts/Marketplace.sol:Marketplace",
"label": "description",
"offset": 0,
"slot": "3",
"type": "t_string_storage"
},
{
"astId": 84,
"contract": "contracts/Marketplace.sol:Marketplace",
"label": "location",
"offset": 0,
"slot": "4",
"type": "t_string_storage"
},
{
"astId": 86,
"contract": "contracts/Marketplace.sol:Marketplace",
"label": "price",
"offset": 0,
"slot": "5",
"type": "t_uint256"
},
{
"astId": 88,
"contract": "contracts/Marketplace.sol:Marketplace",
"label": "sold",
"offset": 0,
"slot": "6",
"type": "t_uint256"
}
],
"numberOfBytes": "224"
},
"t_uint256": {
"encoding": "inplace",
"label": "uint256",
"numberOfBytes": "32"
}
}
}
}
```
Next, create another file inside the abi folder called erc20.json and copy the ABI below into it.
```json
{
"address": "0x874069Fa1Eb16D44d622F2e0Ca25eeA172369bC1",
"abi" : [
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "allowance",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "approve",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalSupply",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "transfer",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
}
]
}
```
This is the ABI for the cUSD ERC20 token we would be using in our marketplace.
#### 3.4.2 Implement Contract Hooks
React Hooks are simple JavaScript functions that we can use to isolate the reusable part from a functional component. Hooks can be stateful and can manage side-effects.
We will take advantage of these hooks to create reusable functions that will aid us with reading and writing to our Marketplace smart contract.
Under the react-app folder, let us create a sub-folder called *hooks*.
Inside this hooks folder, we create another called *contract* which will contain hooks that are used for smart contract interaction.
##### 3.4.2.1 Implement Contract Read
Now let us create a hook which enables us to fetch data from our smart contract easily. Let us create a file called *useContractRead.js* inside the *hooks/contract* directory.
Inside this file we implement the following code:
```typescript
import {useContractRead} from 'wagmi'
import MarketplaceInstance from "../../abi/Marketplace.json";
// read from smart contract
export const useContractCall =(functionName, args, watch, from) =>{
const resp = useContractRead({
address: MarketplaceInstance.address,
abi: MarketplaceInstance.abi,
functionName: functionName,
args,
watch,
overrides: from ? {from} : undefined,
onError : (err) => {
console.log({err})
}
})
return resp
}
```
We first of all import a useContractRead function from wagmi to aid us in reading from the smart contract. Next we import the MarketInstance which is basically our Marketplace ABI and contract address.
Our useContractCall hook receives 4 parameters:
**functionName**: This is the name of the function we want to read data from in our smart contract.
**args**: This is an array of arguments we may want to pass into our smart contract
**watch**: This basically sets up a listener to constantly update us on any change to the data on our smart contract.
**from** : This is an address we may want to send the smart contract read command from. This will override the default account which is being used to fetch data from the smart contract.
Next, let us create a hook to write to the smart contract. It is quite identical to our read hook we created above.
We create a file called *useContractWrite.js* in the same folder as our write hook and put in the following code:
```javascript
import {useContractWrite, usePrepareContractWrite} from 'wagmi'
import MarketplaceInstance from "../../abi/Marketplace.json";
export const useContractSend =(functionName, args) =>{
const { config } = usePrepareContractWrite({
address: MarketplaceInstance.address,
abi: MarketplaceInstance.abi,
functionName,
args,
overrides: {
gasLimit: 1000000,
},
onError : (err) => {
console.log({err})
}
})
const { data, isSuccess, write, writeAsync, error, isLoading } = useContractWrite(config)
return {data, isSuccess, write, writeAsync, isLoading}
}
```
In this write hook, we use this usePrepareContractWrite function from wagmi to prepare our contract write command. This returns us a config. We then use the useContractWrite function and pass in this config as an argument. This will then return us information which we would use to know the status of the contract write.
Lastly, we create a hook to approve our ERC20 token to be spent by our smart contract. We create a file called *useApprove.js* and put in the following code:
```javascript
import {useContractWrite, usePrepareContractWrite} from 'wagmi'
import Erc20Instance from "../../abi/erc20.json";
import MarketplaceInstance from "../../abi/Marketplace.json";
export const useContractApprove =(price) =>{
const { config } = usePrepareContractWrite({
address: Erc20Instance.address,
abi: Erc20Instance.abi,
functionName: 'approve',
args: [MarketplaceInstance.address, price],
overrides: {
gasLimit: 1000000
},
onError : (err) => {
console.log({err})
}
})
const { data, isSuccess, write, writeAsync, error, isLoading } = useContractWrite(config)
return {data, isSuccess, write, writeAsync, isLoading}
}
```
At the end of this section, our hooks folder should look like:

#### 3.4.3 Implement Marketplace Components
Let us go ahead and implement the components we would use as building blocks for our application.
Inside the *components* folder, we see four files:
```markdown
|--components
|---Footer.tsx
|---HeaderRC.tsx
|---HeaderRX.tsx
|---Layout.tsx
```
First, we create a folder under components folder called `alerts`. Inside this `alerts` folder, let us create three files as below:
```markdown
|--components
|---alerts
|---ErrorAlert.jsx
|---LoadingAlert.jsx
|---SuccessAlert.jsx
```
These components will be used as reusable components to display information to the user when our marketplace is interacting with our smart contracts.
Firstly, we enter the *ErrorAlert.jsx* file and put in this code
```javascript
const ErrorAlert = ({message, clear}) => {
return (
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" role="alert">
<span class="block sm:inline">{message}</span>
<span class="absolute top-0 bottom-0 right-0 px-4 py-3">
<svg className="fill-current h-6 w-6 text-red-500" role="button" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20" onClick={clear}><title>Close</title><path
d="M14.348 14.849a1.2 1.2 0 0 1-1.697 0L10 11.819l-2.651 3.029a1.2 1.2 0 1 1-1.697-1.697l2.758-3.15-2.759-3.152a1.2 1.2 0 1 1 1.697-1.697L10 8.183l2.651-3.031a1.2 1.2 0 1 1 1.697 1.697l-2.758 3.152 2.758 3.15a1.2 1.2 0 0 1 0 1.698z"/></svg>
</span>
</div>
)
}
export default ErrorAlert;
```
In the code above, our component accepts two props:
message: which is the error message we want to show to the user
clear: which is a function to clear the error notification.
Next, let us implemenent the code for `LoadingAlert.jsx` and `SuccessAlert.jsx` respectively
```javascript
const LoadingAlert = ({message}) => {
return (
<div class="bg-blue-100 border-t border-b border-blue-500 text-blue-700 px-4 py-3" role="alert">
<p class="font-bold">{message || 'Loading...'}</p>
<p class="text-sm">Interacting with smart contract.</p>
</div>
)
}
export default LoadingAlert
```
```javascript
const SuccessAlert = ({message}) => {
return (
<div class="bg-teal-100 border-t-4 border-teal-500 rounded-b text-teal-900 px-4 py-3 shadow-md" role="alert">
<div class="flex">
<div>
<p class="font-bold">Success</p>
<p class="text-sm">{message}</p>
</div>
</div>
</div>
)
}
export default SuccessAlert
```
#### 3.4.3 Implement Product Create Modal
Now let us create our product create component. This component will allow us create a product on the celo blockchain through our marketplace.
We will create a file called *AddProductModal.jsx* in the components folder.
Firstly, let us import the following:
```javascript
import {useState} from "react";
import {useContractSend} from "@/hooks/contract/useContractWrite";
import {useDebounce} from "use-debounce";
import {toast} from "react-toastify";
```
We are using a package called useDebounce to debounce our input fields. This is to prevent our smart contract from being called too many times when the user is typing in the input fields.
let us install the package in our react-app directory by running the following command:
```
npm install use-debounce
```
or
```
yarn add use-debounce
```
Next let us create a component called AppProductModal and initialize our states with the following code:
```javascript
const AddProductModal = () => {
const [visible, setVisible] = useState(false);
const [productName, setProductName] = useState('');
const [productPrice, setProductPrice] = useState(0);
const [productImage, setProductImage] = useState('');
const [productDescription, setProductDescription] = useState('');
const [productLocation, setProductLocation] = useState('');
const [debouncedProductName] = useDebounce(productName, 500)
const [debouncedProductPrice] = useDebounce(productPrice, 500)
const [debouncedProductImage] = useDebounce(productImage, 500)
const [debouncedProductDescription] = useDebounce(productDescription, 500)
const [debouncedProductLocation] = useDebounce(productLocation, 500)
const [loading, setLoading] = useState('');
```
Next we create an isComplete function to check if all the input fields are filled. We also create a product state to store the product details.
```javascript
const isComplete = productName && productPrice && productImage && productLocation && productDescription;
```
Next let us prepare our contract write hook. We will use this hook to interact with our smart contract.
```javascript
const {
writeAsync: createProduct
} = useContractSend('writeProduct', [debouncedProductName, debouncedProductImage, debouncedProductDescription, debouncedProductLocation, debouncedProductPrice]);
```
Next let us create a function to handle the form submission and clear the form. We will call the createProduct function here.
```javascript
const clearForm = () => {
setProductName('')
setProductPrice(0)
setProductImage('')
setProductDescription('')
setProductLocation('')
}
const handleCreateProduct = async () => {
setLoading('Creating...')
if (!isComplete) throw new Error('Please fill all fields')
const purchaseTx = await createProduct()
setLoading('Waiting for confirmation...')
await purchaseTx.wait()
setVisible(false)
clearForm()
}
const addProduct = async (e) => {
e.preventDefault()
try {
await toast.promise(handleCreateProduct(), {
pending: 'Creating product...',
success: 'Product created successfully',
error: (e) => {
return e?.message || "Something went wrong. Try again."
}
})
} catch (e) {
console.log({e})
toast.error(e?.message || "Something went wrong. Try again.")
} finally {
setLoading('')
}
}
```
Above we make use of the toast.promise function to show a loading notification to the user while the transaction is being processed. We also show a success notification when the transaction is successful.
Next, let us create the modal component and export it. We will use the tailwind modal.
```javascript
return (
<div>
<div>
<button type="button"
onClick={() => setVisible(true)}
className="inline-block ml-4 px-6 py-2.5 bg-black text-white font-medium text-xs leading-tight uppercase rounded-2xl shadow-md hover:bg-blue-700 hover:shadow-lg focus:bg-blue-700 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-blue-800 active:shadow-lg transition duration-150 ease-in-out"
data-bs-toggle="modal" data-bs-target="#exampleModalCenter">
Add Product
</button>
{
visible && (
<div class="fixed z-40 overflow-y-auto top-0 w-full left-0" id="modal">
<form onSubmit={addProduct}>
<div
class="flex items-center justify-center min-height-100vh pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div class="fixed inset-0 transition-opacity">
<div class="absolute inset-0 bg-gray-900 opacity-75"/>
</div>
<span class="hidden sm:inline-block sm:align-middle sm:h-screen">​</span>
<div
class="inline-block align-center bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog" aria-modal="true" aria-labelledby="modal-headline">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<label>Product Name</label>
<input onChange={(e) => {
setProductName(e.target.value)
}} required type="text" className="w-full bg-gray-100 p-2 mt-2 mb-3"/>
<label>Product Image (URL)</label>
<input onChange={(e) => {
setProductImage(e.target.value)
}} required type="text" className="w-full bg-gray-100 p-2 mt-2 mb-3"/>
<label>Product Description</label>
<input onChange={(e) => {
setProductDescription(e.target.value)
}} required type="text" className="w-full bg-gray-100 p-2 mt-2 mb-3"/>
<label>Product Location</label>
<input onChange={(e) => {
setProductLocation(e.target.value)
}} required type="text" className="w-full bg-gray-100 p-2 mt-2 mb-3"/>
<label>Product Price (cUSD)</label>
<input onChange={(e) => {
setProductPrice(e.target.value)
}} required type="number" className="w-full bg-gray-100 p-2 mt-2 mb-3"/>
</div>
<div class="bg-gray-200 px-4 py-3 text-right">
<button type="button"
className="py-2 px-4 bg-gray-500 text-white rounded hover:bg-gray-700 mr-2"
onClick={() => setVisible(false)}><i class="fas fa-times"></i> Cancel
</button>
<button type="submit"
disabled={loading || !isComplete || !createProduct}
className="py-2 px-4 bg-blue-500 text-white rounded hover:bg-blue-700 mr-2">
{loading ? loading : 'Create'}
</button>
</div>
</div>
</div>
</form>
</div>
)
}
</div>
</div>
)
}
export default AddProductModal
```
Above, we have a form that takes in the product name, image, description, location and price. We also have a cancel button and a create button. The create button is disabled if the form is not complete or if the createProduct function is not available.
Finally we export our component
At the end of this section, our AddProductModal.jsx file should look like:
```javascript
import {useState} from "react";
import {useContractSend} from "@/hooks/contract/useContractWrite";
import {useDebounce} from "use-debounce";
import {toast} from "react-toastify";
const AddProductModal = () => {
const [visible, setVisible] = useState(false);
const [productName, setProductName] = useState('');
const [productPrice, setProductPrice] = useState(0);
const [productImage, setProductImage] = useState('');
const [productDescription, setProductDescription] = useState('');
const [productLocation, setProductLocation] = useState('');
const [debouncedProductName] = useDebounce(productName, 500)
const [debouncedProductPrice] = useDebounce(productPrice, 500)
const [debouncedProductImage] = useDebounce(productImage, 500)
const [debouncedProductDescription] = useDebounce(productDescription, 500)
const [debouncedProductLocation] = useDebounce(productLocation, 500)
const [loading, setLoading] = useState('');
const isComplete = productName && productPrice && productImage && productLocation && productDescription;
const {
writeAsync: createProduct
} = useContractSend('writeProduct', [debouncedProductName, debouncedProductImage, debouncedProductDescription, debouncedProductLocation, debouncedProductPrice]);
const clearForm = () => {
setProductName('')
setProductPrice(0)
setProductImage('')
setProductDescription('')
setProductLocation('')
}
const handleCreateProduct = async () => {
setLoading('Creating...')
if (!isComplete) throw new Error('Please fill all fields')
const purchaseTx = await createProduct()
setLoading('Waiting for confirmation...')
await purchaseTx.wait()
setVisible(false)
clearForm()
}
const addProduct = async (e) => {
e.preventDefault()
try {
await toast.promise(handleCreateProduct(), {
pending: 'Creating product...',
success: 'Product created successfully',
error: (e) => {
return e?.message || "Something went wrong. Try again."
}
})
} catch (e) {
console.log({e})
toast.error(e?.message || "Something went wrong. Try again.")
} finally {
setLoading('')
}
}
return (
<div>
<div>
<button type="button"
onClick={() => setVisible(true)}
className="inline-block ml-4 px-6 py-2.5 bg-black text-white font-medium text-xs leading-tight uppercase rounded-2xl shadow-md hover:bg-blue-700 hover:shadow-lg focus:bg-blue-700 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-blue-800 active:shadow-lg transition duration-150 ease-in-out"
data-bs-toggle="modal" data-bs-target="#exampleModalCenter">
Add Product
</button>
{
visible && (
<div class="fixed z-40 overflow-y-auto top-0 w-full left-0" id="modal">
<form onSubmit={addProduct}>
<div
class="flex items-center justify-center min-height-100vh pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div class="fixed inset-0 transition-opacity">
<div class="absolute inset-0 bg-gray-900 opacity-75"/>
</div>
<span class="hidden sm:inline-block sm:align-middle sm:h-screen">​</span>
<div
class="inline-block align-center bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
role="dialog" aria-modal="true" aria-labelledby="modal-headline">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<label>Product Name</label>
<input onChange={(e) => {
setProductName(e.target.value)
}} required type="text" className="w-full bg-gray-100 p-2 mt-2 mb-3"/>
<label>Product Image (URL)</label>
<input onChange={(e) => {
setProductImage(e.target.value)
}} required type="text" className="w-full bg-gray-100 p-2 mt-2 mb-3"/>
<label>Product Description</label>
<input onChange={(e) => {
setProductDescription(e.target.value)
}} required type="text" className="w-full bg-gray-100 p-2 mt-2 mb-3"/>
<label>Product Location</label>
<input onChange={(e) => {
setProductLocation(e.target.value)
}} required type="text" className="w-full bg-gray-100 p-2 mt-2 mb-3"/>
<label>Product Price (cUSD)</label>
<input onChange={(e) => {
setProductPrice(e.target.value)
}} required type="number" className="w-full bg-gray-100 p-2 mt-2 mb-3"/>
</div>
<div class="bg-gray-200 px-4 py-3 text-right">
<button type="button"
className="py-2 px-4 bg-gray-500 text-white rounded hover:bg-gray-700 mr-2"
onClick={() => setVisible(false)}><i class="fas fa-times"></i> Cancel
</button>
<button type="submit"
disabled={loading || !isComplete || !createProduct}
className="py-2 px-4 bg-blue-500 text-white rounded hover:bg-blue-700 mr-2">
{loading ? loading : 'Create'}
</button>
</div>
</div>
</div>
</form>
</div>
)
}
</div>
</div>
)
}
export default AddProductModal
```
#### 3.4.3.1 Implement Product Create Wrapper
Let us then create an AddProduct component which wraps our AddProductModal component. We will create this component in the components folder. We will name this component *AddProduct.jsx*.
Firstly, let us import the following:
```javascript
import AddProductModal from "./AddProductModal";
```
Next, let us render out our AddProductModal component:
```javascript
const AddProduct = () => {
return(
<div className="flex justify-start">
<AddProductModal/>
</div>
)
}
export default AddProduct;
```
At the end of this section, our AddProduct.jsx file should look like:
```javascript
import AddProductModal from "./AddProductModal";
const AddProduct = () => {
return(
<div className="flex justify-start">
<AddProductModal/>
</div>
)
}
export default AddProduct;
````
#### 3.4.4 Implement Product Component
Now let us create our Product component. This component will display our product details and also allow us to buy the product.
We will create a file called *Product.jsx* in the components folder.
Firstly, let us import the following:
```javascript
import {useContractApprove} from "@/hooks/contract/useApprove";
import {toast} from "react-toastify";
import {useContractCall} from "@/hooks/contract/useContractRead";
import {identiconTemplate} from "@/helpers";
import {useState, useEffect} from "react";
import {useContractSend} from "@/hooks/contract/useContractWrite";
```
We import our useContractApprove hook, our toastify library, our useContractCall hook and our identiconTemplate helper function. We would create this helper function in the next section.
Next let us initialize our component state variables, hooks and functions:
```javascript
const Product = ({id, setError, setLoading, clear}) => {
const {data: rawProduct} = useContractCall('readProduct', [id], true);
const {
writeAsync: purchase
} = useContractSend('buyProduct', [Number(id)]);
const [product, setProduct] = useState(null);
const {
writeAsync: approve
} = useContractApprove(product?.price?.toString() || '0');
const formatProduct = () => {
if (!rawProduct) return null;
return {
owner: rawProduct[0],
name: rawProduct[1],
image: rawProduct[2],
description: rawProduct[3],
location: rawProduct[4],
price: Number(rawProduct[5]),
sold: rawProduct[6].toString()
}
}
useEffect(() => {
if (!rawProduct) return
setProduct(formatProduct());
}, [rawProduct]);
const handlePurchase = async () => {
const approveTx = await approve()
await approveTx.wait(1)
setLoading('Purchasing...')
const res = await purchase()
await res.wait()
}
const purchaseProduct = async () => {
setLoading('Approving ...')
clear()
try {
await toast.promise(handlePurchase(), {
pending: 'Purchasing product...',
success: 'Product purchased successfully',
error: (e) => {
return e?.reason || e?.message || "Something went wrong. Try again."
}
})
} catch (e) {
console.log({e})
setError(e?.reason || e?.message || "Something went wrong. Try again.")
} finally {
setLoading(null)
}
}
```
Our product component takes in an id, an error setter, a loading setter and a clear function.
We then initialize our useContractSend hook to call our buyProduct function. We also initialize our useContractApprove hook to approve our product price. We then create a function to format our product data. Finally, we then create a function to handle our purchase.
We use the toast.promise function to handle our purchase. This function takes in a promise and an object with pending, success and error messages. We then return our product component:
Finally, we return our product component. If the product does not exist, we return null:
```javascript
if (!product) return null
return (
<div className={'shadow-lg relative'}>
<a key={product.id} href={product.href} className="group">
<div className="aspect-w-1 aspect-h-1 w-full overflow-hidden bg-gray-200 xl:aspect-w-7 xl:aspect-h-8">
<span
className={'absolute z-10 right-0 mt-4 bg-amber-400 text-black p-1 rounded-l-lg'}>{product.sold} sold</span>
<img
src={product.image}
alt={'image'}
className=" w-full w-full h-80 object-cover object-center group-hover:opacity-75"
/>
<span className={'absolute -mt-6 ml-4'}>{identiconTemplate(product.owner)}</span>
</div>
<div className={'m-5'}>
<div>
<p className="mt-4 text-2xl font-bold">{product.name}</p>
<h3 className="mt-4 text-sm text-gray-700">{product.description}</h3>
</div>
<div className={'mt-12'}>
<div className={'flex flex-row'}>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5}
stroke="currentColor" className="w-6 h-6">
<path strokeLinecap="round" strokeLinejoin="round"
d="M15 10.5a3 3 0 11-6 0 3 3 0 016 0z"/>
<path strokeLinecap="round" strokeLinejoin="round"
d="M19.5 10.5c0 7.142-7.5 11.25-7.5 11.25S4.5 17.642 4.5 10.5a7.5 7.5 0 1115 0z"/>
</svg>
<h3 className=" px-2 pt-1 text-sm text-gray-700">{product.location}</h3>
</div>
<button onClick={purchaseProduct}
className="mt-4 h-14 w-full border-2 border-black text-black p-2 rounded-lg hover:bg-black hover:text-white">Buy
for {product.price.toFixed(2)} cUSD
</button>
</div>
</div>
</a>
</div>
)
}
export default Product
````
let us create our identiconTemplate helper function. This function will take in an address and return an identicon image.
We will create a folder called *helpers* in the *react-app* folder. We will then create a file called *index.js* in the helpers folder.
We will then create our identiconTemplate function:
```javascript
import Blockies from 'react-blockies';
export const identiconTemplate = (address) => {
return <Blockies
size={14} // number of pixels square
scale={3} // width/height of each 'pixel'
className="identicon rounded-2xl border-2" // optional className
seed={address} // seed used to generate icon data, default: random
/>
}
export const truncateAddress = (address) => {
if(!address) return '';
return address.slice(0, 6) + '...' + address.slice(-4)
}
```
we are using the react-blockies package to create our identicon. We install the package by running the following command inside the *react-app-tailwindscss* folder:
```bash
npm install react-blockies
```
or
```bash
yarn add react-blockies
```
We then export our identiconTemplate function and our truncateAddress function. We will use the truncateAddress function to truncate our address to 6 characters.
#### 3.4.4 Implement Product List Component
Let us now implement our product list component. We will create a file called *ProductList.js* in the *components* folder.
We will then import our Product component and our useContractCall hook as well as our alert components:
```javascript
import {useContractCall} from "@/hooks/contract/useContractRead";
import Product from "@/components/Product";
import ErrorAlert from "@/components/alerts/ErrorAlert";
import {useState} from "react";
import SuccessAlert from "@/components/alerts/SuccessAlert";
import LoadingAlert from "@/components/alerts/LoadingAlert";
```
We will then create our ProductList component:
```javascript
const ProductList = () => {
const {data} = useContractCall('getProductsLength', [], true);
const productLength = data ? Number(data.toString()) : 0;
const [error, setError] = useState("");
const [success, setSuccess] = useState("");
const [loading, setLoading] = useState("");
```
We use our useContractCall hook we created earlier to get the length of our products array. We then initialize our error, success and loading state variables.
Next, let us create a couple of functions to clear our errors and also get our marketplace products.
```javascript
const clear = () => {
setError("");
setSuccess("");
setLoading("");
}
const getProducts = () => {
if (!productLength) return null
const nfts = [];
for (let i = 0; i < productLength; i++) {
nfts.push(<Product key={i} id={i} setSuccess={setSuccess} setError={setError} setLoading={setLoading}
loading={loading} clear={clear}/>)
}
return nfts;
}
```
We basically loop through our products array and create a Product component for each product. We then return this array of Product components.
Next, let us render out our products:
```javascript
return(
<div>
{error && <ErrorAlert message={error} clear={clear}/>}
{success && <SuccessAlert message={success}/>}
{loading && <LoadingAlert message={loading}/>}
<div className="mx-auto max-w-2xl py-16 px-4 sm:py-24 sm:px-6 lg:max-w-7xl lg:px-8">
<h2 className="sr-only">Products</h2>
<div className="grid grid-cols-1 gap-y-10 gap-x-6 sm:grid-cols-2 lg:grid-cols-3 xl:gap-x-8">
{productLength ? getProducts() : <div className={'text-center'}>No Products</div>}
</div>
</div>
</div>
)
}
export default ProductList;
```
We render out our error, success and loading alerts. We then render out our products array. At the end of this section, our AddProductModal.jsx file should look like:
```javascript
import {useContractCall} from "@/hooks/contract/useContractRead";
import Product from "@/components/Product";
import ErrorAlert from "@/components/alerts/ErrorAlert";
import {useState} from "react";
import SuccessAlert from "@/components/alerts/SuccessAlert";
import LoadingAlert from "@/components/alerts/LoadingAlert";
const ProductList = () => {
const {data} = useContractCall('getProductsLength', [], true);
const productLength = data ? Number(data.toString()) : 0;
const [error, setError] = useState("");
const [success, setSuccess] = useState("");
const [loading, setLoading] = useState("");
const clear = () => {
setError("");
setSuccess("");
setLoading("");
}
const getProducts = () => {
if (!productLength) return null
const nfts = [];
for (let i = 0; i < productLength; i++) {
nfts.push(<Product key={i} id={i} setSuccess={setSuccess} setError={setError} setLoading={setLoading}
loading={loading} clear={clear}/>)
}
return nfts;
}
return(
<div>
{error && <ErrorAlert message={error} clear={clear}/>}
{success && <SuccessAlert message={success}/>}
{loading && <LoadingAlert message={loading}/>}
<div className="mx-auto max-w-2xl py-16 px-4 sm:py-24 sm:px-6 lg:max-w-7xl lg:px-8">
<h2 className="sr-only">Products</h2>
<div className="grid grid-cols-1 gap-y-10 gap-x-6 sm:grid-cols-2 lg:grid-cols-3 xl:gap-x-8">
{productLength ? getProducts() : <div className={'text-center'}>No Products</div>}
</div>
</div>
</div>
)
}
export default ProductList;
```
## 6. Piece it all together
Now we have created our components, hooks and helpers. We will now piece it all together in our Layout.tsx file under the components folder.
Celo composer by default exports typescript code but for the sake of the tutorial we will use javascript.
Firstly we rename our layout.tsx file to layout.jsx to avoid using typescript checks.
Inside of our layout.tsx file, we will import our components:
```javascript
import Footer from "./Footer";
import Header from "./HeaderRK";
import AddProduct from "./AddProduct";
import ProductList from "./ProductList";
const Layout = () => {
return (
<>
<div className="bg-gray-50 overflow-hidden flex flex-col min-h-screen">
<Header />
<div className="w-full py-16 max-w-7xl mx-auto space-y-8 sm:px-6 lg:px-8">
<AddProduct />
<ProductList />
</div>
<Footer />
</div>
</>
)
}
export default Layout;
```
and we are done. We can now run our application by running the following command inside the *react-app-tailwindscss* folder:
```bash
npm run dev
```
We can now view our application by visiting http://localhost:3000 in our browser.