Introduction
Welcome to Part 2 of our tutorial series on building a Farm-to-Table tracking solution on the Celo blockchain. In Part 1, we covered the smart contract development using Solidity and the deployment process using the Hardhat development environment. If you haven’t read Part 1, we recommend starting there to get a better understanding of the smart contract and its deployment.
In this article, we will focus on building the frontend application using Next.js, a popular React framework, to interact with the deployed smart contract. We will create a user-friendly interface for adding and retrieving product information on the blockchain. Let’s get started!
Prerequisites
Before we begin, please make sure you have the following prerequisites installed:
- Node.js and npm (Node Package Manager) - Install from nodejs.org
- Next.js - Install globally using
npm install -g create-next-app
- Hardhat - Install globally using
npm install -g hardhat
Setting Up the Project
- Create a new Next.js project by running the following command in your terminal:
npx create-next-app farm-to-table-frontend
- Change to the project directory:
cd farm-to-table-frontend
- Install the necessary Ethereum libraries:
npm install ethers
-
Now, let’s create a new file called
TraceContract.js
inside thepages/api
directory. This file will handle the interaction with the smart contract. -
Open the
TraceContract.js
file and add the following code:
import { ethers } from 'ethers';
const contractAddress = 'CONTRACT_ADDRESS';
const abi = [
// Paste the ABI of the Trace contract here
];
export default async function handler(req, res) {
if (req.method === 'POST') {
try {
const { id, name, farmer, location } = req.body;
const provider = ethers.getDefaultProvider();
const signer = provider.getSigner();
const contract = new ethers.Contract(contractAddress, abi, signer);
const addProductTx = await contract.addProduct(id, name, farmer, location);
await addProductTx.wait();
res.status(200).json({ message: 'Product added successfully' });
} catch (error) {
console.error(error);
res.status(500).json({ message: 'Error adding product' });
}
} else if (req.method === 'GET') {
try {
const { id } = req.query;
const provider = ethers.getDefaultProvider();
const contract = new ethers.Contract(contractAddress, abi, provider);
const product = await contract.getProduct(id);
res.status(200).json({
name: product[0],
farmer: product[1],
location: product[2],
timestamp: product[3],
});
} catch (error) {
console.error(error);
res.status(500).json({ message: 'Error retrieving product' });
}
} else {
res.status(404).json({ message: 'Invalid request method' });
}
}
Make sure to replace 'CONTRACT_ADDRESS'
with the actual address of your deployed smart contract. Also, include the ABI (Application Binary Interface) of the Trace contract obtained during the deployment process.
-
Next, let’s create a new file called
index.js
inside thepages
directory. This file will contain the frontend code. -
Open the
index.js
file and add the following code:
import { useState } from 'react';
export default function Home() {
const [productId, setProductId] = useState('');
const [productName, setProductName] = useState('');
const [farmerName, setFarmerName] = useState('');
const [location, setLocation] = useState('');
const [product, setProduct] = useState(null);
const [message, setMessage] = useState('');
const handleAddProduct = async () => {
try {
const response = await fetch('/api/TraceContract', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
id: productId,
name: productName,
farmer: farmerName,
location: location,
}),
});
const data = await response.json();
setMessage(data.message);
} catch (error) {
console.error(error);
setMessage('Error adding product');
}
};
const handleGetProduct = async () => {
try {
const response = await fetch(`/api/TraceContract?id=${productId}`);
const data = await response.json();
setProduct(data);
setMessage('');
} catch (error) {
console.error(error);
setMessage('Error retrieving product');
}
};
return (
<div className="container">
<h1 className="title">Trace Contract Frontend</h1>
<div className="form-group">
<label>Product ID:</label>
<input type="text" value={productId} onChange={(e) => setProductId(e.target.value)} />
</div>
<div className="form-group">
<label>Product Name:</label>
<input type="text" value={productName} onChange={(e) => setProductName(e.target.value)} />
</div>
<div className="form-group">
<label>Farmer Name:</label>
<input type="text" value={farmerName} onChange={(e) => setFarmerName(e.target.value)} />
</div>
<div className="form-group">
<label>Location:</label>
<input type="text" value={location} onChange={(e) => setLocation(e.target.value)} />
</div>
<div className="button-group">
<button className="add-button" onClick={handleAddProduct}>Add Product</button>
<button className="get-button" onClick={handleGetProduct}>Get Product</button>
</div>
{message && <p className="message">{message}</p>}
{product && (
<div className="product-details">
<h2>Product Details</h2>
<p>Name: {product.name}</p>
<p>Farmer: {product.farmer}</p>
<p>Location: {product.location}</p>
<p>Timestamp: {product.timestamp}</p>
</div>
)}
<style jsx>{`
.container {
max-width: 400px;
margin: 0 auto;
padding: 20px;
background-color: #f7f7f7;
}
.title {
font-size: 24px;
text-align: center;
margin-bottom: 20px;
color: #333;
}
.form-group {
margin-bottom: 10px;
}
label {
display: block;
font-weight: bold;
margin-bottom: 5px;
}
input {
width: 100%;
padding: 8px;
border-radius: 4px;
border: 1px solid #ccc;
}
.button-group {
display: flex;
justify-content: space-between;
margin-bottom: 20px;
}
.add-button,
.get-button {
padding: 8px 16px;
font-size: 16px;
border-radius: 4px;
color: #fff;
background-color: #007bff;
border: none;
cursor: pointer;
}
.add-button:hover,
.get-button:hover {
background-color: #0056b3;
}
.message {
font-weight: bold;
margin-bottom: 10px;
}
.product-details {
margin-top: 20px;
background-color: #fff;
border-radius: 4px;
padding: 10px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.product-details h2 {
font-size: 20px;
margin-bottom: 10px;
}
.product-details p {
margin-bottom: 5px;
}
`}</style>
</div>
);
}
This code sets up a simple user interface for adding and retrieving product information. We use React hooks to manage the form inputs and the state of the product and error messages. The handleAddProduct
function sends a POST request to the TraceContract
API endpoint to add a product, while the handleGetProduct
function sends a GET request to retrieve a product based on its ID.
- Replace the
'CONTRACT_ADDRESS'
inTraceContract.js
with the actual address of your deployed smart contract.
Running the Application
- Start the Next.js development server:
npm run dev
- Open your browser and navigate to
http://localhost:3000
. You should see the Farm-to-Table frontend application.
-
Enter the product details in the input fields and click the “Add Product” button to add a product to the blockchain.
-
To retrieve a product, enter the product ID in the input field and click the “Get Product” button. The product details will be displayed below.
Conclusion
You have successfully built the frontend application for your Farm-to-Table tracking solution using Next.js. The application allows users to add and retrieve product information on the Celo blockchain.
In Part 2 of this tutorial series, we learned how to set up the frontend using Next.js and interact with the smart contract through API calls
About Us
Joel Obafemi
A marketer, copywriter, and collab manager for web3 brands. You can connect with me on LinkedIn.