+
+
DESCRIPTION
+
QTY
+
UNIT PRICE
+
DISCOUNT
+
TAX(%)
+
AMOUNT
+ {/*
AMOUNT
*/}
-
+
+
{itemData.map((_, index) => (
-
-
Total : {totalAmountDue}
-
+
+
+
+
+ Total:
+
+ {/* {totalAmountDue} ETH */}
+ {totalAmountDue} cBTC
+
+
+
+
+
+
+ {/* Form Actions */}
+
+
+
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 && (
-
-
-
- handleScrollToHome()}>Home
-
- -
- handleScrollToFeature()}>Feature
-
- -
- handleScrollToService()}>Services
-
-
+
+
+
+
{
+ navigate("/");
+ closeMobileMenu();
+ }}
+ >
+
+
+ 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 (
+
+
-
-
+ {/* 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}
-
- ))}
-
-
-
-

+
+
+
+ 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}
-
-
-
-

-
-
Start Sending Your
-
Invoice Today!
-
-

-
-
-
-
+
+ {feature.title}
+
+
{feature.description}
+
+ ))}
+
+
+
+

+
+
+
+
+

+
+
+ {" "}
+ Start Sending Your
+
+
+ Invoice Today!
+
+
+

+
-
- >
- )
+
+
+ >
+ );
}
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) => (
+
| {item.description} |
@@ -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
+ ) : (
<>
- {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 (
-
-
- {invoice.isPaid ? "Paid" : "Not Paid"}
-
-
- );
- }
- 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"
+ />
+
+ {loading.setAddress ? (
+
+ ) : (
+ "Update"
+ )}
+
+
+
+ Requires contract owner privileges
+
+
+
+ {/* Withdraw */}
+
+
+
+
Funds Withdrawal
+
- {loading ? "Setting..." : "Set Address"}
+ {loading.withdraw ? (
+
+ ) : null}
+ Transfer {treasureAmount} cBTC
+
+ Will be sent to the current treasury address
+
-
- Disclaimer: Only the owner of the contract can set the treasury address.
-
-
-
-
Withdraw Collection
-
- {loading ? "Withdrawing..." : "Withdraw Collection"}
-
-
-
-
-
-
);
};
-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