diff --git a/frontend/package.json b/frontend/package.json index ce268161..84beb46c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -30,6 +30,7 @@ "eth-crypto": "^2.7.0", "ethereum-unit-converter": "^0.0.17", "ethers": "^6.13.5", + "framer-motion": "^12.23.0", "html2canvas": "^1.4.1", "jspdf": "^3.0.0", "lucide-react": "^0.471.1", @@ -37,6 +38,8 @@ "react-day-picker": "^8.10.1", "react-dom": "^18.3.1", "react-hook-form": "^7.54.2", + "react-hot-toast": "^2.5.2", + "react-icons": "^5.5.0", "react-router-dom": "^7.1.1", "react-to-print": "^3.0.5", "tailwind-merge": "^2.6.0", diff --git a/frontend/public/logo.png b/frontend/public/logo.png new file mode 100644 index 00000000..46809fdd Binary files /dev/null and b/frontend/public/logo.png differ diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 5edf1be7..6a9ab5a3 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,33 +1,29 @@ -import '@rainbow-me/rainbowkit/styles.css'; +import "@rainbow-me/rainbowkit/styles.css"; import { getDefaultConfig, RainbowKitProvider, darkTheme, } from "@rainbow-me/rainbowkit"; -import { WagmiProvider } from 'wagmi'; -import { - QueryClientProvider, - QueryClient, -} from "@tanstack/react-query"; +import { WagmiProvider } from "wagmi"; +import { QueryClientProvider, QueryClient } from "@tanstack/react-query"; import * as chains from "wagmi/chains"; -import { BrowserRouter as Router, Route, Routes } from 'react-router-dom' -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'; -import About from './page/About'; -import Working from './page/Working'; -import Treasure from './page/Treasure'; -import CreateInvoice from './components/CreateInvoice'; -import SentInvoice from './page/SentInvoice'; -import ReceivedInvoice from './page/ReceivedInvoice'; -const AllChains = [ - ...Object.values(chains), -]; -// citreaTestnet, +import { BrowserRouter as Router, Route, Routes } from "react-router-dom"; +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"; +import About from "./page/About"; +import Working from "./page/Working"; +import Treasure from "./page/Treasure"; +import CreateInvoice from "./components/CreateInvoice"; +import SentInvoice from "./page/SentInvoice"; +import ReceivedInvoice from "./page/ReceivedInvoice"; + +const AllChains = [...Object.values(chains), citreaTestnet]; + export const config = getDefaultConfig({ appName: "My RainbowKit App", projectId: "YOUR_PROJECT_ID", @@ -35,46 +31,48 @@ export const config = getDefaultConfig({ ssr: true, }); const queryClient = new QueryClient(); +import { Toaster } from "react-hot-toast"; function App() { - return ( -
- - - -
- - - }> - } /> - } > - }/> - }/> - }/> +
+ + + + +
+ + + }> + } /> + }> + } /> + } /> + } /> + + } /> + } /> + } /> + } /> - } /> - } /> - } /> - } /> - - - -
-
-
-
+ + +
+ + +
- ) + ); } -export default App; \ No newline at end of file +export default App; diff --git a/frontend/src/components/CreateInvoice.jsx b/frontend/src/components/CreateInvoice.jsx index 8053bb37..642b0868 100644 --- a/frontend/src/components/CreateInvoice.jsx +++ b/frontend/src/components/CreateInvoice.jsx @@ -10,7 +10,7 @@ import { PopoverTrigger, } from "@/components/ui/popover"; import { Calendar } from "@/components/ui/calendar"; -import { CalendarIcon, Loader2 } from "lucide-react"; +import { CalendarIcon, Loader2, PlusIcon } from "lucide-react"; import { cn } from "@/lib/utils"; import { format } from "date-fns"; import { Label } from "./ui/label"; @@ -135,8 +135,6 @@ function CreateInvoice() { 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, @@ -166,13 +164,10 @@ function CreateInvoice() { const invoiceString = JSON.stringify(invoicePayload); // 2. Setup Lit - - const litNodeClient = litClientRef.current; if (!litNodeClient) { alert("Lit client not initialized"); return; } - const accessControlConditions = [ { contractAddress: "", @@ -246,7 +241,6 @@ function CreateInvoice() { ChainvoiceABI, signer ); - console.log("amt : ",ethers.parseEther(totalAmountDue.toString())); const tx = await contract.createInvoice( data.clientAddress, ethers.parseEther(totalAmountDue.toString()), @@ -254,8 +248,9 @@ function CreateInvoice() { dataToEncryptHash ); - console.log("Invoice created:", tx); - setTimeout(() => navigate("/home/sent"), 4000); + const receipt = await tx.wait(); + setTimeout(() => navigate("/dashboard/sent"), 4000); + } catch (err) { console.error("Encryption or transaction failed:", err); alert("Failed to create invoice."); @@ -309,258 +304,402 @@ function CreateInvoice() { return (
-

Create New Invoice Request

-
- - -

Issued Date

- - -

Due Date

- - - - - - - - +

+ Create New Invoice Request +

+ +
+
+ + +
+ +
+ + +
+ +
+ + + + + + + { + if (date) { + setDueDate(date); + document.dispatchEvent( + new KeyboardEvent("keydown", { key: "Escape" }) + ); + } + }} + initialFocus + disabled={(date) => date < new Date()} + /> + + +
+
-
-
-

From (Your Information)

+
+ {/* Your Information */} +
+

+ From (Your Information) +

-
-

Add Your Info

-
- - + +
+
+
+ + +
+
+ + +
-
- - +
+
+ + +
+
+ + +
-
- - + +
+
+ + +
+
+ + +
- -
-

Client Information

+ {/* Client Information */} +
+

+ Client Information +

-
-

Add Client Info

-
- - + +
+
+
+ + +
+
+ + +
-
- - +
+
+ + +
+
+ + +
-
- - + +
+
+ + +
+
+ + +
-
-
-
DESCRIPTION
-
QTY
-
UNIT PRICE
-
DISCOUNT
-
TAX(%)
-
AMOUNT
+ + {/* Invoice Items Section */} +
+
+
DESCRIPTION
+
QTY
+
UNIT PRICE
+
DISCOUNT
+
TAX(%)
+
AMOUNT
+ {/*
AMOUNT
*/}
-
+ +
{itemData.map((_, index) => ( -
- handleItemData(e, index)} - /> - handleItemData(e, index)} - /> - handleItemData(e, index)} - /> - handleItemData(e, index)} - /> - handleItemData(e, index)} - /> - handleItemData(e, index)} - /> +
+ {/* Item Fields */} +
+ handleItemData(e, index)} + /> +
+
+ handleItemData(e, index)} + /> +
+
+ handleItemData(e, index)} + /> +
+
+ handleItemData(e, index)} + /> +
+
+ handleItemData(e, index)} + /> +
+
+ +
+ + {index > 0 && ( + + )}
))}
-

Total : {totalAmountDue}

-
+
+ +
+
+ Total: + + {/* {totalAmountDue} ETH */} + {totalAmountDue} cBTC + +
+
+
+
+ + {/* Form Actions */} +
+ +
+ + Creating Invoice... +
) : ( - + "Create Invoice" )} -
+
diff --git a/frontend/src/components/Navbar.jsx b/frontend/src/components/Navbar.jsx index a8067e25..a88bc0fe 100644 --- a/frontend/src/components/Navbar.jsx +++ b/frontend/src/components/Navbar.jsx @@ -1,103 +1,318 @@ -import { ConnectButton } from '@rainbow-me/rainbowkit'; -import React, { useEffect } from 'react' -import { Link, useNavigate } from 'react-router-dom'; -import { useAccount } from 'wagmi'; -import AccountBalanceWalletIcon from '@mui/icons-material/AccountBalanceWallet'; -import HomeIcon from '@mui/icons-material/Home'; +// Updated Navbar.js +import { ConnectButton } from "@rainbow-me/rainbowkit"; +import React, { useEffect, useState } from "react"; +import { Link, useNavigate, useLocation } from "react-router-dom"; +import { useAccount } from "wagmi"; +import AccountBalanceWalletIcon from "@mui/icons-material/AccountBalanceWallet"; +import HomeIcon from "@mui/icons-material/Home"; +import DashboardIcon from "@mui/icons-material/Dashboard"; +import FeaturedPlayListIcon from "@mui/icons-material/FeaturedPlayList"; +import DesignServicesIcon from "@mui/icons-material/DesignServices"; +import { motion, AnimatePresence } from "framer-motion"; +import CloseIcon from "@mui/icons-material/Close"; +import MenuIcon from "@mui/icons-material/Menu"; function Navbar() { - const { address } = useAccount(); - const navigate = useNavigate() + const { address, isConnected } = useAccount(); + const navigate = useNavigate(); + const location = useLocation(); + const [hasConnected, setHasConnected] = useState(false); + const [isScrolled, setIsScrolled] = useState(false); + const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); + + // Improved active route detection + const isActive = (path) => { + if (path.startsWith("/#")) { + return location.pathname === "/" && location.hash === path.substring(1); + } + return ( + location.pathname.startsWith(path) && + (path !== "/" || location.pathname === "/") + ); + }; + useEffect(() => { - if (!address) navigate('/') - }, [address]); - const handleScrollToFeature = () => { - const featureSection = document.getElementById("feature-section"); - const offset = 200; - const position = featureSection.offsetTop - offset; - - window.scrollTo({ - top: position, - behavior: "smooth", - }); + if ( + address && + !hasConnected && + !location.pathname.startsWith("/dashboard") + ) { + navigate("/dashboard/create"); + setHasConnected(true); + } + if (!address) { + navigate("/"); + setHasConnected(false); + } + + const handleScroll = () => { + setIsScrolled(window.scrollY > 10); + }; + window.addEventListener("scroll", handleScroll); + return () => window.removeEventListener("scroll", handleScroll); + }, [isConnected, hasConnected, navigate, location.pathname]); + + const handleScroll = (sectionId) => { + if (location.pathname !== "/") { + navigate("/"); + setTimeout(() => { + const section = document.getElementById(sectionId); + if (section) { + window.scrollTo({ + top: section.offsetTop - 200, + behavior: "smooth", + }); + } + }, 100); + } else { + const section = document.getElementById(sectionId); + if (section) { + window.scrollTo({ + top: section.offsetTop - 200, + behavior: "smooth", + }); + } + } }; - const handleScrollToHome = () => { - const featureSection = document.getElementById("home-section"); - const offset = 200; - const position = featureSection.offsetTop - offset; + const navItems = [ + { + name: "Home", + icon: , + action: () => handleScroll("home-section"), + path: "/", + }, + { + name: "Features", + icon: , + action: () => handleScroll("feature-section"), + path: "/#feature-section", + }, + { + name: "Services", + icon: , + action: () => handleScroll("service-section"), + path: "/#service-section", + }, + ]; + + const appItems = [ + { + name: "Dashboard", + icon: , + path: "/dashboard", + activePaths: [ + "/dashboard/create", + "/dashboard/sent", + "/dashboard/pending", + ], + }, + { + name: "Treasure", + icon: , + path: "/treasure", + }, + ]; - window.scrollTo({ - top: position, - behavior: "smooth", - }); + const toggleMobileMenu = () => { + setIsMobileMenuOpen(!isMobileMenuOpen); }; - const handleScrollToService = () => { - const featureSection = document.getElementById("service-section"); - const offset = 200; - const position = featureSection.offsetTop - offset; - - window.scrollTo({ - top: position, - behavior: "smooth", - }); + + const closeMobileMenu = () => { + setIsMobileMenuOpen(false); }; + return ( - <> -
-
navigate('/')} >Chainvoice
-
-
    - { - address && ( -
  • - - Home -
  • - ) - } - { - !address && ( -
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
    + +
    +
    + { + navigate("/"); + closeMobileMenu(); + }} + > + logo +

    + Chainvoice +

    +
    + + {/* Desktop Navigation */} +
    + {navItems.map((item) => ( + + {item.icon} + {item.name} + + ))} + + {isConnected && + appItems.map((item) => ( + + {item?.path === "/dashboard" ? ( + + {item.icon} + {item.name} + + ) : ( + + {item.icon} + {item.name} + + )} + + ))} + + + + +
    - ) - } - {/*
  • - How It Works -
  • */} - - { - address && ( -
  • - - Treasure -
  • - ) - } - {/*
  • - About Us -
  • */} - - -
+ {/* Mobile menu button */} +
+ + + + + {isMobileMenuOpen ? ( + + ) : ( + + )} + +
-
- {/* Add padding to prevent content from being hidden behind the navbar */} -
+ {/* Mobile Menu */} + + {isMobileMenuOpen && ( + +
+ {/* Navigation Links */} + {navItems.map((item) => ( + { + item.action(); + closeMobileMenu(); + }} + className={`w-full flex items-center px-4 py-3 rounded-lg transition-colors ${ + isActive(item.path) + ? "bg-green-900/30 text-green-400 font-medium" + : "text-white hover:bg-gray-800" + }`} + > + {item.icon} + {item.name} + + ))} + + {isConnected && + appItems.map((item) => ( + +
+ {item.icon} + {item.name} +
+ + ))} - - ) +
+ +
+
+
+ )} +
+
+ + ); } -export default Navbar; +export default Navbar; \ No newline at end of file diff --git a/frontend/src/page/Applayout.jsx b/frontend/src/page/Applayout.jsx index 41ae62e2..8b459d0c 100644 --- a/frontend/src/page/Applayout.jsx +++ b/frontend/src/page/Applayout.jsx @@ -1,20 +1,19 @@ +// AppLayout.js import Footer from "@/components/Footer"; import Navbar from "@/components/Navbar"; import React from "react"; import { Outlet } from "react-router-dom"; function Applayout() { - return ( - <> -
- -
- -
-
-
- - ) + return ( +
+ +
+ +
+
+
+ ); } -export default Applayout; \ No newline at end of file +export default Applayout; diff --git a/frontend/src/page/Home.jsx b/frontend/src/page/Home.jsx index 24381b36..cc80256b 100644 --- a/frontend/src/page/Home.jsx +++ b/frontend/src/page/Home.jsx @@ -1,111 +1,149 @@ -import * as React from 'react'; -import Box from '@mui/material/Box'; -import Drawer from '@mui/material/Drawer'; -import List from '@mui/material/List'; -import ListItem from '@mui/material/ListItem'; -import ListItemButton from '@mui/material/ListItemButton'; -import ListItemIcon from '@mui/material/ListItemIcon'; -import ListItemText from '@mui/material/ListItemText'; -import AddCircleIcon from '@mui/icons-material/AddCircle'; -import PendingIcon from '@mui/icons-material/Pending'; -import MarkEmailReadIcon from '@mui/icons-material/MarkEmailRead'; -import { Outlet, useNavigate } from 'react-router-dom'; +// Home.js +import * as React from "react"; +import Box from "@mui/material/Box"; +import Drawer from "@mui/material/Drawer"; +import List from "@mui/material/List"; +import ListItem from "@mui/material/ListItem"; +import ListItemButton from "@mui/material/ListItemButton"; +import ListItemIcon from "@mui/material/ListItemIcon"; +import ListItemText from "@mui/material/ListItemText"; +import MailOutlineIcon from "@mui/icons-material/MailOutline"; +import DraftsIcon from "@mui/icons-material/Drafts"; +import AddCircleOutlineIcon from "@mui/icons-material/AddCircleOutline"; +import { Outlet, useNavigate, useLocation } from "react-router-dom"; + export default function Home() { const navigate = useNavigate(); + const location = useLocation(); - const list = [ + const menuItems = [ { - text: 'Sent Payment Requests', - icon: , - route: 'sent' + text: "Sent Requests", + icon: , + route: "sent", + color: "#4ade80", }, { - text: 'Received Payment Requests', - icon: , - route: 'pending' + text: "Received Requests", + icon: , + route: "pending", + color: "#60a5fa", }, { - text: 'Create New Invoice Request', - icon: , - route: 'create' + text: "New Invoice", + icon: , + route: "create", + color: "#f472b6", }, - ] - return ( - <> -
+ ]; -

- Welcome Back! -

+ return ( +
+
+

+ Welcome Back! +

+
- - + {/* Sidebar Navigation */} + - - - {list.map((obj, index) => ( - navigate(obj.route)}> + + {menuItems.map((item) => ( + navigate(item.route)} + selected={location.pathname.includes(item.route)} sx={{ - borderRadius: '8px', - transition: '0.3s', - '&:hover': { backgroundColor: 'rgba(255, 255, 255, 0.1)' }, + borderRadius: "8px", + transition: "all 0.2s ease", + backgroundColor: location.pathname.includes(item.route) + ? "rgba(255, 255, 255, 0.08)" + : "transparent", + "&:hover": { + backgroundColor: "rgba(255, 255, 255, 0.05)", + transform: "translateX(4px)", + }, + "&.Mui-selected": { + borderLeft: `4px solid ${item.color}`, + }, + padding: "12px 16px", }} > - - {obj.icon} + + {item.icon} - + ))} - - - + + + {/* Main Content */} + -
- +
); } diff --git a/frontend/src/page/Landing.jsx b/frontend/src/page/Landing.jsx index 5636c72c..4f87f6a5 100644 --- a/frontend/src/page/Landing.jsx +++ b/frontend/src/page/Landing.jsx @@ -2,161 +2,220 @@ import { ConnectButton } from "@rainbow-me/rainbowkit"; import React, { useEffect } from "react"; import { useNavigate } from "react-router-dom"; import { useAccount } from "wagmi"; -import ShieldIcon from '@mui/icons-material/Shield'; -import EmailIcon from '@mui/icons-material/Email'; -import GavelIcon from '@mui/icons-material/Gavel'; -import LeaderboardIcon from '@mui/icons-material/Leaderboard'; +import ShieldIcon from "@mui/icons-material/Shield"; +import EmailIcon from "@mui/icons-material/Email"; +import GavelIcon from "@mui/icons-material/Gavel"; +import LeaderboardIcon from "@mui/icons-material/Leaderboard"; function Landing() { - const Account = useAccount(); - const navigate = useNavigate(); + const Account = useAccount(); + const navigate = useNavigate(); - useEffect(() => { - if (Account.address) navigate('/home/sent'); - }, [Account, Account.address]) - return ( - <> -
-
-

Decentralized Payment Requests &
Invoice Automation

-

One click to effortless invoicing — process payments in any ERC20 token with transparency and trustless efficiency!

- {/*

Only with

-
Chainvoice
*/} + // useEffect(() => { + // if (Account.address) navigate("/home/sent"); + // }, [Account, Account.address]); -

Connect with your wallet and Get Started!

-
- -
+ return ( + <> +
+
+

+ Decentralized Payment Requests & +
+ Invoice Automation +

+

+ One click to effortless invoicing — process payments in any ERC20 + token with transparency and trustless efficiency! +

+ {/*

Only with

+
Chainvoice
*/} -
-

Trusted by 1000+ users

-

Smart Contract Driven 100% Secure

-
-
-
- -
-
-
+

+ Connect with your wallet and Get Started! +

+
+ +
-
-

- A powerful and secure{" "} - - invoicing solution - {" "} - designed for growing businesses. -

-
- {[ - { - title: "Secure and Transparent Transactions", - description: - "Leverage blockchain technology to ensure encrypted, tamper-proof, and immutable transactions. Provide complete transparency for invoice verification.", - icon: , - }, - { - title: "Send and Receive Invoices", - description: - "Effortlessly create and manage invoices with a few clicks. Track real-time status and maintain a comprehensive invoice dashboard.", - icon: , - }, - { - title: "Smart Contract Integration", - description: - "Automate payment processes with secure smart contracts. Ensure funds are released only when invoice conditions are met, reducing intermediary dependencies.", - icon: , - }, - { - title: "Comprehensive Invoice Tracking", - description: - "Gain complete visibility into your invoice lifecycle. Monitor payment statuses, track financial performance, and manage all transactions seamlessly.", - icon: , - }, - ].map((feature, index) => ( -
-
- {feature.icon} -
-

{feature.title}

-

{feature.description}

-
- ))} -
-
-
- Invoice Illustration +
+

+ + Trusted by 1000+ users +

+

+ + Smart Contract Driven 100% Secure +

+
+
+
+ +
+
+
+
+

+ A powerful and secure{" "} + + invoicing solution + {" "} + designed for growing businesses. +

+
+ {[ + { + title: "Secure and Transparent Transactions", + description: + "Leverage blockchain technology to ensure encrypted, tamper-proof, and immutable transactions. Provide complete transparency for invoice verification.", + icon: , + }, + { + title: "Send and Receive Invoices", + description: + "Effortlessly create and manage invoices with a few clicks. Track real-time status and maintain a comprehensive invoice dashboard.", + icon: , + }, + { + title: "Smart Contract Integration", + description: + "Automate payment processes with secure smart contracts. Ensure funds are released only when invoice conditions are met, reducing intermediary dependencies.", + icon: , + }, + { + title: "Comprehensive Invoice Tracking", + description: + "Gain complete visibility into your invoice lifecycle. Monitor payment statuses, track financial performance, and manage all transactions seamlessly.", + icon: , + }, + ].map((feature, index) => ( +
+
+ {feature.icon}
-
-
-
- Aeroplane 2 -
-

Start Sending Your

-

Invoice Today!

-
- Aeroplane 1 -
- -
-
-
-
Chainvoice
-

Secure & Smart Invoicing

-
-
- -
-

Quick Links

-
    - {["Home", "Feature", "Treasure", "Service", "Invoice"].map((link) => ( -
  • - - {link} - -
  • - ))} -
-
-
-

Services

-
    - {["Blog & Article", "Terms ans Conditions", "Privacy Policy", "Contact Us", "Invoice"].map((link) => ( -
  • - - {link} - -
  • - ))} -
-
-
-

Contact

-
    - {["chainvoice@gmail.com"].map((link) => ( -
  • - - {link} - -
  • - ))} -
-
-
-
-
-
+

+ {feature.title} +

+

{feature.description}

+
+ ))} +
+
+
+ Invoice Illustration +
+ +
+
+ Aeroplane 2 +
+

+ {" "} + Start Sending Your +

+

+ Invoice Today! +

+
+ Aeroplane 1 +
- - - ) +
+
+
+
+ logo +

+ Chain + voice +

+
+

+ Secure & Smart Invoicing +

+
+
+
+

Quick Links

+
    + {["Home", "Feature", "Treasure", "Service", "Invoice"].map( + (link) => ( +
  • + + {link} + +
  • + ) + )} +
+
+
+

Services

+
    + {[ + "Blog & Article", + "Terms and Conditions", + "Privacy Policy", + "Contact Us", + "Invoice", + ].map((link) => ( +
  • + + {link} + +
  • + ))} +
+
+
+

Contact

+
    + {["chainvoice@gmail.com"].map((link) => ( +
  • + + {link} + +
  • + ))} +
+
+
+
+
+
+ + ); } export default Landing; diff --git a/frontend/src/page/ReceivedInvoice.jsx b/frontend/src/page/ReceivedInvoice.jsx index eb679cfa..daade8b6 100644 --- a/frontend/src/page/ReceivedInvoice.jsx +++ b/frontend/src/page/ReceivedInvoice.jsx @@ -38,6 +38,14 @@ const columns = [ function ReceivedInvoice() { const [page, setPage] = useState(0); const [rowsPerPage, setRowsPerPage] = useState(10); + const { data: walletClient } = useWalletClient(); + const { address } = useAccount(); + const [loading, setLoading] = useState(false); + const [receivedInvoices, setReceivedInvoice] = useState([]); + const [fee, setFee] = useState(0); + const [error, setError] = useState(null); + const [litReady, setLitReady] = useState(false); + const litClientRef = useRef(null); const handleChangePage = (event, newPage) => { setPage(newPage); @@ -48,14 +56,6 @@ function ReceivedInvoice() { setPage(0); }; - const { data: walletClient } = useWalletClient(); - const { address } = useAccount(); - const [loading, setLoading] = useState(false); - const [receivedInvoices, setReceivedInvoice] = useState([]); - const [fee, setFee] = useState(0); - const [litReady, setLitReady] = useState(false); - const litClientRef = useRef(null); - useEffect(() => { const initLit = async () => { try { @@ -80,15 +80,24 @@ function ReceivedInvoice() { }, []); useEffect(() => { - if (!walletClient || !litReady) return; + if (!walletClient || !address || !litReady) return; const fetchReceivedInvoices = async () => { try { setLoading(true); - + setError(null); const provider = new BrowserProvider(walletClient); const signer = await provider.getSigner(); + const network = await provider.getNetwork(); + + if (network.chainId != 5115) { + setError( + `Failed to load invoices. You're connected to the "${network.name}" network, but your invoices are on the "Citrea" testnet. Please switch to Sepolia and try again.` + ); + setLoading(false); + return; + } // 1. Setup Lit Node const litNodeClient = litClientRef.current; @@ -107,6 +116,15 @@ function ReceivedInvoice() { const res = await contract.getReceivedInvoices(address); console.log("getReceivedInvoices raw response:", res); + // First check if user has any invoices +// if (!res || !Array.isArray(res) || res.length === 0) { +// setReceivedInvoice([]); +// const fee = await contract.fee(); +// setFee(fee); +// return; +// } + + const decryptedInvoices = []; for (const invoice of res) { @@ -297,8 +315,12 @@ function ReceivedInvoice() { }} > {loading ? ( -

loading........

- ) : receivedInvoices?.length > 0 ? ( +

Loading invoices...

+ ) : error ? ( +

{error}

+ ) : receivedInvoices.length === 0 ? ( +

No invoices found

+ ) : ( <> {columns.map((column) => { + const value = invoice?.user[column.id] || ""; + if (column.id === "to") { return ( - ) : ( -

No invoices found

)} @@ -514,7 +536,7 @@ function ReceivedInvoice() {

From

- {drawerState.selectedInvoice.client.from} + {drawerState.selectedInvoice.user.address}

{`${drawerState.selectedInvoice.user.fname} ${drawerState.selectedInvoice.user.lname}`}

@@ -546,6 +568,7 @@ function ReceivedInvoice() { {drawerState.selectedInvoice?.items?.map((item, index) => ( +

@@ -567,7 +590,9 @@ function ReceivedInvoice() {

{/* Fee for invoice pay : {ethers.formatUnits(fee)} ETH */} - Fee for invoice pay : {ethers.formatUnits(fee)} ETH + + Fee for invoice pay : {parseFloat(ethers.formatUnits(fee))}{" "} + ETH

{" "} diff --git a/frontend/src/page/SentInvoice.jsx b/frontend/src/page/SentInvoice.jsx index 277de25b..f24eb7f4 100644 --- a/frontend/src/page/SentInvoice.jsx +++ b/frontend/src/page/SentInvoice.jsx @@ -52,6 +52,7 @@ function SentInvoice() { const [invoiceItems, setInvoiceItems] = useState([]); const [loading, setLoading] = useState(false); const [fee, setFee] = useState(0); + const [error, setError] = useState(null); const { address } = useAccount(); const [litReady, setLitReady] = useState(false); const litClientRef = useRef(null); @@ -216,8 +217,6 @@ function SentInvoice() { }; fetchSentInvoices(); - - console.log("invoices : ", sentInvoices); }, [walletClient, litReady]); const [drawerState, setDrawerState] = useState({ @@ -270,7 +269,7 @@ function SentInvoice() { return (

-

Your Sent Invoice Request

+

Your Sent Invoice Request

{loading ? ( -

Loading...

- ) : sentInvoices?.length > 0 ? ( +

Loading invoices...

+ ) : error ? ( +

{error}

+ ) : sentInvoices.length === 0 ? ( +

No invoices found

+ ) : ( <>
{item.description}
- {sentInvoices.length > 0 && - sentInvoices - .slice( - page * rowsPerPage, - page * rowsPerPage + rowsPerPage - ) - .map((invoice, index) => ( - - {columns.map((column) => { - const value = invoice?.client[column.id]; - if (column.id === "to") { - return ( - - {invoice.client?.address - ? `${invoice.client.address.substring( - 0, - 10 - )}...${invoice.client.address.substring( - invoice.client.address.length - 10 - )}` - : "N/A"} - - ); - } - if (column.id === "amountDue") { - return ( - - {invoice.amountDue} ETH - {/* {ethers.formatUnits(invoice.amountDue)} ETH */} - - ); - } - if (column.id === "isPaid") { - return ( - - - - ); - } - if (column.id === "detail") { - return ( - ( + + {columns.map((column) => { + const value = invoice?.client[column.id]; + if (column.id === "to") { + return ( + + {invoice.client.address + ? `${invoice.client.address.substring( + 0, + 10 + )}...${invoice.client.address.substring( + invoice.client.address.length - 10 + )}` + : "N/A"} + + ); + } + if (column.id === "amountDue") { + return ( + + {invoice.amountDue} ETH + + ); + } + if (column.id === "isPaid") { + return ( + + - - ); - } + {invoice.isPaid ? "Paid" : "Not Paid"} + + + ); + } + if (column.id === "detail") { return ( - {value} + ); - })} - - ))} + } + return ( + + {value} + + ); + })} + + ))}
@@ -442,8 +440,6 @@ function SentInvoice() { }} /> - ) : ( -

No invoices found

)} @@ -514,13 +510,11 @@ function SentInvoice() { {item.description} {item.qty.toString()} - {/* {ethers.formatUnits(item.unitPrice)} */} {item.unitPrice} {item.discount.toString()} {item.tax.toString()} - {/* {ethers.formatUnits(item.amount)} */} {item.amount} @@ -529,12 +523,12 @@ function SentInvoice() {

- Fee for invoice pay : {ethers.formatUnits(fee)} ETH + Fee for invoice pay : {parseFloat(ethers.formatUnits(fee))}{" "} + ETH

{" "} Amount:{" "} - {/* {ethers.formatUnits(drawerState.selectedInvoice.amountDue)}{" "} */} {drawerState.selectedInvoice.amountDue} ETH

diff --git a/frontend/src/page/Treasure.jsx b/frontend/src/page/Treasure.jsx index 8ccac602..e2cbaf7b 100644 --- a/frontend/src/page/Treasure.jsx +++ b/frontend/src/page/Treasure.jsx @@ -4,11 +4,24 @@ import { ChainvoiceABI } from "@/contractsABI/ChainvoiceABI"; import { BrowserProvider, Contract, ethers } from "ethers"; import { useState, useEffect } from "react"; import { useWalletClient } from "wagmi"; +import { + Loader2, + Shield, + Banknote, + Key, + Wallet, + DollarSignIcon, +} from "lucide-react"; const Treasure = () => { const [treasureAmount, setTreasureAmount] = useState(0); + const [fee, setFee] = useState(0); const { data: walletClient } = useWalletClient(); - const [loading, setLoading] = useState(false); + const [loading, setLoading] = useState({ + fetch: false, + setAddress: false, + withdraw: false, + }); const [treasuryAddress, setTreasuryAddress] = useState(""); const [newTreasuryAddress, setNewTreasuryAddress] = useState(""); @@ -16,18 +29,26 @@ const Treasure = () => { const fetchTreasureAmount = async () => { try { if (!walletClient) return; - setLoading(true); + setLoading((prev) => ({ ...prev, fetch: true })); const provider = new BrowserProvider(walletClient); const signer = await provider.getSigner(); - const contract = new Contract(import.meta.env.VITE_CONTRACT_ADDRESS, ChainvoiceABI, signer); - const amt = await contract.accumulatedFees(); + const contract = new Contract( + import.meta.env.VITE_CONTRACT_ADDRESS, + ChainvoiceABI, + signer + ); + const [amt, add, feeAmt] = await Promise.all([ + contract.accumulatedFees(), + contract.treasuryAddress(), + contract.fee(), + ]); setTreasureAmount(ethers.formatUnits(amt)); - const add = await contract.treasuryAddress(); setTreasuryAddress(add); + setFee(ethers.formatUnits(feeAmt)); } catch (error) { console.error("Error fetching treasure amount:", error); } finally { - setLoading(false); + setLoading((prev) => ({ ...prev, fetch: false })); } }; @@ -36,111 +57,194 @@ const Treasure = () => { const handleSetTreasuryAddress = async () => { if (!ethers.isAddress(newTreasuryAddress)) { - console.error("Invalid address:", newTreasuryAddress); + alert("Please enter a valid Ethereum address"); return; } try { - if (!walletClient || !newTreasuryAddress) return; - setLoading(true); + if (!walletClient) return; + setLoading((prev) => ({ ...prev, setAddress: true })); const provider = new BrowserProvider(walletClient); const signer = await provider.getSigner(); - const contract = new Contract(import.meta.env.VITE_CONTRACT_ADDRESS, ChainvoiceABI, signer); + const contract = new Contract( + import.meta.env.VITE_CONTRACT_ADDRESS, + ChainvoiceABI, + signer + ); const tx = await contract.setTreasuryAddress(newTreasuryAddress); await tx.wait(); setTreasuryAddress(newTreasuryAddress); setNewTreasuryAddress(""); + alert("Treasury address updated successfully!"); } catch (error) { console.error("Error setting treasury address:", error); + alert(error.message || "Failed to update treasury address"); } finally { - setLoading(false); + setLoading((prev) => ({ ...prev, setAddress: false })); } }; const handleWithdrawCollection = async () => { try { if (!walletClient) return; - setLoading(true); + setLoading((prev) => ({ ...prev, withdraw: true })); const provider = new BrowserProvider(walletClient); const signer = await provider.getSigner(); - const contract = new Contract(import.meta.env.VITE_CONTRACT_ADDRESS, ChainvoiceABI, signer); - const amt = await contract.withdraw(); - setTreasureAmount(ethers.formatUnits(amt)); + const contract = new Contract( + import.meta.env.VITE_CONTRACT_ADDRESS, + ChainvoiceABI, + signer + ); + const tx = await contract.withdrawFees(); + await tx.wait(); + const newAmt = await contract.accumulatedFees(); + setTreasureAmount(ethers.formatUnits(newAmt)); + alert("Funds withdrawn successfully!"); } catch (error) { console.error("Error withdrawing collection:", error); + alert(error.message || "Failed to withdraw funds"); } finally { - setLoading(false); + setLoading((prev) => ({ ...prev, withdraw: false })); } }; return ( -

- -
-

- Treasure Account -

-

- The treasure account is the account where all the fees collected from the platform are stored. -

- -
-

Treasure Amount:

-

- {loading ? "Loading..." : `${treasureAmount} ETH`} -

+
+
+
+
+
+
+
+
+ +
+

+ Treasury Vault +

+
+
+ + + Current Balance: + + + {loading.fetch ? ( + + ) : ( + `${treasureAmount} cBTC` + )} + +
+
+ + + Fee Per Transaction: + + + {loading.fetch ? ( + + ) : ( + `${fee} cBTC` + )} + +
+
+ + + Admin Access Only + +
+
+
+
+
-
-

Treasury Address:

-

- {loading ? "Loading..." : treasuryAddress} + {/* Content */} +

+

+ Treasury Controls +

+

+ Secure management of platform funds and treasury settings

-
-
-

Set New Treasury Address

-
- setNewTreasuryAddress(e.target.value)} - /> +
+
+ +

Current Treasury

+
+
+

+ {loading.fetch ? ( + + ) : treasuryAddress ? ( + treasuryAddress + ) : ( + "Not configured" + )} +

+
+
+ +
+
+ +

+ Update Treasury Address +

+
+
+ setNewTreasuryAddress(e.target.value)} + className="flex-1 bg-gray-800 border-gray-700 text-white font-mono text-sm" + /> + +
+

+ Requires contract owner privileges +

+
+ + {/* Withdraw */} +
+
+ +

Funds Withdrawal

+
+

+ Will be sent to the current treasury address +

-

- Disclaimer: Only the owner of the contract can set the treasury address. -

- -
-

Withdraw Collection

- -
-
- -
- Treasure Illustration
); }; -export default Treasure; \ No newline at end of file +export default Treasure; diff --git a/frontend/{ b/frontend/{ new file mode 100644 index 00000000..e69de29b