diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..5c6b89f7 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "contracts/lib/forge-std"] + path = contracts/lib/forge-std + url = https://github.com/foundry-rs/forge-std +[submodule "contracts/lib/chainlink-brownie-contracts"] + path = contracts/lib/chainlink-brownie-contracts + url = https://github.com/smartcontractkit/chainlink-brownie-contracts diff --git a/README.md b/README.md index fc05779c..c0df07d3 100644 --- a/README.md +++ b/README.md @@ -45,3 +45,46 @@ forge install ```bash forge test ``` +## Deploying to Ethereum Classic (ETC) with Foundry + +This guide explains how to deploy Chainvoice smart contracts to the Ethereum Classic Mainnet using Foundry (Forge). + +Prerequisites +- Foundry installed +- A funded wallet with ETC +- RPC URL (e.g. from Rivet, Ankr, or Chainstack) + +1. Create .env File for Secrets + + - Create a .env in your project root i.e. `contracts/` + - Copy all the varible from `contracts/.env.example` to newly created `.env` + + `cp .env.example .env` + - Assign valid values to the variable. + +2. Compile Contract + + `forge build` +3. Load your .env in the terminal + + `source .env` +4. Deploy the Contract using forge create + +``` +forge create contracts/src/Chainvoice.sol:Chainvoice \ + --rpc-url $ETC_RPC_URL \ + --private-key $PRIVATE_KEY \ + --broadcast +``` +5. Finally add Contract Address to Frontend `.env` + - Create a new .env file by copying .env.example: + + `cp frontend/.env.example frontend/.env` + - Open the new .env file and update the variables, especially: + `VITE_CONTRACT_ADDRESS=your_deployed_contract_address_here` + + Replace your_deployed_contract_address_here with the actual contract address you got after deployment. + + - Save the .env file. + + - Restart your frontend development server so the new environment variables are loaded. diff --git a/contracts/.env.example b/contracts/.env.example new file mode 100644 index 00000000..586d96d4 --- /dev/null +++ b/contracts/.env.example @@ -0,0 +1,11 @@ +# Wallet Private Key +PRIVATE_KEY=your_private_key_here + +# Ethereum Sepolia +SEPOLIA_RPC_URL=https://ethereum-sepolia.publicnode.com/ +SEPOLIA_CHAIN_ID=11155111 +SEPOLIA_EXPLORER=https://sepolia.etherscan.io/ + +# Ethereum Classic +ETC_RPC_URL=https://etc.rivet.link +ETC_CHAIN_ID=61 diff --git a/contracts/lib/forge-std b/contracts/lib/forge-std new file mode 160000 index 00000000..3b20d60d --- /dev/null +++ b/contracts/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 3b20d60d14b343ee4f908cb8079495c07f5e8981 diff --git a/contracts/src/Chainvoice.sol b/contracts/src/Chainvoice.sol index 9b25397e..a729f8e8 100644 --- a/contracts/src/Chainvoice.sol +++ b/contracts/src/Chainvoice.sol @@ -1,174 +1,143 @@ // SPDX-License-Identifier: Unlicense pragma solidity ^0.8.13; -import {Test} from "../lib/forge-std/src/Test.sol"; -import {console} from "../lib/forge-std/src/console.sol"; contract Chainvoice { - struct UserDetails { - string fname; - string lname; - string email; - string country; - string city; - string postalcode; - } - struct ItemData { - string description; - int256 qty; - int256 unitPrice; - int256 discount; - int256 tax; - int256 amount; - } struct InvoiceDetails { uint256 id; address from; - string dueDate; - string issueDate; - UserDetails user; // Struct to store user details address to; - UserDetails client; // Struct to store client details uint256 amountDue; bool isPaid; + string encryptedInvoiceData; // Base64-encoded ciphertext + string encryptedHash; } InvoiceDetails[] public invoices; + mapping(address => uint256[]) public sentInvoices; mapping(address => uint256[]) public receivedInvoices; - mapping(uint256 => ItemData[]) public itemDatas; address public owner; address public treasuryAddress; uint256 public fee; + uint256 public accumulatedFees; - constructor() { - owner = msg.sender; - fee = 500000000000000 ; //0.0005 ether - } - - modifier OnlyOwner() { - require(msg.sender == owner, "Only Owner is accessible"); - _; - } event InvoiceCreated( - uint256 id, + uint256 indexed id, address indexed from, - address indexed to, - uint256 amountDue + address indexed to ); + event InvoicePaid( - uint256 id, + uint256 indexed id, address indexed from, address indexed to, - uint256 amountPaid + uint256 amount ); + constructor() { + owner = msg.sender; + fee = 0.0005 ether; + } + + modifier onlyOwner() { + require(msg.sender == owner, "Only owner can call"); + _; + } + function createInvoice( - uint256 amountDue, address to, - string memory _dueDate, - string memory _issueDate, - UserDetails memory user, - UserDetails memory client, - ItemData[] memory _items + uint256 amountDue, + string memory encryptedInvoiceData, + string memory encryptedHash ) external { - require(amountDue > 0, "Amount must be greater than zero"); - require(to != address(0), "Receiver address cannot be zero"); - require(to != msg.sender, "Cannot create invoice for yourself"); + require(to != address(0), "Recipient address is zero"); + require(to != msg.sender, "Self-invoicing not allowed"); uint256 invoiceId = invoices.length; + invoices.push( InvoiceDetails({ id: invoiceId, from: msg.sender, - dueDate:_dueDate, - issueDate: _issueDate, - user: user, to: to, - client: client, amountDue: amountDue, - isPaid: false + isPaid: false, + encryptedInvoiceData: encryptedInvoiceData, + encryptedHash: encryptedHash }) ); - for (uint256 i = 0; i < _items.length; i++) { - itemDatas[invoiceId].push(_items[i]); - } - sentInvoices[msg.sender].push(invoiceId); receivedInvoices[to].push(invoiceId); - emit InvoiceCreated(invoiceId, msg.sender, to, amountDue); + emit InvoiceCreated(invoiceId, msg.sender, to); } - uint256 public accumulatedFees; - function payInvoice(uint256 invoiceId) external payable { require(invoiceId < invoices.length, "Invalid invoice ID"); + InvoiceDetails storage invoice = invoices[invoiceId]; - require(msg.sender == invoice.to, "Not authorized to pay this invoice"); - require(!invoice.isPaid, "Invoice already paid"); - require( - msg.value >= invoice.amountDue + fee, - "Payment must cover the invoice amount and fee" - ); - uint256 amountToRecipient = msg.value - fee; - (bool success, ) = payable(invoice.from).call{value: amountToRecipient}( - "" - ); - require(success, "Payment transfer failed"); + require(msg.sender == invoice.to, "Not authorized"); + require(!invoice.isPaid, "Already paid"); + require(msg.value == invoice.amountDue + fee, "Incorrect payment amount"); + accumulatedFees += fee; invoice.isPaid = true; + + uint256 amountToSender = msg.value - fee; + (bool sent, ) = payable(invoice.from).call{value: amountToSender}(""); + require(sent, "Transfer failed"); + + emit InvoicePaid(invoiceId, invoice.from, invoice.to, msg.value); } - function getSentInvoices(address _address) - external - view - returns (InvoiceDetails[] memory, ItemData[][] memory) - { - return _getInvoices(sentInvoices[_address]); + function getSentInvoices( + address user + ) external view returns (InvoiceDetails[] memory) { + return _getInvoices(sentInvoices[user]); } function getReceivedInvoices( - address _address - ) external view returns (InvoiceDetails[] memory, ItemData[][] memory) { - return _getInvoices(receivedInvoices[_address]); + address user + ) external view returns (InvoiceDetails[] memory) { + return _getInvoices(receivedInvoices[user]); } function _getInvoices( - uint256[] storage invoiceIds - ) internal view returns (InvoiceDetails[] memory, ItemData[][] memory) { - InvoiceDetails[] memory userInvoices = new InvoiceDetails[]( - invoiceIds.length - ); - ItemData[][] memory items = new ItemData[][](invoiceIds.length); - - for (uint256 i = 0; i < invoiceIds.length; i++) { - userInvoices[i] = invoices[invoiceIds[i]]; - items[i] = itemDatas[invoiceIds[i]]; + uint256[] storage ids + ) internal view returns (InvoiceDetails[] memory) { + InvoiceDetails[] memory result = new InvoiceDetails[](ids.length); + for (uint256 i = 0; i < ids.length; i++) { + result[i] = invoices[ids[i]]; } - - return (userInvoices, items); + return result; } - function setTreasuryAddress(address newTreauserAdd) public OnlyOwner { - require(newTreauserAdd != address(0), "Treasury Address cannot be equal to zero"); - treasuryAddress = newTreauserAdd; + function getInvoice( + uint256 invoiceId + ) external view returns (InvoiceDetails memory) { + require(invoiceId < invoices.length, "Invalid ID"); + return invoices[invoiceId]; } - - function setFeeAmount(uint256 _fee) public OnlyOwner { + function setFeeAmount(uint256 _fee) external onlyOwner { fee = _fee; } - function withdraw() external { - require(treasuryAddress != address(0), "Treasury address not set"); - require(accumulatedFees > 0, "No fees to withdraw"); - + function setTreasuryAddress(address newTreasury) external onlyOwner { + require(newTreasury != address(0), "Zero address"); + treasuryAddress = newTreasury; + } + + function withdrawFees() external { + require(treasuryAddress != address(0), "Treasury not set"); + require(accumulatedFees > 0, "No fees available"); + uint256 amount = accumulatedFees; accumulatedFees = 0; (bool success, ) = payable(treasuryAddress).call{value: amount}(""); - require(success, "Fee withdrawal failed"); + require(success, "Withdraw failed"); } } diff --git a/contracts/src/MockV3Aggregator.sol b/contracts/src/MockV3Aggregator.sol deleted file mode 100644 index c00a076b..00000000 --- a/contracts/src/MockV3Aggregator.sol +++ /dev/null @@ -1,66 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import {AggregatorV3Interface} from "../lib/chainlink-brownie-contracts/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol"; - -contract MockV3Aggregator is AggregatorV3Interface { - uint256 public constant version = 4; - - uint8 public decimals; - int256 public latestAnswer; - uint256 public latestTimestamp; - uint256 public latestRound; - - mapping(uint256 => int256) public getAnswer; - mapping(uint256 => uint256) public getTimestamp; - mapping(uint256 => uint256) private getStartedAt; - - constructor(uint8 _decimals, int256 _initialAnswer) { - decimals = _decimals; - updateAnswer(_initialAnswer); - } - - function updateAnswer(int256 _answer) public { - latestAnswer = _answer; - latestTimestamp = block.timestamp; - latestRound++; - getAnswer[latestRound] = _answer; - getTimestamp[latestRound] = block.timestamp; - getStartedAt[latestRound] = block.timestamp; - } - - function updateRoundData(uint80 _roundId, int256 _answer, uint256 _timestamp, uint256 _startedAt) public { - latestRound = _roundId; - latestAnswer = _answer; - latestTimestamp = _timestamp; - getAnswer[latestRound] = _answer; - getTimestamp[latestRound] = _timestamp; - getStartedAt[latestRound] = _startedAt; - } - - function getRoundData(uint80 _roundId) - external - view - returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) - { - return (_roundId, getAnswer[_roundId], getStartedAt[_roundId], getTimestamp[_roundId], _roundId); - } - - function latestRoundData() - external - view - returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) - { - return ( - uint80(latestRound), - getAnswer[latestRound], - getStartedAt[latestRound], - getTimestamp[latestRound], - uint80(latestRound) - ); - } - - function description() external pure returns (string memory) { - return "v0.6/test/mock/MockV3Aggregator.sol"; - } -} \ No newline at end of file diff --git a/frontend/.env b/frontend/.env deleted file mode 100644 index e761d5b0..00000000 --- a/frontend/.env +++ /dev/null @@ -1,18 +0,0 @@ -# Sepolia testnet rpc URL -VITE_BLOCKCHAIN_URI=https://ethereum-sepolia.publicnode.com/ -VITE_CHAIN_ID=11155111 -ViTE_EXPLORER=https://sepolia.etherscan.io/ - - -# -VITE_CONTRACT_ADDRESS=0x11B4894d1D723FB24310f28E58796d452eBb5aDe -VITE_PRICEFEED_ADDRESS=0xAE26bD30c88Cc8bcB9b9Be97De7Ad33315fb3D65 -# 0x00eca3bd631b3623680d9a511bd89dd5fc965dea79104b02cf6d74ad8eaf31a6 - -# 0x24F13d40CF7DE6a81a2a1949aA45F2242e81f1e2 - -# forge create --rpc-url https://ethereum-sepolia.publicnode.com/ --private-key d198e5f47b1b0f09b8412805be0b90b3ee1321eec231f86a5cc884d3f460f1e2 src/Chainvoice.sol:Chainvoice --broadcast --constructor-args 0x694AA1769357215DE4FAC081bf1f309aDC325306 - -# ETH/USD Mainnet: 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419 -# ETH/USD Sepolia: 0x694AA1769357215DE4FAC081bf1f309aDC325306 -# ETH/USD Goerli: 0xD4a33860578De61DBAbDc8BFdb98FD742fA7028e diff --git a/frontend/.env.example b/frontend/.env.example new file mode 100644 index 00000000..dd86d5ba --- /dev/null +++ b/frontend/.env.example @@ -0,0 +1,15 @@ +# env-copy + +# Blockchain RPC URL (e.g., Sepolia, Ethereum Classic, etc.) +VITE_BLOCKCHAIN_URI=https://your-network-rpc-url/ + +# Network Chain ID (e.g., 11155111 for Sepolia, 61 for Ethereum Classic) +VITE_CHAIN_ID=your_chain_id_here + +# Block explorer URL for your network +VITE_EXPLORER=https://your-network-explorer-url/ + +# Deployed contract address (update after deployment) +VITE_CONTRACT_ADDRESS=0xYourContractAddressHere + + diff --git a/frontend/.gitignore b/frontend/.gitignore index 68b18a8f..bf1f56f8 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -11,6 +11,7 @@ node_modules dist dist-ssr *.local +.env # Editor directories and files .vscode/* @@ -22,4 +23,4 @@ dist-ssr *.ntvs* *.njsproj *.sln -*.sw? +*.sw? \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index f2069cb9..ce268161 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,6 +12,10 @@ "dependencies": { "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", + "@lit-protocol/auth-helpers": "^7.2.0", + "@lit-protocol/constants": "^7.2.0", + "@lit-protocol/encryption": "^7.2.0", + "@lit-protocol/lit-node-client": "^7.2.0", "@mui/icons-material": "^6.4.6", "@mui/material": "^6.4.6", "@radix-ui/react-label": "^2.1.1", diff --git a/frontend/src/App.css b/frontend/src/App.css deleted file mode 100644 index e69de29b..00000000 diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index ec859072..5edf1be7 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -11,10 +11,9 @@ import { } from "@tanstack/react-query"; import * as chains from "wagmi/chains"; import { BrowserRouter as Router, Route, Routes } from 'react-router-dom' -import './App.css' import Landing from './page/Landing' import Applayout from './page/Applayout' - + import { citreaTestnet } from './utils/CitreaTestnet'; import Home from './page/Home'; import Feature from './page/Feature'; diff --git a/frontend/src/components/CreateInvoice.jsx b/frontend/src/components/CreateInvoice.jsx index eda38146..8053bb37 100644 --- a/frontend/src/components/CreateInvoice.jsx +++ b/frontend/src/components/CreateInvoice.jsx @@ -1,20 +1,30 @@ -import React, { useEffect, useState } from 'react'; -import { Input } from './ui/input'; -import { Button } from './ui/button'; -import { BrowserProvider, Contract, ethers } from 'ethers'; -import { useAccount, useWalletClient } from 'wagmi'; -import { ChainvoiceABI } from '../contractsABI/ChainvoiceABI'; +import React, { useEffect, useRef, useState } from "react"; +import { Input } from "./ui/input"; +import { Button } from "./ui/button"; +import { BrowserProvider, Contract, ethers, formatUnits, parseUnits } from "ethers"; +import { useAccount, useWalletClient } from "wagmi"; +import { ChainvoiceABI } from "../contractsABI/ChainvoiceABI"; import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; import { Calendar } from "@/components/ui/calendar"; -import { CalendarIcon, Loader2 } from 'lucide-react'; -import { cn } from '@/lib/utils'; -import { format } from 'date-fns'; -import { Label } from './ui/label'; -import { useNavigate } from 'react-router-dom'; +import { CalendarIcon, Loader2 } from "lucide-react"; +import { cn } from "@/lib/utils"; +import { format } from "date-fns"; +import { Label } from "./ui/label"; +import { useNavigate } from "react-router-dom"; + +import { LitNodeClient } from "@lit-protocol/lit-node-client"; +import { encryptString } from "@lit-protocol/encryption/src/lib/encryption.js"; +import { LIT_ABILITY, LIT_NETWORK } from "@lit-protocol/constants"; +import { + createSiweMessageWithRecaps, + generateAuthSig, + LitAccessControlConditionResource, +} from "@lit-protocol/auth-helpers"; + function CreateInvoice() { const { data: walletClient } = useWalletClient(); const { isConnected } = useAccount(); @@ -23,6 +33,97 @@ function CreateInvoice() { const [issueDate, setIssueDate] = useState(new Date()); const [loading, setLoading] = useState(false); const navigate = useNavigate(); + const litClientRef = useRef(null); + + const [itemData, setItemData] = useState([ + { + description: "", + qty: "", + unitPrice: "", + discount: "", + tax: "", + amount: "", + }, + ]); + + const [totalAmountDue, setTotalAmountDue] = useState(0); + + useEffect(() => { + const total = itemData.reduce((sum, item) => { + const qty = parseUnits(item.qty || "0", 18); + const unitPrice = parseUnits(item.unitPrice || "0", 18); + const discount = parseUnits(item.discount || "0", 18); + const tax = parseUnits(item.tax || "0", 18); + // qty * price (then divide by 1e18 to cancel double scaling) + const lineTotal = qty * unitPrice / parseUnits("1", 18); + const adjusted = lineTotal - discount + tax; + + return sum + adjusted; + }, 0n); + + setTotalAmountDue(formatUnits(total, 18)); + + }, [itemData]); + + useEffect(() => { + const initLit = async () => { + if (!litClientRef.current) { + const client = new LitNodeClient({ + litNetwork: LIT_NETWORK.DatilDev, + debug: false, + }); + await client.connect(); + litClientRef.current = client; + console.log(litClientRef.current); + } + }; + initLit(); + }, []); + + const handleItemData = (e, index) => { + const { name, value } = e.target; + + setItemData((prevItemData) => + prevItemData.map((item, i) => { + if (i === index) { + const updatedItem = { ...item, [name]: value }; + if ( + name === "qty" || + name === "unitPrice" || + name === "discount" || + name === "tax" + ) { + const qty = parseUnits(updatedItem.qty || "0", 18); + const unitPrice = parseUnits(updatedItem.unitPrice || "0", 18); + const discount = parseUnits(updatedItem.discount || "0", 18); + const tax = parseUnits(updatedItem.tax || "0", 18); + + const lineTotal = (qty * unitPrice) / parseUnits("1", 18); + const finalAmount = lineTotal - discount + tax; + + updatedItem.amount = formatUnits(finalAmount, 18); + } + return updatedItem; + } + return item; + }) + ); + }; + + const addItem = () => { + setItemData((prev) => [ + ...prev, + { + description: "", + qty: "", + unitPrice: "", + discount: "", + tax: "", + amount: "", + }, + ]); + }; + const createInvoiceRequest = async (data) => { if (!isConnected || !walletClient) { alert("Please connect your wallet"); @@ -33,68 +134,137 @@ function CreateInvoice() { setLoading(true); const provider = new BrowserProvider(walletClient); const signer = await provider.getSigner(); + + console.log("data >>>>>> ", data); + console.log("acc :", account.address); + // 1. Prepare invoice data + const invoicePayload = { + amountDue: totalAmountDue, + dueDate, + issueDate, + user: { + address: account?.address.toString(), + fname: data.userFname, + lname: data.userLname, + email: data.userEmail, + country: data.userCountry, + city: data.userCity, + postalcode: data.userPostalcode, + }, + client: { + address: data.clientAddress, + fname: data.clientFname, + lname: data.clientLname, + email: data.clientEmail, + country: data.clientCountry, + city: data.clientCity, + postalcode: data.clientPostalcode, + }, + items: itemData, + }; + + const invoiceString = JSON.stringify(invoicePayload); + + // 2. Setup Lit + + const litNodeClient = litClientRef.current; + if (!litNodeClient) { + alert("Lit client not initialized"); + return; + } + + const accessControlConditions = [ + { + contractAddress: "", + standardContractType: "", + chain: "ethereum", + method: "", + parameters: [":userAddress"], + returnValueTest: { + comparator: "=", + value: account.address.toLowerCase(), + }, + }, + { operator: "or" }, + { + contractAddress: "", + standardContractType: "", + chain: "ethereum", + method: "", + parameters: [":userAddress"], + returnValueTest: { + comparator: "=", + value: data.clientAddress.toLowerCase(), + }, + }, + ]; + + // 3. Encrypt + const { ciphertext, dataToEncryptHash } = await encryptString( + { + accessControlConditions, + dataToEncrypt: invoiceString, + }, + litNodeClient + ); + + const sessionSigs = await litNodeClient.getSessionSigs({ + chain: "ethereum", + resourceAbilityRequests: [ + { + resource: new LitAccessControlConditionResource("*"), + ability: LIT_ABILITY.AccessControlConditionDecryption, + }, + ], + authNeededCallback: async ({ + uri, + expiration, + resourceAbilityRequests, + }) => { + const nonce = await litNodeClient.getLatestBlockhash(); + const toSign = await createSiweMessageWithRecaps({ + uri, + expiration, + resources: resourceAbilityRequests, + walletAddress: account.address, + nonce, + litNodeClient, + }); + + return await generateAuthSig({ + signer, + toSign, + }); + }, + }); + + const encryptedStringBase64 = btoa(ciphertext); + + // 4. Send to contract const contract = new Contract( import.meta.env.VITE_CONTRACT_ADDRESS, ChainvoiceABI, signer ); - const sanitizedItemData = data.itemData.map((item) => ({ - ...item, - qty: item.qty ? String(item.qty) : "0", - unitPrice: item.unitPrice ? ethers.parseUnits(String(item.unitPrice), 18) : ethers.parseUnits("0", 18), - discount: item.discount ? String(item.discount) : "0", - tax: item.tax ? String(item.tax) : "0", - amount: item.amount ? ethers.parseUnits(String(item.amount), 18) : ethers.parseUnits("0", 18), - })); - - console.log(sanitizedItemData); - const res = await contract.createInvoice( - ethers.parseUnits(String(totalAmountDue), 18), + console.log("amt : ",ethers.parseEther(totalAmountDue.toString())); + const tx = await contract.createInvoice( data.clientAddress, - dueDate.toLocaleString(undefined,{timeZone:"Asia/Kolkata"}).toString(), - issueDate.toLocaleString(undefined,{timeZone:"Asia/Kolkata"}).toString(), - [ - data.userFname, - data.userLname, - data.userEmail, - data.userCountry, - data.userCity, - data.userPostalcode, - ], - [ - data.clientFname, - data.clientLname, - data.clientEmail, - data.clientCountry, - data.clientCity, - data.clientPostalcode, - ], - sanitizedItemData + ethers.parseEther(totalAmountDue.toString()), + encryptedStringBase64, + dataToEncryptHash ); - setTimeout(()=>{ - navigate('/home/sent'); - },4000); - console.log("Transaction successful", res); - } catch (error) { - console.error("Invoice creation error:", error); - setLoading(false); - alert("Failed to create invoice"); + + console.log("Invoice created:", tx); + setTimeout(() => navigate("/home/sent"), 4000); + } catch (err) { + console.error("Encryption or transaction failed:", err); + alert("Failed to create invoice."); } finally { setLoading(false); } }; - - const [itemData, setItemData] = useState([{ - description: '', - qty: '', - unitPrice: '', - discount: '', - tax: '', - amount: '' - }]); - - const handleSubmit = (e) => { + const handleSubmit = async (e) => { e.preventDefault(); const formData = new FormData(e.target); @@ -131,60 +301,18 @@ function CreateInvoice() { clientCountry, clientCity, clientPostalcode, - itemData + itemData, }; console.log(data); - createInvoiceRequest(data); + await createInvoiceRequest(data); }; - const [totalAmountDue, setTotalAmountDue] = useState(0); - const handleItemData = (e, index) => { - const { name, value } = e.target; - - setItemData((prevItemData) => - prevItemData.map((item, i) => { - if (i === index) { - const updatedItem = { ...item, [name]: value }; - if (name === 'qty' || name === 'unitPrice' || name === 'discount' || name === 'tax') { - const qty = parseFloat(updatedItem.qty) || 0; - const unitPrice = parseFloat(updatedItem.unitPrice) || 0; - const discount = parseFloat(updatedItem.discount) || 0; - const tax = parseFloat(updatedItem.tax) || 0; - updatedItem.amount = (qty * unitPrice - discount + tax).toString(); - } - return updatedItem; - } - return item; - }) - ); - }; - - const addItem = () => { - setItemData((prev) => [...prev, { - description: '', - qty: '', - unitPrice: '', - discount: '', - tax: '', - amount: '', - }]); - console.log(itemData.length); - }; - - useEffect(() => { - let total = itemData.reduce((sum, item) => { - return sum + ((parseFloat(item.qty) || 0) * (parseFloat(item.unitPrice) || 0) - (parseFloat(item.discount) || 0) + (parseFloat(item.tax) || 0)); - }, 0); - - setTotalAmountDue(total); - }, [itemData]); - return ( -
Issued Date