Building a Farm-to-Table Tracking Solution on Celo Blockchain (Part 2)

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

  1. Create a new Next.js project by running the following command in your terminal:
npx create-next-app farm-to-table-frontend
  1. Change to the project directory:
cd farm-to-table-frontend
  1. Install the necessary Ethereum libraries:
npm install ethers
  1. Now, let’s create a new file called TraceContract.js inside the pages/api directory. This file will handle the interaction with the smart contract.

  2. 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.

  1. Next, let’s create a new file called index.js inside the pages directory. This file will contain the frontend code.

  2. 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.

  1. Replace the 'CONTRACT_ADDRESS' in TraceContract.js with the actual address of your deployed smart contract.

Running the Application

  1. Start the Next.js development server:
npm run dev
  1. Open your browser and navigate to http://localhost:3000. You should see the Farm-to-Table frontend application.

  1. Enter the product details in the input fields and click the “Add Product” button to add a product to the blockchain.

  2. 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.

Reference

Source Code

4 Likes

Fantastic news! Your proposal has landed in this week’s top voted list. As you begin your project journey, remember to align with our community and technical guidelines, ensuring a high quality platform for our developers. Congratulations! :mortar_board: :seedling:

2 Likes

Hi @Joel ll be reviewing your piece in 1 to 2 days