From 26f89ec298ab7b2be8276b5ec749e257e736a11e Mon Sep 17 00:00:00 2001 From: sharayu Date: Sun, 11 Aug 2024 11:46:27 +0530 Subject: [PATCH] Integration with blockchain --- next-app/.env | 3 + next-app/blockend/build/invoice-factory.json | 161 ++++++++++++- next-app/blockend/build/invoice.json | 209 ++++++++++++++++- next-app/blockend/contracts/Invoice.sol | 158 +++++++++++-- next-app/blockend/interact.js | 2 +- next-app/components/Homepage/Homepage.tsx | 18 +- next-app/components/Invest/InvoiceInfo.tsx | 45 +++- next-app/components/Invest/NFTDashbaord.tsx | 154 ------------- .../components/Portfolio/ClientDashboard.tsx | 112 ++++++--- next-app/interfaces/invoice.ts | 8 +- next-app/models/UploadedInvoice.js | 4 + next-app/pages/api/uploaded_invoices.ts | 45 +++- next-app/pages/company.tsx | 7 +- next-app/pages/factor.tsx | 217 ++++++++++++++---- next-app/pages/invest.tsx | 209 +++++++++-------- next-app/pages/portfolio.tsx | 5 +- next-app/tsconfig.json | 5 +- 17 files changed, 987 insertions(+), 375 deletions(-) delete mode 100644 next-app/components/Invest/NFTDashbaord.tsx diff --git a/next-app/.env b/next-app/.env index d58bce1..5ab00ac 100644 --- a/next-app/.env +++ b/next-app/.env @@ -20,3 +20,6 @@ VERCEL_URL=https://altinvest.vercel.app NEXTAUTH_SECRET=ea965be99a62236097ffe6262f5c7993 MORALIS_API_KEY=GR7Ec67gro7mwF5JWmAslOCJxG4E288DDhV8DfgzoYuw1VeIxVptNzZNRJV0zTXN JWT_SECRET=7D722A1A23FF39883312AD2134A73EC61CFD1557395F63BB02450D5647F80112 + +LISK_TESTNET_ADDRESS=0xD0bC1E61B6130FFB3681BdA3F42fB8514e21D150 +OPTIMISM_SEPOLIA_FINAL_ADDRESS=0xa95E81dEE8E4cD3C37130e2396000DF6a69Ee723 diff --git a/next-app/blockend/build/invoice-factory.json b/next-app/blockend/build/invoice-factory.json index fc98955..ab208a2 100644 --- a/next-app/blockend/build/invoice-factory.json +++ b/next-app/blockend/build/invoice-factory.json @@ -1,4 +1,67 @@ [ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "activeInvoices", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_factor", + "type": "address" + } + ], + "name": "addFactor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "contractAddress", + "type": "address" + } + ], + "name": "addSignedContract", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "admin", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -8,7 +71,13 @@ } ], "name": "createID", - "outputs": [], + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], "stateMutability": "nonpayable", "type": "function" }, @@ -31,6 +100,25 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "factors", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "getDeployedContracts", @@ -43,5 +131,76 @@ ], "stateMutability": "view", "type": "function" + }, + { + "inputs": [], + "name": "getSignedContracts", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "contractAddress", + "type": "address" + } + ], + "name": "markinvoiceInactive", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_factor", + "type": "address" + } + ], + "name": "removeFactor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "contractAddress", + "type": "address" + } + ], + "name": "removeSignedContract", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "signedContracts", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" } ] \ No newline at end of file diff --git a/next-app/blockend/build/invoice.json b/next-app/blockend/build/invoice.json index 504501a..3cad9de 100644 --- a/next-app/blockend/build/invoice.json +++ b/next-app/blockend/build/invoice.json @@ -10,6 +10,11 @@ "internalType": "address", "name": "_seller", "type": "address" + }, + { + "internalType": "address", + "name": "_factoryAddress", + "type": "address" } ], "stateMutability": "nonpayable", @@ -197,6 +202,11 @@ "name": "_totalUnits", "type": "uint256" }, + { + "internalType": "uint256", + "name": "_tenure", + "type": "uint256" + }, { "internalType": "string", "name": "_agreementHash", @@ -206,6 +216,21 @@ "internalType": "uint256", "name": "_fees", "type": "uint256" + }, + { + "internalType": "string", + "name": "_sellerName", + "type": "string" + }, + { + "internalType": "string", + "name": "_buyerName", + "type": "string" + }, + { + "internalType": "uint256", + "name": "_xirr", + "type": "uint256" } ], "name": "approveInvoice", @@ -213,6 +238,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "buyerName", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "declineInvoice", @@ -235,7 +273,130 @@ }, { "inputs": [], - "name": "fees", + "name": "factoryAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getAllInvestors", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "investor_address", + "type": "address" + }, + { + "internalType": "uint256", + "name": "invested_amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "repayment_amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "total_units", + "type": "uint256" + } + ], + "internalType": "struct Invoice.Investor[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getInvoiceInfo", + "outputs": [ + { + "components": [ + { + "internalType": "string", + "name": "invoiceHash", + "type": "string" + }, + { + "internalType": "string", + "name": "agreementHash", + "type": "string" + }, + { + "internalType": "string", + "name": "sellerName", + "type": "string" + }, + { + "internalType": "string", + "name": "buyerName", + "type": "string" + }, + { + "internalType": "uint256", + "name": "xirr", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "totalInvoiceAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "fees", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountPerUnit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "repaymentPerUnit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "totalUnits", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "purchasedUnits", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "tenure", + "type": "uint256" + } + ], + "internalType": "struct Invoice.InvoiceInfo", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getPayableAmount", "outputs": [ { "internalType": "uint256", @@ -365,6 +526,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "sellerName", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "signAgreement", @@ -372,6 +546,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "tenure", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "totalInvoiceAmount", @@ -397,5 +584,25 @@ ], "stateMutability": "view", "type": "function" + }, + { + "inputs": [], + "name": "triggerPaymentOnCampaignClose", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "xirr", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" } ] \ No newline at end of file diff --git a/next-app/blockend/contracts/Invoice.sol b/next-app/blockend/contracts/Invoice.sol index 58b3d5d..9b7fe21 100644 --- a/next-app/blockend/contracts/Invoice.sol +++ b/next-app/blockend/contracts/Invoice.sol @@ -1,23 +1,76 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; - -contract InvoiceFactory{ +contract InvoiceFactory { address[] public deployedInvoices; + address[] public signedContracts; + mapping(address => bool) public activeInvoices; + mapping(address=>bool) public factors; + address public admin; + + constructor(){ + factors[msg.sender] = true; + admin = msg.sender; + } - function createID(string calldata ipfsHash) public{ - - address newID = address(new Invoice(ipfsHash, msg.sender)); + function createID(string calldata ipfsHash) public returns (address) { + address newID = address(new Invoice(ipfsHash, msg.sender, address(this))); deployedInvoices.push(newID); + activeInvoices[newID] = true; + return newID; + } + + modifier onlyValidInvoice() { + require(activeInvoices[msg.sender], "Only valid Invoice contracts can call this function."); + _; + } + + modifier onlyAdmin(){ + require(msg.sender==admin, "Only Admin can call this function"); + _; + } + + function addFactor(address _factor) public onlyAdmin{ + factors[_factor]=true; + } + function removeFactor(address _factor) public onlyAdmin{ + factors[_factor] = false; } - function getDeployedContracts() public view returns(address[] memory){ + function getDeployedContracts() public view returns (address[] memory) { return deployedInvoices; } + + function getSignedContracts() public view returns (address[] memory) { + return signedContracts; + } + + function addSignedContract(address contractAddress) external onlyValidInvoice { + require(activeInvoices[contractAddress], "Only the invoice contract can call this function."); + signedContracts.push(contractAddress); + } + + function removeSignedContract(address contractAddress) external onlyValidInvoice{ + require(activeInvoices[contractAddress], "Only the invoice contract can call this function."); + + for (uint i = 0; i < signedContracts.length; i++) { + if (signedContracts[i] == contractAddress) { + signedContracts[i] = signedContracts[signedContracts.length - 1]; + signedContracts.pop(); + break; + } + } + } + + function markinvoiceInactive(address contractAddress) external onlyValidInvoice { + require(activeInvoices[contractAddress], "Only the invoice contract can call this function."); + activeInvoices[contractAddress] = false; + } } + contract Invoice{ @@ -27,12 +80,17 @@ contract Invoice{ address public factor; address public recourse_payer; + string public sellerName; + string public buyerName; + uint256 public xirr; + uint256 public totalInvoiceAmount; - uint256 public fees; + uint256 private fees; uint256 public amountPerUnit; uint256 public repaymentPerUnit; uint256 public totalUnits; uint256 public purchasedUnits = 0; + uint256 public tenure; uint256 private expected_repayment_date; uint256 private expected_payout_date; @@ -40,7 +98,11 @@ contract Invoice{ uint256 private totalInvestedAmount = 0; uint256 private paidAmount; - enum Status { PROCESSING, APPROVED, DECLINED, PURCHASED, COMPLETED} + address public factoryAddress; + + uint256 private totalPayableAmount; + + enum Status { PROCESSING, APPROVED, FACTOR_APPROVED, DECLINED, PURCHASED, COMPLETED} Status status; struct Investor{ @@ -50,6 +112,23 @@ contract Invoice{ uint256 total_units; } + struct InvoiceInfo{ + string invoiceHash; + string agreementHash; + + string sellerName; + string buyerName; + uint256 xirr; + + uint256 totalInvoiceAmount; + uint256 fees; + uint256 amountPerUnit; + uint256 repaymentPerUnit; + uint256 totalUnits; + uint256 purchasedUnits; + uint256 tenure; + } + Investor[] public investors; @@ -59,6 +138,7 @@ contract Invoice{ } modifier _onlyFactor{ + require(InvoiceFactory(factoryAddress).factors(msg.sender), "Only factor can call this function."); _; } @@ -91,11 +171,13 @@ contract Invoice{ constructor( string memory _ipfsHash, - address _seller + address _seller, + address _factoryAddress ){ seller = _seller; recourse_payer = seller; invoiceHash = _ipfsHash; + factoryAddress = _factoryAddress; } @@ -104,8 +186,13 @@ contract Invoice{ uint256 _amountPerunit, uint256 _repaymentPerUnit, uint256 _totalUnits, + uint256 _tenure, string memory _agreementHash, - uint256 _fees) _onlyFactor external{ + uint256 _fees, + string memory _sellerName, + string memory _buyerName, + uint256 _xirr + ) _onlyFactor external{ // require(factor == msg.sender, "Only factor can approve invoice"); assert(fees<_totalInvoiceAmount); @@ -117,12 +204,19 @@ contract Invoice{ amountPerUnit = _amountPerunit * (10**9); repaymentPerUnit = _repaymentPerUnit * (10**9); totalUnits = _totalUnits; - status = Status.APPROVED; + totalPayableAmount = (totalUnits * repaymentPerUnit) + fees; + + status = Status.FACTOR_APPROVED; + tenure = _tenure; + sellerName = _sellerName; + buyerName = _buyerName; + xirr = _xirr; + emit InvoiceApproved(factor); } - function declineInvoice() external{ + function declineInvoice() external _onlyFactor{ agreementSigned = false; status=Status.DECLINED; } @@ -132,8 +226,10 @@ contract Invoice{ //Think on how to get it signed by Payer and Seller //Request should go to seller's and payer's dashboard to sign - but apart from that //It should be signed just by the seller - perhaps. Confirm on this with AB - require(status == Status.APPROVED, "Aggrement is not approved by factor."); + require(status == Status.FACTOR_APPROVED, "Aggrement is not approved by factor."); agreementSigned = true; + status = Status.APPROVED; + InvoiceFactory(factoryAddress).addSignedContract(address(this)); emit InvoiceSigned(msg.sender); } @@ -166,6 +262,7 @@ contract Invoice{ if(purchasedUnits==totalUnits){ transferMoneyToSeller(); + InvoiceFactory(factoryAddress).removeSignedContract(address(this)); } } @@ -173,6 +270,8 @@ contract Invoice{ (bool success, ) = payable(factor).call{value: address(this).balance}(""); require(success, "Transfer to factor failed"); status= Status.COMPLETED; + InvoiceFactory(factoryAddress).markinvoiceInactive(address(this)); + emit FeesTransferToFactor(factor, address(this).balance); } @@ -190,9 +289,13 @@ contract Invoice{ // emit MoneyTransferToInvestor(investors[0], paidAmount-fees); } + function getPayableAmount() public view returns (uint) { + return totalUnits * repaymentPerUnit + fees; + } + function payInvoiceAmount() payable external _invoiceApproved{ require(msg.value == ((totalUnits * repaymentPerUnit) + fees) - , "Amount must be equal to the repay amount"); + , "Amount must be equal to the total payable amount."); paidAmount +=msg.value; emit InvoicePaid(msg.value, msg.sender); @@ -200,5 +303,32 @@ contract Invoice{ transferMoneyToInvestor(); transferFeeToFactor(); } + + function getAllInvestors() public view returns (Investor[] memory){ + return investors; + } + + function triggerPaymentOnCampaignClose() public _onlyFactor{ + transferMoneyToSeller(); + } + + function getInvoiceInfo() public view returns(InvoiceInfo memory){ + InvoiceInfo memory invoiceInfo; + invoiceInfo.agreementHash = agreementHash; + invoiceInfo.amountPerUnit = amountPerUnit; + invoiceInfo.buyerName = buyerName; + invoiceInfo.fees = fees; + invoiceInfo.invoiceHash = invoiceHash; + invoiceInfo.sellerName = sellerName; + invoiceInfo.xirr = xirr; + invoiceInfo.purchasedUnits = purchasedUnits; + invoiceInfo.repaymentPerUnit = repaymentPerUnit; + invoiceInfo.tenure = tenure; + invoiceInfo.totalInvoiceAmount = totalInvoiceAmount; + invoiceInfo.totalUnits = totalUnits; + + return invoiceInfo; + + } } diff --git a/next-app/blockend/interact.js b/next-app/blockend/interact.js index 16122c8..eca7b10 100644 --- a/next-app/blockend/interact.js +++ b/next-app/blockend/interact.js @@ -7,7 +7,7 @@ import web3 from "./web3"; // Contract address of the InvoiceFactory contract -const factoryAddress = '0x698e9d8C3Dc156bBB4bE95e8300b3C30212845E0'; +const factoryAddress = '0xa95E81dEE8E4cD3C37130e2396000DF6a69Ee723'; // Setup provider (you can use Infura, Alchemy, or any other provider) // const provider = new ethers.providers.JsonRpcProvider('https://mainnet.infura.io/v3/09b5a24c345742ea95c3cf2636900c17'); diff --git a/next-app/components/Homepage/Homepage.tsx b/next-app/components/Homepage/Homepage.tsx index dfaafaa..c569555 100644 --- a/next-app/components/Homepage/Homepage.tsx +++ b/next-app/components/Homepage/Homepage.tsx @@ -36,12 +36,12 @@ function Homepage({}: Props) { const [uploadClicked, setUploadClicked] = useState(false) const inputFile = useRef(null) const [file, setFile] = useState() - const [email, setEmail] = useState("") + const [email, setEmail] = useState('') const [isSubmitting, setIsSubmitting] = useState(false) - const [alertStatus, setAlertStatus] = useState("") - const [ipfsHash, setIPFSHash] = useState(""); + const [alertStatus, setAlertStatus] = useState('') + const [ipfsHash, setIPFSHash] = useState(null); const [isFileuploaded, setIsFileUploaded] = useState(false); - const [deployedContractAddress, setDeployedContractAddress] = useState(''); + const [deployedContractAddress, setDeployedContractAddress] = useState(null); const pinataConfig = { root: "https://api.pinata.cloud", @@ -202,18 +202,22 @@ function Homepage({}: Props) { useEffect(() => { - saveToMongoDB(); + if(deployedContractAddress){ + saveToMongoDB(); + } },[deployedContractAddress]); + const handleSubmit = async (e: any) => { e.preventDefault() //await blockchain transaction - once it returns the IDof the Smart contract then proceed with the MongoDB storage try{ - const res = await factoryContract.methods.createID(ipfsHash).send({from: address}); + const res = await factoryContract.methods.createID(ipfsHash).send({from: address}).then(() => + {getInvoices();}) console.log("Invoice Uploaded: ", res); - getInvoices(); + } catch{ diff --git a/next-app/components/Invest/InvoiceInfo.tsx b/next-app/components/Invest/InvoiceInfo.tsx index 9936f76..9710bb9 100644 --- a/next-app/components/Invest/InvoiceInfo.tsx +++ b/next-app/components/Invest/InvoiceInfo.tsx @@ -1,3 +1,4 @@ +import web3 from "@/blockend/web3"; import { Stack, Center, @@ -9,8 +10,9 @@ import { Container, Divider, } from "@chakra-ui/react" -import React from "react" +import React, {useEffect, useState} from "react" import { LiaIndustrySolid } from "react-icons/lia" +import invoiceAbi from '@/blockend/build/invoice.json' type Props = { seller: string; @@ -21,12 +23,35 @@ type Props = { unitsInvested: number; totalUnits: number; tenure: number; + contractAddress: any; } + const InvoiceInfo = (props:Props) => { + + const [invoiceDetails, setInvoiceDetails] =useState(null); + + + //write a function to get all of these + useEffect(() => { + const fetchInvoiceInfo = async () => { + const invoiceContract = new web3.eth.Contract(invoiceAbi, props.contractAddress); + try { + const res = await invoiceContract.methods.getInvoiceInfo().call(); + setInvoiceDetails(res || {}); // Ensure the state is always an object + console.log("ID: ", res); + } catch (error) { + console.error("Error fetching invoice info:", error); + } + }; + + fetchInvoiceInfo(); + }, [props.contractAddress]); + + return ( - - + diff --git a/next-app/components/Invest/NFTDashbaord.tsx b/next-app/components/Invest/NFTDashbaord.tsx deleted file mode 100644 index 6c826d1..0000000 --- a/next-app/components/Invest/NFTDashbaord.tsx +++ /dev/null @@ -1,154 +0,0 @@ -import React from 'react' -import { - Button, - Text, - Flex, - Heading, - Stack, - Image, - Input, - useNumberInput, - HStack, - Center, -} from "@chakra-ui/react" - -function HookUsage() { - const { getInputProps, getIncrementButtonProps, getDecrementButtonProps } = - useNumberInput({ - step: 1, - defaultValue: 0, - min: 0, - max: 10, - precision: 1, - }) - - const inc = getIncrementButtonProps() - const dec = getDecrementButtonProps() - const input = getInputProps() - - return ( - <> -
- - - No. of Units - - - - - - - -
- - ) -} - -function NFTDashbaord() { - return ( - - - - - Invoice Amount - $100 - - - XIRR - 17% - - - Recourse On - Seller - - - - Due Date - Dec 24, 2024 - - - - - Seller - Apple - - - Buyer - Imagine Store - - - - Risk - Low - - - View Contract - Link - - - - - {/* - The 2023 Tesla Model 3 is the cheapest Tesla car currently - offered. The base rear-wheel drive (RWD) trim has an official - starting price of $40,240. The Model 3 Long Range is a tad more - expensive at $47,240. The most expensive Model 3 is the - Performance model, which costs a minimum of $53,240. - */} - - - - - - - - - - - - - Unit Value - $100 - - - Invested Amount - $200 - - - Repayment Date - Septemeber 24, 2024 - - - Repayment Value - $220 - -
- -
-
-
- ) -} - -export default NFTDashbaord \ No newline at end of file diff --git a/next-app/components/Portfolio/ClientDashboard.tsx b/next-app/components/Portfolio/ClientDashboard.tsx index d2d0b31..13dddd2 100644 --- a/next-app/components/Portfolio/ClientDashboard.tsx +++ b/next-app/components/Portfolio/ClientDashboard.tsx @@ -8,11 +8,74 @@ import { Thead, Tr, } from "@chakra-ui/react" -import React from "react" +import React, {useEffect, useState} from "react" +import invoiceAbi from '@/blockend/build/invoice.json' +import web3 from "@/blockend/web3"; + +type Props = { +} + +const ClientDashboard = (sellerAddress:any) => { + const [invoices, setInvoices] = useState([]); + + const address = sellerAddress['address']; + //get the agreements for the address == deployed.contract. seller + //How will you find that - think + //In activeInvoices check if anyseller has this or seller's -> invoice + //Do this in MongoDB => seller - invoice - active - signed + //This is while apporiving invoice + //Keep this track - same for investors -> when the invoice is paid. + //Keep track of fees for factor + + useEffect(() => { + const fetchInvoices = async () => { + const response = await fetch(`api/uploaded_invoices?sellerAddress=${address}`).then((res) => + res.json() + ) + setInvoices(response) + } + + fetchInvoices() + }, []) + + + const handleSignAgreement =async(invoice) => { + + const invoiceContract = new web3.eth.Contract(invoiceAbi, invoice.contractAddress); + + await invoiceContract.methods.signAgreement().send({from:address}).then((res)=>{ + if(res){ + updateToMongoDB(invoice); + } + } + ) + + } + + const updateToMongoDB = async(invoice: UploadedInvoice) =>{ + let approvedInvoice = invoice + + approvedInvoice.signedBySeller = true + + const updatedInvoice = await fetch("api/uploaded_invoices", { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(approvedInvoice), + }) + + const result = await updatedInvoice.json(); + + if (result.acknowledged) { + alert("Invoice updated successfully to MongoDb") + } else { + alert("There was an error updating the invoice") + } + + } -type Props = {} -const ClientDashboard = (props: Props) => { return ( <>
@@ -22,51 +85,28 @@ const ClientDashboard = (props: Props) => { - + - - + - - - - - - - - - - - - - + {invoices.map((invoice:UploadedInvoice)=>( + + + + - - - - - - - - + + ))}
Transaction IDContract Address Due DateInvested Amount/Invoice AmountTotal UnitsInvoice URL Sign Agreement Pay Invoice
12343444412/12/2024100000/13000025 - - - -
12343444412/12/2024100000/13000025
{invoice.contractAddress}{invoice.date_added}{invoice.fileURL} - - - -
12343444412/12/2024100000/13000025 - +
diff --git a/next-app/interfaces/invoice.ts b/next-app/interfaces/invoice.ts index 09f08c0..c81cf25 100644 --- a/next-app/interfaces/invoice.ts +++ b/next-app/interfaces/invoice.ts @@ -1,13 +1,13 @@ interface UploadedInvoice { _id: string email: string - walletAddress: string + contractAddress: string + sellerAddress: string date_added: string fileURL: string - fileName: string approved: boolean - declineReason: string - verifierAddress: any + declineReason: string, active: boolean archived: boolean + signedBySeller: boolean } diff --git a/next-app/models/UploadedInvoice.js b/next-app/models/UploadedInvoice.js index 599af07..98e0cea 100644 --- a/next-app/models/UploadedInvoice.js +++ b/next-app/models/UploadedInvoice.js @@ -52,6 +52,10 @@ const InvoiceSchema = new mongoose.Schema({ verificationDate: { type: Date, }, + signedBySeller:{ + type: Boolean, + default:false + } //Make a different database for approved invoices - With everything that an investor needs to know, like due date, amount, risk, company, rate of discount }) diff --git a/next-app/pages/api/uploaded_invoices.ts b/next-app/pages/api/uploaded_invoices.ts index f4cc28c..93c4751 100644 --- a/next-app/pages/api/uploaded_invoices.ts +++ b/next-app/pages/api/uploaded_invoices.ts @@ -12,7 +12,7 @@ export default async function handler( req: NextApiRequest, res: NextApiResponse ) { - const { method } = req + const { method, query} = req const connection = await connectToDatabase() var collection @@ -28,8 +28,40 @@ export default async function handler( switch (method) { case "GET": try { - const results = await collection.find({}).limit(10).toArray() - res.status(200).json(results) + + const results = await collection.find({}).limit(10).toArray() + + if(Object.keys(query).length ==0) { + res.status(200).json(results) + } + + if(query.active!== undefined && query.approved!== undefined) { + const invoices = await collection + .find({active:true, approved:false}) + .toArray(); + res.status(200).json(invoices); + } + + if(query.signedBySeller!== undefined + ) { + const invoices = await collection + .find({active:true, approved:true, + signedBySeller:true}) + .toArray(); + res.status(200).json(invoices); + } + + + if(query.sellerAddress!== undefined + ) { + const invoices = await collection + .find({active:true, approved:true + , sellerAddress:query.sellerAddress, + signedBySeller:false}) + .toArray(); + res.status(200).json(invoices); + } + } catch (error) { console.error(error) res.status(500).json({ error: "Internal Server Error" }) @@ -48,12 +80,13 @@ export default async function handler( case "PUT": try { const uploadedInvoice = new UploadedInvoice(req.body) - const { _id, approved, verifierAddress } = uploadedInvoice + const { _id, approved, signedBySeller } = uploadedInvoice - const result = await collection.updateOne( + var result = await collection.updateOne( { _id: _id }, - { $set: { approved: approved, verifierAddress: verifierAddress } } + { $set: { approved: approved, signedBySeller:signedBySeller } } ) + res.status(201).json(result) } catch (error) { console.error(error) diff --git a/next-app/pages/company.tsx b/next-app/pages/company.tsx index ebc216c..7bbc9f7 100644 --- a/next-app/pages/company.tsx +++ b/next-app/pages/company.tsx @@ -1,8 +1,7 @@ import IDFAQ from "@/components/FAQ/IDFAQ" import Footer from "@/components/Header/Footer" import Header from "@/components/Header/Header" -import NFTCard from "@/components/Invest/NFTCard" -import NFTDashbaord from "@/components/Invest/NFTDashbaord" +import InvoiceDetails from "@/components/Invest/InvoiceDetails" import ProfileCard from "@/components/Invest/ProfileCard" import { Divider, @@ -21,7 +20,8 @@ function company() { return ( <>
- + + @@ -37,6 +37,7 @@ function company() { + diff --git a/next-app/pages/factor.tsx b/next-app/pages/factor.tsx index 968d73a..81f376f 100644 --- a/next-app/pages/factor.tsx +++ b/next-app/pages/factor.tsx @@ -23,7 +23,8 @@ import { import Link from "next/link" import React, { useEffect, useState } from "react" import { useAccount } from "wagmi" -import {factoryContract} from "@/blockend/interact" +import invoiceAbi from '@/blockend/build/invoice.json' +import web3 from "@/blockend/web3"; type Props = {} @@ -45,24 +46,31 @@ export default function ({}: Props) { const [approveForm, setApproveForm] = useState(false) const [isSubmitting, setIsSubmitting] = useState(false) const [invoiceAddresses, setInvoiceAddresses] = useState([]) + const [invoice, setInvoice] = useState(); - const getInvoices = async() => { - const result = await factoryContract.methods.getDeployedContracts().call(); - setInvoiceAddresses(result); - } + // const getInvoices = async() => { + // const result = await factoryContract.methods.getDeployedContracts().call(); + // setInvoiceAddresses(result); + // } - getInvoices(); + // getInvoices(); const [formData, setFormData] = useState({ - invoiceAmount: "", - discountedAmount: "", - fees: "", + totalInvoiceAmount: 0, + amountPerUnit:0, + repaymentPerUnit:0, + totalUnits:0, + tenure:0, agreementHash: "", + fees:0, + seller: "", + buyer: "", + xirr:0 }) useEffect(() => { const fetchInvoices = async () => { - const response = await fetch("api/uploaded_invoices").then((res) => + const response = await fetch("api/uploaded_invoices?active=true&approved=false").then((res) => res.json() ) setUploadedInvoices(response) @@ -73,21 +81,15 @@ export default function ({}: Props) { const updateDecline = async (invoice: UploadedInvoice) => {} + var varToSaveToMongoDb = null; const approveInvoice = async (invoice: UploadedInvoice) => { - setApproveForm(true) - - // let approvedInvoice = invoice - // approvedInvoice.verifierAddress = address - // approvedInvoice.approved = true + setApproveForm(true) + setInvoice(invoice); + + - // const updatedInvoice = await fetch("api/uploaded_invoices", { - // method: "PUT", - // headers: { - // "Content-Type": "application/json", - // }, - // body: JSON.stringify(approvedInvoice), - // }) + // const res = await updatedInvoice.json() // if (res.acknowledged) { //Here comes the blockchain functionality @@ -96,8 +98,43 @@ export default function ({}: Props) { //Pop - Up a form to add amount, discountedAmount, due date, fees, agreement url //ROI?. // } + + } + + const saveToMongoDB = async(invoice: UploadedInvoice) =>{ + let approvedInvoice = invoice + + approvedInvoice.approved = true; + approvedInvoice.signedBySeller = false; + + const updatedInvoice = await fetch("api/uploaded_invoices", { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(approvedInvoice), + }) + + const result = await updatedInvoice.json(); + + if (result.acknowledged) { + alert("Invoice uploaded successfully to MongoDb") + } else { + alert("There was an error uploading the invoice") + } + + } + + // useEffect(() => { + // if(varToSaveToMongoDb){ + // saveToMongoDB(invoice); + // } + // },[varToSaveToMongoDb]); + + + const handleInputChange = (event: React.ChangeEvent) => { const { name, value } = event.target setFormData((prevFormData) => ({ @@ -106,17 +143,40 @@ export default function ({}: Props) { })) } - const submitApproval = async () => { - setIsSubmitting(true) + const submitApproval = async (e) => { + e.preventDefault(); + + setIsSubmitting(true) + + const invoiceContract = new web3.eth.Contract(invoiceAbi, invoice.contractAddress) + + console.log(invoice.contractAddress); + console.log(invoiceContract); - //do the blockchain logic here + const res = await invoiceContract.methods.approveInvoice( + formData.totalInvoiceAmount, + formData.amountPerUnit, + formData.repaymentPerUnit, + formData.totalUnits, + formData.tenure, + formData.agreementHash, + formData.fees, + formData.seller, + formData.buyer, + formData.xirr + ).send({from: address}) + .then(() => { + + saveToMongoDB(invoice); - //get the smart contract Id + setIsSubmitting(false) + setApproveForm(false) + }); - //fetch the invoice from the database using the smart contract Id and update it ...calls the approveInvoice(); + console.log("Invoice Approved: ", res); - setIsSubmitting(false) - // setApproveForm(false) + + //notification success or error! } @@ -132,30 +192,56 @@ export default function ({}: Props) {
- + + + + + + + + - + + Agreement Hash + + + + + + + + + + + + + + {isSubmitting ? (