From 1042d99adcbed95876f67c22f8002340a9268e84 Mon Sep 17 00:00:00 2001 From: ShinichiShi Date: Sat, 14 Sep 2024 01:27:05 +0530 Subject: [PATCH] buyer dashboard phase 2 done + linting left --- client/firebase.js | 5 +- client/index.html | 2 +- client/src/Landing.jsx | 6 +- client/src/components/Buyer/BHeader.jsx | 44 +- client/src/components/Buyer/BNavbar.jsx | 46 +- .../components/Buyer/Contract/BContract.jsx | 60 +-- .../Buyer/Contract/ContractStatus.jsx | 374 +++++++++++++++- .../components/Buyer/Contract/Download.jsx | 95 +++- .../Buyer/Contract/GenerateContract.jsx | 168 ++++--- .../components/Buyer/Contract/Metamask.jsx | 226 +++++++++- client/src/components/Buyer/Contract/Pdf.jsx | 81 ---- .../components/Buyer/Contract/PdfDetails.jsx | 132 +++--- client/src/components/Buyer/Deals/BDeals.jsx | 12 +- .../components/Buyer/Deals/BDealsSection.jsx | 25 +- client/src/components/Buyer/Login.jsx | 21 +- client/src/components/Buyer/Navi.jsx | 37 +- .../components/Buyer/Settings/BSettings.jsx | 3 +- client/src/components/Buyer/Signup.jsx | 24 +- client/src/components/Buyer/signup.module.css | 298 ++++++------- client/src/components/Contract/Contract_2.jsx | 136 +++--- client/src/components/Farmer/FDashboard.jsx | 33 +- .../components/Farmer/FDashboard.module.css | 121 ++--- client/src/components/Farmer/FLogin.jsx | 25 +- client/src/components/Farmer/FSignup.jsx | 68 +-- client/src/components/Farmer/FarmSell.jsx | 70 ++- .../src/components/Farmer/FarmSell.module.css | 377 ++++++++-------- client/src/components/Farmer/cropsData.json | 175 ++++---- .../components/LandingPage/AgriConnect.jsx | 276 ++++++++++++ client/src/components/LandingPage/Footer.jsx | 8 +- .../components/LandingPage/Landing.module.css | 86 ++-- client/src/components/LandingPage/Navbar.jsx | 95 ++-- client/src/components/Negotiate/Chat.jsx | 2 +- client/src/components/Negotiate/chat.css | 26 ++ .../components/ProfileSetup/Profilesetup.jsx | 12 +- .../ProfileSetup/profilesetup.module.css | 416 +++++++++--------- client/src/components/context/AuthContext.jsx | 43 +- .../src/contractAddress/MultiAgriConnect.json | 81 +--- client/src/main.jsx | 22 +- client/src/output.css | 114 +++-- client/vite.config.js | 1 - 40 files changed, 2444 insertions(+), 1402 deletions(-) delete mode 100644 client/src/components/Buyer/Contract/Pdf.jsx create mode 100644 client/src/components/LandingPage/AgriConnect.jsx create mode 100644 client/src/components/Negotiate/chat.css diff --git a/client/firebase.js b/client/firebase.js index 20d131b..e99daf8 100644 --- a/client/firebase.js +++ b/client/firebase.js @@ -1,6 +1,5 @@ - import { initializeApp } from 'firebase/app'; -import { getAuth, GoogleAuthProvider} from 'firebase/auth'; +import { getAuth, GoogleAuthProvider } from 'firebase/auth'; import { getFirestore } from 'firebase/firestore'; import { getStorage } from 'firebase/storage'; @@ -20,4 +19,4 @@ const db = getFirestore(app); const googleProvider = new GoogleAuthProvider(); const storage = getStorage(app); -export { auth, app, googleProvider, db, storage}; +export { auth, app, googleProvider, db, storage }; diff --git a/client/index.html b/client/index.html index a5f95e3..b56eeed 100644 --- a/client/index.html +++ b/client/index.html @@ -9,7 +9,7 @@ - +
diff --git a/client/src/Landing.jsx b/client/src/Landing.jsx index 9a656f2..c410e80 100644 --- a/client/src/Landing.jsx +++ b/client/src/Landing.jsx @@ -1,11 +1,7 @@ import './App.css'; import AgriConnect from './components/LandingPage/AgriConnect'; function Landing() { - return ( - <> - {}, - - ); + return <>{},; } export default Landing; diff --git a/client/src/components/Buyer/BHeader.jsx b/client/src/components/Buyer/BHeader.jsx index db221b9..f4aad00 100644 --- a/client/src/components/Buyer/BHeader.jsx +++ b/client/src/components/Buyer/BHeader.jsx @@ -1,10 +1,9 @@ -import { FaSearch } from 'react-icons/fa'; import { MdOutlineTranslate } from 'react-icons/md'; -import { IoIosArrowDropdown ,IoIosArrowDropup } from "react-icons/io"; +import { IoIosArrowDropdown, IoIosArrowDropup } from 'react-icons/io'; import { useState, useEffect, useRef } from 'react'; -import { signOut } from 'firebase/auth'; -import { auth } from '../../../firebase' -import {useNavigate} from 'react-router-dom' +import { signOut } from 'firebase/auth'; +import { auth } from '../../../firebase'; +import { useNavigate } from 'react-router-dom'; import { toast, ToastContainer } from 'react-toastify'; function BHeader() { @@ -21,16 +20,16 @@ function BHeader() { document.addEventListener('mousedown', handleClickOutside); return () => { document.removeEventListener('mousedown', handleClickOutside); - }; + }; }, [dropdownRef]); const handleLogout = async () => { - console.log("enter") + console.log('enter'); try { await signOut(auth); - console.log('hello') - toast.success("Logged Out") - navigate('/login'); + console.log('hello'); + toast.success('Logged Out'); + navigate('/login'); } catch (error) { toast.error('Error signing out:', error); } @@ -40,19 +39,7 @@ function BHeader() {
AgriConnect
-
-
- - -
-
+
@@ -64,7 +51,7 @@ function BHeader() { Eng {dropdownOpen ? : }
- {dropdownOpen && ( + {dropdownOpen && (
  • @@ -80,16 +67,19 @@ function BHeader() {
)}
-
- + ); } -export default BHeader; \ No newline at end of file +export default BHeader; diff --git a/client/src/components/Buyer/BNavbar.jsx b/client/src/components/Buyer/BNavbar.jsx index 8f48414..85e2104 100644 --- a/client/src/components/Buyer/BNavbar.jsx +++ b/client/src/components/Buyer/BNavbar.jsx @@ -5,12 +5,42 @@ export default function BNavbar({ handleNavigate, navigate }) { @@ -18,6 +48,6 @@ export default function BNavbar({ handleNavigate, navigate }) { } BNavbar.propTypes = { - handleNavigate: PropTypes.func.isRequired, - navigate: PropTypes.string.isRequired, + handleNavigate: PropTypes.func.isRequired, + navigate: PropTypes.string.isRequired, }; diff --git a/client/src/components/Buyer/Contract/BContract.jsx b/client/src/components/Buyer/Contract/BContract.jsx index 4793f32..d272c02 100644 --- a/client/src/components/Buyer/Contract/BContract.jsx +++ b/client/src/components/Buyer/Contract/BContract.jsx @@ -1,33 +1,8 @@ -import { useContext, useEffect, useState } from 'react'; -import { AuthContext } from '../../context/AuthContext'; -import { db } from '../../../../firebase'; -import { getDoc, doc } from 'firebase/firestore'; -import ContractStatus from './ContractStatus'; -import GenerateContract from './GenerateContract' +import { useState } from 'react'; +import GenerateContract from './GenerateContract'; +import ContractList from './ContractList'; export default function BContract() { - const { currentUser } = useContext(AuthContext); - const [contracts, setContracts] = useState([]); // Initialize state to store contracts const [choice, setChoice] = useState('generate'); - - useEffect(() => { - const fetchDetails = async () => { - if (currentUser) { - const buyerRef = doc(db, 'buyers', currentUser.uid); - const docSnap = await getDoc(buyerRef); - - if (docSnap.exists()) { - const data = docSnap.data(); - const contractsData = data.contracts - ? Object.values(data.contracts) - : []; // Convert map to array - setContracts(contractsData); // Update state with contracts - } - } - }; - - fetchDetails(); // Fetch details on component mount - }, [currentUser]); - return (
@@ -50,32 +25,9 @@ export default function BContract() {
- - {choice==='generate' && ()} - {choice === 'status' && ( -
-
- Contract History : -
-
-
Date
-
Total
-
Status
-
Details
-
-
- {contracts.length > 0 ? ( - contracts.map((contract, index) => ( - - )) - ) : ( -

- No contracts found -

- )} -
-
- )} + + {choice === 'generate' && } + {choice === 'status' && } ); } diff --git a/client/src/components/Buyer/Contract/ContractStatus.jsx b/client/src/components/Buyer/Contract/ContractStatus.jsx index df9f586..51fd65c 100644 --- a/client/src/components/Buyer/Contract/ContractStatus.jsx +++ b/client/src/components/Buyer/Contract/ContractStatus.jsx @@ -1,13 +1,371 @@ -export default function ContractStatus({ contract, key }) { +import { useEffect, useState, useContext } from 'react'; +import PropTypes from 'prop-types'; +import Web3 from 'web3'; +import { doc, getDoc, setDoc } from 'firebase/firestore'; +import { AuthContext } from '../../context/AuthContext'; +import { db } from '../../../../firebase'; +import MultiAgriConnect from '../../../contractAddress/MultiAgriConnect.json'; +import { ToastContainer, toast } from 'react-toastify'; + +export default function ContractStatus({ contract }) { + const [showModal, setShowModal] = useState(false); + const { currentUser } = useContext(AuthContext); + const [localContract, setLocalContract] = useState(contract); + const [web3, setWeb3] = useState(null); + const [contractState, setContractState] = useState(null); + const [currentMetaUser, setCurrentMetaUser] = useState(''); + const [account, setAccount] = useState(''); + useEffect(() => { + const initWeb3 = async () => { + if (window.ethereum) { + const web3Instance = new Web3(window.ethereum); + + try { + // Request account access + await window.ethereum.request({ method: 'eth_requestAccounts' }); + setWeb3(web3Instance); + // Get accounts + const accounts = await web3Instance.eth.getAccounts(); + setAccount(accounts[0]); + + // Initialize the contract + const contractInstance = new web3Instance.eth.Contract( + MultiAgriConnect.abi, + MultiAgriConnect.networks[5777].address + ); + setContractState(contractInstance); + + // Set buyer details in formData + setCurrentMetaUser(accounts[0]); + } catch (error) { + toast.error('User denied account access', error.message); + } + } else { + toast.error('Please install MetaMask!'); + } + }; + + // Handle account changes and reload window + const handleAccountChange = (accounts) => { + if (accounts.length > 0) { + setAccount(accounts[0]); + window.location.reload(); // Reload the page when the account changes + } else { + toast.error('No account detected'); + } + }; + + // Initialize web3 + initWeb3(); + + // Listen for account changes + if (window.ethereum) { + window.ethereum.on('accountsChanged', handleAccountChange); + } + + // Cleanup event listener on unmount + return () => { + if (window.ethereum) { + window.ethereum.removeListener('accountsChanged', handleAccountChange); + } + }; + }, [currentUser, account]); + + // Function to handle date formatting + const formatDate = (createdAt) => { + if (createdAt instanceof Date) { + return createdAt.toLocaleDateString(); + } else if (createdAt && createdAt.toDate) { + return createdAt.toDate().toLocaleDateString(); + } else if (typeof createdAt === 'string') { + return new Date(createdAt).toLocaleDateString(); + } + return 'N/A'; + }; + + const handleSignContract = async () => { + if (!contractState || !account) { + toast.error('Web3 or contract not initialized'); + return; + } + + if (currentMetaUser !== contract.buyerId) { + toast.error('Log in through the appropriate MetaMask account'); + return; + } + + try { + // Sign the contract on the blockchain + await signContractOnBlockchain(contract.contractId); + + // Update the contract status in Firebase + await updateContractStatusInFirebase(contract.contractId); + + setLocalContract((prevContract) => ({ + ...prevContract, + status: 'Signed', + })); + + toast.success( + `Contract with ID ${contract.contractId} has been signed and updated in the database.` + ); + } catch (error) { + console.error('Error in handleSignContract:', error); + toast.error('Failed to sign contract. Please try again.'); + } + }; + + const signContractOnBlockchain = async (contractId) => { + try { + await contractState.methods + .signContract(contractId) + .send({ from: account }); + } catch (error) { + console.error('Error signing contract on blockchain:', error); + throw new Error('Failed to sign contract on blockchain'); + } + }; + + const updateContractStatusInFirebase = async (contractId) => { + try { + const buyerRef = doc(db, 'buyers', currentUser.uid); + const docSnap = await getDoc(buyerRef); + + if (!docSnap.exists()) { + throw new Error('Buyer profile not found'); + } + + const existingContracts = docSnap.data().contracts || []; + const updatedContracts = existingContracts.map((contractVar) => { + if (contractVar.contractId === contractId.toString()) { + return { ...contractVar, status: 'Signed' }; + } + return contractVar; + }); + + await setDoc(buyerRef, { contracts: updatedContracts }, { merge: true }); + } catch (error) { + console.error('Error updating contract status in Firebase:', error); + throw new Error('Failed to update contract status in database'); + } + }; + + const handleMakePayment = async () => { + if (!contractState || !account || !web3) { + toast.error('Web3 or contract not initialized'); + return; + } + + if (currentMetaUser !== contract.buyerId) { + toast.error('Log in through the appropriate MetaMask account'); + return; + } + + try { + // Make payment on the blockchain + await makePaymentOnBlockchain( + contract.contractId, + contract.installmentAmt, + contract.unit + ); + + // Update the contract status in Firebase + // Get updated payments made + const paymentsMade = await getPaymentsMade(contract.contractId); + + // Convert totalAmt and paymentsMade to Wei for accurate comparison + const totalAmtWei = web3.utils.toWei(contract.totalAmt, contract.unit); + const paymentsMadeWei = web3.utils.toWei( + paymentsMade.toString(), + 'ether' + ); + + // Check if total payments made are greater than or equal to the contract's total amount + try { + if ( + new web3.utils.BN(paymentsMadeWei).gte(new web3.utils.BN(totalAmtWei)) + ) { + // Update the contract status in Firebase + await updateContractPaymentInFirebase(contract.contractId); + + setLocalContract((prevContract) => ({ + ...prevContract, + status: 'Paid', + })); + } + } catch (error) { + console.log(error); + } + + toast.success( + `Payment of ${contract.installmentAmt} ${contract.unit} made for contract with ID ${contract.contractId}.` + ); + } catch (error) { + console.error('Error in handleMakePayment:', error); + // toast.error("Failed to make payment. Please try again."); + } + }; + + const getPaymentsMade = async (contractId) => { + try { + const payments = await contractState.methods + .paymentsMade(contractId, account) + .call(); + return web3.utils.fromWei(payments, 'ether'); + } catch (error) { + console.error('Error getting payments made:', error); + throw error; + } + }; + const makePaymentOnBlockchain = async (contractId, amount, unit) => { + try { + await contractState.methods.makePayment(contractId).send({ + from: account, + value: web3.utils.toWei(amount, unit), + }); + } catch (error) { + console.error('Error making payment on blockchain:', error); + throw new Error('Failed to make payment on blockchain'); + } + }; + + const updateContractPaymentInFirebase = async (contractId) => { + try { + const buyerRef = doc(db, 'buyers', currentUser.uid); + const docSnap = await getDoc(buyerRef); + + if (!docSnap.exists()) { + throw new Error('Buyer profile not found'); + } + + const existingContracts = docSnap.data().contracts || []; + const updatedContracts = existingContracts.map((contractVar) => { + if (contractVar.contractId === contractId.toString()) { + return { ...contractVar, status: 'Paid' }; // Or update to an appropriate status + } + return contractVar; + }); + + await setDoc(buyerRef, { contracts: updatedContracts }, { merge: true }); + } catch (error) { + console.error( + 'Error updating contract payment status in Firebase:', + error + ); + throw new Error('Failed to update contract payment status in database'); + } + }; + return ( -
- {/* Assuming `contract.id` is available */} -
{contract.date || 'N/A'}
-
{contract.total || 'N/A'}
-
{contract.status || 'N/A'}
-
- View Details +
+ {/* Contract Row */} +
+
{contract.contractId || 'N/A'}
+
{contract.crop || 'N/A'}
+
{contract.farmerName || 'N/A'}
+
+ {localContract.status || 'N/A'} +
+
+ {formatDate(contract.createdAt)} +
+
setShowModal(true)} + > + View Details +
+ + {/* Modal */} + {showModal && ( +
+
+
+
+ Contract ID: {contract.contractId || 'N/A'} +
+
+ Crop: {contract.crop || 'N/A'} +
+
+ Farmer Name: {contract.farmerName || 'N/A'} +
+
+ Farmer Id: {contract.farmerId || 'N/A'} +
+
+ Buyer Name: {contract.buyerName || 'N/A'} +
+
+ Buyer Id: {contract.buyerId || 'N/A'} +
+
+ Final Date: {contract.date || 'N/A'} +
+
+ Total Amount:{' '} + {contract.totalAmt + ' ' + contract.unit || 'N/A'} +
+
+ Installment Amount:{' '} + {contract.installmentAmt + ' ' + contract.unit || 'N/A'} +
+
+ Location: {contract.location || 'N/A'} +
+
+ Status: {localContract.status || 'N/A'} +
+
+ Created At: {formatDate(contract.createdAt)} +
+
+ + {localContract.status === 'Created' && ( + + )} + {localContract.status === 'Signed' && ( + + )} +
+
+
+ )} +
); } + +ContractStatus.propTypes = { + contract: PropTypes.shape({ + date: PropTypes.string.isRequired, + farmerName: PropTypes.string.isRequired, + farmerId: PropTypes.string.isRequired, + buyerName: PropTypes.string.isRequired, + buyerId: PropTypes.string.isRequired, + crop: PropTypes.string.isRequired, + totalAmt: PropTypes.string.isRequired, + installmentAmt: PropTypes.string.isRequired, + location: PropTypes.string.isRequired, + unit: PropTypes.string.isRequired, + contractId: PropTypes.string.isRequired, + status: PropTypes.string.isRequired, + createdAt: PropTypes.string.isRequired, + }), +}; diff --git a/client/src/components/Buyer/Contract/Download.jsx b/client/src/components/Buyer/Contract/Download.jsx index 839ec60..1572b3d 100644 --- a/client/src/components/Buyer/Contract/Download.jsx +++ b/client/src/components/Buyer/Contract/Download.jsx @@ -1,8 +1,95 @@ +import jsPDF from 'jspdf'; +import PropTypes from 'prop-types'; +function Download({ formData }) { + const generatePDF = () => { + const doc = new jsPDF(); + + doc.setFontSize(12); + doc.text('BILATERAL CONTRACT AGREEMENT', 50, 20); + doc.text(`THIS AGREEMENT made on this ${formData.date}`, 20, 40); + doc.text( + `between ${formData.buyerName} with id ${formData.buyerId} (hereafter described as the Buyer/Investor)`, + 20, + 50 + ); + + doc.text( + `AND ${formData.farmerName} with id ${formData.farmerId} (hereafter described as the Farmer).`, + 20, + 60 + ); + + doc.text( + `WHEREAS the Buyer/Investor is interested in the promotion of quality ${formData.crop}`, + 20, + 70 + ); + doc.text(`in ${formData.location}.`, 20, 80); + doc.text( + `AND WHEREAS the Farmer/Cooperative/Association requires assistance in growing ${formData.crop}`, + 20, + 90 + ); + + doc.text( + `NOW THEREFORE THE Buyer and the Farmer/Cooperative/Association agree as follows:`, + 20, + 120 + ); + + doc.text(`1. The Buyer agrees to provide the following:`, 20, 130); + doc.text(`a) Technical extension and research services.`, 30, 140); + doc.text( + `b) All inputs required during the growing season within loan limits.`, + 30, + 150 + ); + doc.text( + `c) An indicative price for the season's crop is ${formData.totalAmt}.`, + 30, + 160 + ); + doc.text( + `d) Pay an installment amount of ${formData.installmentAmt}.`, + 30, + 170 + ); + doc.text( + `2. The Farmer agrees to grow ${formData.crop} as per the Buyer's advice and not sell to another buyer.`, + 20, + 180 + ); + + // Save or open the generated PDF + doc.save('Contract_Agreement.pdf'); + }; -export default function Download() { return ( -
- +
+

Download Contract PDF here

+
- ) + ); } + +Download.propTypes = { + formData: PropTypes.shape({ + date: PropTypes.string.isRequired, + farmerName: PropTypes.string.isRequired, + farmerId: PropTypes.string.isRequired, + buyerName: PropTypes.string.isRequired, + buyerId: PropTypes.string.isRequired, + crop: PropTypes.string.isRequired, + totalAmt: PropTypes.number.isRequired, + installmentAmt: PropTypes.number.isRequired, + location: PropTypes.string.isRequired, + }), +}; + +export default Download; diff --git a/client/src/components/Buyer/Contract/GenerateContract.jsx b/client/src/components/Buyer/Contract/GenerateContract.jsx index eea43b4..6fb29dd 100644 --- a/client/src/components/Buyer/Contract/GenerateContract.jsx +++ b/client/src/components/Buyer/Contract/GenerateContract.jsx @@ -1,18 +1,54 @@ -import { useState } from "react"; -import { MdDone } from "react-icons/md"; -import PdfDetails from "./PdfDetails"; -import Metamask from "./Metamask"; -import Download from "./Download"; +import { useState } from 'react'; +import { MdDone } from 'react-icons/md'; +import PdfDetails from './PdfDetails'; +import Metamask from './Metamask'; +import Download from './Download'; export default function GenerateContract() { - - const steps = ["Fill Details", "Connect to Metamask", "Download Pdf"]; + const [formData, setFormData] = useState({ + date: '', + farmerId: '', + buyerId: '', + buyerName: '', + farmerName: '', + crop: '', + totalAmt: 0, + installmentAmt: 0, + location: '', + unit: 'wei', + }); + const [validationErrors, setValidationErrors] = useState({}); + const validateForm = () => { + const errors = {}; + if (!formData.date) errors.date = 'Date is required'; + if (!formData.farmerId) errors.farmerId = 'Farmer ID is required'; + if (!formData.farmerName) errors.farmerName = 'Farmer name is required'; + if (!formData.crop) errors.crop = 'Crop is required'; + if (formData.totalAmt <= 0) + errors.totalAmt = 'Total amount must be greater than 0'; + if (formData.installmentAmt <= 0) + errors.installmentAmt = 'Installment amount must be greater than 0'; + if (!formData.location) errors.location = 'Location is required'; + return errors; + }; + + const handleChange = (e) => { + setFormData({ ...formData, [e.target.name]: e.target.value }); + }; + + const steps = ['Fill Details', 'Sign via Metamask', 'Download Pdf']; const [currentStep, setCurrentStep] = useState(1); -// const [complete, setComplete] = useState(false); const handleNext = () => { - if (currentStep === steps.length) { - // setComplete(true); - } else { + if (currentStep === 1) { + const errors = validateForm(); + if (Object.keys(errors).length === 0) { + setValidationErrors({}); + setCurrentStep((prev) => prev + 1); + } else { + setValidationErrors(errors); + alert('fill all details'); + } + } else if (currentStep < steps.length) { setCurrentStep((prev) => prev + 1); } }; @@ -24,65 +60,60 @@ export default function GenerateContract() { }; return ( <> -
-
- {steps?.map((step, i) => ( -
+
+
+ {steps?.map((step, i) => (
- {i + 1 < currentStep ? ( - - ) : ( - i + 1 - )} -
-

- {step} -

- {i !== 0 && (
- )} -
- ))} -
- {steps[currentStep-1] === 'Fill Details' && ( - <> - - - )} - {currentStep === 'Connect to Metamask' && ( - <> - - - )} - {currentStep === 'Download Pdf' && ( - <> - - - )} - - + > + {i + 1 < currentStep ? : i + 1} +
+

+ {step} +

+ {i !== 0 && ( +
+ )} +
+ ))} +
+ {steps[currentStep - 1] === 'Fill Details' && ( + <> + + + )} + {steps[currentStep - 1] === 'Sign via Metamask' && ( + <> + + + )} + {steps[currentStep - 1] === 'Download Pdf' && ( + <> + + + )} +
- -
+
); } diff --git a/client/src/components/Buyer/Contract/Metamask.jsx b/client/src/components/Buyer/Contract/Metamask.jsx index 973a8a9..1ed6355 100644 --- a/client/src/components/Buyer/Contract/Metamask.jsx +++ b/client/src/components/Buyer/Contract/Metamask.jsx @@ -1,7 +1,225 @@ -export default function Metamask() { +import { useState, useEffect, useContext } from 'react'; +import Web3 from 'web3'; +import MultiAgriConnect from '../../../contractAddress/MultiAgriConnect.json'; +import PropTypes from 'prop-types'; +import { doc, getDoc, setDoc } from 'firebase/firestore'; +import { AuthContext } from '../../context/AuthContext'; +import { db } from '../../../../firebase'; +import { ToastContainer, toast } from 'react-toastify'; + +export default function Metamask({ formData }) { + const { currentUser } = useContext(AuthContext); + const [web3, setWeb3] = useState(null); + const [contract, setContract] = useState(null); + const [account, setAccount] = useState(''); + const [currentId, setCurrentId] = useState(0); + + useEffect(() => { + const initWeb3 = async () => { + if (window.ethereum) { + const web3Instance = new Web3(window.ethereum); + try { + // Request account access + await window.ethereum.request({ method: 'eth_requestAccounts' }); + setWeb3(web3Instance); + + // Get accounts + const accounts = await web3Instance.eth.getAccounts(); + setAccount(accounts[0]); + + // Initialize the contract + const contractInstance = new web3Instance.eth.Contract( + MultiAgriConnect.abi, + MultiAgriConnect.networks[5777].address + ); + setContract(contractInstance); + + // Set buyer details in formData + formData.buyerId = accounts[0]; + } catch (error) { + toast.error('User denied account access', error.message); + } + } else { + toast.error('Please install MetaMask!'); + } + + // Fetch user data from Firestore if available + if (currentUser) { + const buyerRef = doc(db, 'buyers', currentUser.uid); + const docSnap = await getDoc(buyerRef); + const data = docSnap.data(); + if (docSnap.exists()) { + formData.buyerName = + data.profile.displayName + ' ' + data.profile.lname; + } + } + }; + + // Handle account changes and reload window + const handleAccountChange = (accounts) => { + if (accounts.length > 0) { + setAccount(accounts[0]); + window.location.reload(); // Reload the page when the account changes + } else { + toast.error('No account detected'); + } + }; + + // Initialize web3 + initWeb3(); + + // Listen for account changes + if (window.ethereum) { + window.ethereum.on('accountsChanged', handleAccountChange); + } + + // Cleanup event listener on unmount + return () => { + if (window.ethereum) { + window.ethereum.removeListener('accountsChanged', handleAccountChange); + } + }; + }, [currentUser, formData, account]); + + const handleCreateContract = async () => { + try { + const result = await contract.methods + .createContract( + formData.farmerId, + formData.crop, + web3.utils.toWei(formData.totalAmt.toString(), formData.unit), + web3.utils.toWei(formData.installmentAmt.toString(), formData.unit) + ) + .send({ from: account }); + + const newContractId = + result.events.ContractCreated.returnValues.contractId; + setCurrentId(newContractId); + console.log(result.events.ContractCreated.returnValues); + + const buyerRef = doc(db, 'buyers', currentUser.uid); + const docSnap = await getDoc(buyerRef); + + if (!docSnap.exists()) { + toast.error('Kindly fill in your details at profile page'); + return; + } + + const existingContracts = docSnap.data().contracts || []; + const newContract = { + contractId: newContractId.toString(), + farmerId: formData.farmerId || 'Unknown', + farmerName: formData.farmerName || 'Unknown', + crop: formData.crop || 'Unknown', + totalAmt: formData.totalAmt.toString() || '0', + installmentAmt: formData.installmentAmt.toString() || '0', + unit: formData.unit || 'ether', + status: 'Created', + date: formData.date, + buyerName: formData.buyerName, + buyerId: formData.buyerId, + location: formData.location, + createdAt: new Date().toISOString(), + }; + + const updatedContracts = [...existingContracts, newContract]; + + // Update Firestore with the new/updated contracts array + await setDoc( + buyerRef, + { + contracts: updatedContracts, + }, + { merge: true } + ); + + toast.success(`Contract created with ID: ${newContractId}`); + } catch (error) { + console.error('Error creating contract:', error); + toast.error('Failed to create contract. Please try again.'); + } + }; + // const details = await contract.methods.getContractDetails(selectedContractId).call(); // contractDetails: details.contractDetails, + + const handleSignContract = async () => { + try { + // Sign the contract on the blockchain + await contract.methods.signContract(currentId).send({ from: account }); + + // Update the contract status in Firebase + const buyerRef = doc(db, 'buyers', currentUser.uid); + const docSnap = await getDoc(buyerRef); + + if (!docSnap.exists()) { + toast.error('Buyer profile not found'); + return; + } + + const existingContracts = docSnap.data().contracts || []; + const updatedContracts = existingContracts.map((contract) => { + if (contract.contractId === currentId.toString()) { + return { ...contract, status: 'Signed' }; + } + return contract; + }); + + // Update Firestore with the modified contracts array + await setDoc( + buyerRef, + { + contracts: updatedContracts, + }, + { merge: true } + ); + + toast.success( + `Contract with ID ${currentId} has been signed and updated in the database.` + ); + } catch (error) { + console.error('Error signing contract:', error); + toast.error('Failed to sign contract. Please try again.'); + } + }; + return ( -
- +
+ Connected Metamask Address : {account} +
+

Create Contract

+ +
+
+

Sign Contract

+ {/* Are you sure you want to sign the contract with ID {currentId}? */} + Are you sure you want to sign the contract ? + +
+
- ) + ); } + +Metamask.propTypes = { + formData: PropTypes.shape({ + date: PropTypes.string.isRequired, + farmerName: PropTypes.string.isRequired, + farmerId: PropTypes.string.isRequired, + buyerName: PropTypes.string.isRequired, + buyerId: PropTypes.string.isRequired, + crop: PropTypes.string.isRequired, + totalAmt: PropTypes.number.isRequired, + installmentAmt: PropTypes.number.isRequired, + location: PropTypes.string.isRequired, + unit: PropTypes.string.isRequired, + }), +}; diff --git a/client/src/components/Buyer/Contract/Pdf.jsx b/client/src/components/Buyer/Contract/Pdf.jsx deleted file mode 100644 index e2a64d5..0000000 --- a/client/src/components/Buyer/Contract/Pdf.jsx +++ /dev/null @@ -1,81 +0,0 @@ -import { useState } from 'react'; -import jsPDF from 'jspdf'; -// import 'jspdf-autotable'; // Optional: if you want to use table features - -const Pdf = () => { - const [contractDetails, setContractDetails] = useState({ - buyerName: '', - sellerName: '', - contractDate: '', - details: '' - }); - - const handleChange = (e) => { - const { name, value } = e.target; - setContractDetails({ ...contractDetails, [name]: value }); - }; - - const generatePDF = () => { - const doc = new jsPDF(); - - doc.setFontSize(16); - doc.text('Contract Details', 20, 20); - - doc.setFontSize(12); - doc.text(`Buyer Name: ${contractDetails.buyerName}`, 20, 30); - doc.text(`Seller Name: ${contractDetails.sellerName}`, 20, 40); - doc.text(`Contract Date: ${contractDetails.contractDate}`, 20, 50); - doc.text('Details:', 20, 60); - doc.text(contractDetails.details, 20, 70); - - doc.save('contract.pdf'); - }; - - return ( -
-

Contract Form

-
-
- - -
-
- - -
-
- - -
-
- -