diff --git a/blockchain/hardhat.config.ts b/blockchain/hardhat.config.ts index 8e1dcf7..0ccedcb 100644 --- a/blockchain/hardhat.config.ts +++ b/blockchain/hardhat.config.ts @@ -21,6 +21,14 @@ const config: HardhatUserConfig = { url: `https://rpc.linea.build/`, accounts: [process.env.PRIVATE_KEY as string], }, + scroll_sepolia: { + url: `https://sepolia-rpc.scroll.io/`, + accounts: [process.env.PRIVATE_KEY as string], + }, + scroll_mainnet: { + url: `https://rpc.scroll.io/`, + accounts: [process.env.PRIVATE_KEY as string], + }, } }; diff --git a/frontend/package.json b/frontend/package.json index 3ba36ce..a928dae 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -9,10 +9,11 @@ "lint": "next lint" }, "dependencies": { - "metamask-react": "^2.7.0", + "@metamask/sdk-react-ui": "^0.28.4", "next": "14.2.5", "react": "^18", - "react-dom": "^18" + "react-dom": "^18", + "web3": "^4.12.1" }, "devDependencies": { "@types/node": "^20", diff --git a/frontend/src/app/elections/[id]/results/page.tsx b/frontend/src/app/elections/[id]/results/page.tsx index c11e3cc..f983f18 100644 --- a/frontend/src/app/elections/[id]/results/page.tsx +++ b/frontend/src/app/elections/[id]/results/page.tsx @@ -1,5 +1,4 @@ -'use client'; -import { useState } from 'react'; + export default function ElectionResults() { const totalVoters = 42; // Total eligible voters diff --git a/frontend/src/app/elections/add/components/AddElectionForm.tsx b/frontend/src/app/elections/add/components/AddElectionForm.tsx new file mode 100644 index 0000000..bba4c0b --- /dev/null +++ b/frontend/src/app/elections/add/components/AddElectionForm.tsx @@ -0,0 +1,132 @@ +"use client"; +import { ELECTION_FACTORY_ABI, ELECTION_FACTORY_ADDRESS } from "@/contracts/ElectionFactory"; +import { useSDK } from "@metamask/sdk-react-ui"; +import { useRouter } from "next/navigation"; +import { SyntheticEvent } from "react"; +import Web3 from "web3"; + +export default function AddElectionForm() { + const router = useRouter(); + const { provider, connected, account, sdk } = useSDK(); + + const createElection = async (event: SyntheticEvent) => { + // Prevent default form submit behaviour + event.preventDefault(); + // Use FormData API to collect data + const formData = new FormData(event.currentTarget); + // Send transaction + if (connected) { + // Initialize web3 + const web3 = new Web3(provider); + // Initialize contract + const electionFactory = new web3.eth.Contract(ELECTION_FACTORY_ABI, ELECTION_FACTORY_ADDRESS); + // Invote method + const receipt = await electionFactory.methods.createElection( + formData.get('title'), + formData.get('description'), + formData.get('electionType') === 'public', + new Date(formData.get('startDate') as any).valueOf(), + new Date(formData.get('endDate') as any).valueOf() + ).send({ from: account }); + // Navigate to detail page + if (receipt.events?.ElectionCreated?.returnValues?.electionAddress) { + return router.push(`/elections/${receipt.events?.ElectionCreated?.returnValues?.electionAddress}`) + } else { + alert('You may have interacted with the wrong network'); + } + } else { + await sdk?.connect(); + } + } + + return ( +
+
+ + +
+ +
+ + +
+ +
+ +
+ {/* Start Date Picker */} + + + {/* End Date Picker */} + +
+
+ +
+ +
+ + +
+
+ + +
+ ); +} \ No newline at end of file diff --git a/frontend/src/app/elections/add/page.tsx b/frontend/src/app/elections/add/page.tsx index a7fbb86..80ef148 100644 --- a/frontend/src/app/elections/add/page.tsx +++ b/frontend/src/app/elections/add/page.tsx @@ -1,107 +1,19 @@ -"use client"; import TopNav from "@/components/TopNav"; -import Link from "next/link"; -import { useState } from "react"; +import AddElectionForm from "./components/AddElectionForm"; +import { Metadata } from "next"; -export default function AddElection() { - const [startDate, setStartDate] = useState(""); - const [endDate, setEndDate] = useState(""); +export const metadata: Metadata = { + title: "ABVS | Add Election", +} +export default function AddElection() { return ( <>
-
-
- - -
- -
- - -
- -
- -
- {/* Start Date Picker */} - setStartDate(e.target.value)} /> - - {/* End Date Picker */} - setEndDate(e.target.value)} /> -
-
- -
- -
- - -
-
- - - next - -
+
); diff --git a/frontend/src/app/elections/components/AllElections.tsx b/frontend/src/app/elections/components/AllElections.tsx new file mode 100644 index 0000000..8844926 --- /dev/null +++ b/frontend/src/app/elections/components/AllElections.tsx @@ -0,0 +1,38 @@ +'use client'; +import { ELECTION_FACTORY_ABI, ELECTION_FACTORY_ADDRESS } from "@/contracts/ElectionFactory"; +import { useSDK } from "@metamask/sdk-react-ui"; +import Link from "next/link"; +import { useEffect, useState } from "react"; +import Web3 from "web3"; + +export default function AllElections() { + const [elections, setElections] = useState([]); + const { provider, connected } = useSDK(); + + useEffect(() => { + // Call transaction + if (connected) { + // Initialize web3 + const web3 = new Web3(provider); + // Initialize contract + const electionFactory = new web3.eth.Contract(ELECTION_FACTORY_ABI, ELECTION_FACTORY_ADDRESS); + // Invote method + electionFactory + .methods + .getElections() + .call() + .then((addresses) => setElections(addresses as [])) + .catch(console.log); + } + }, [provider, connected]) + + return ( +
+ {elections.map(address => ( +
+ {address} +
+ ))} +
+ ); +} \ No newline at end of file diff --git a/frontend/src/app/elections/layout.tsx b/frontend/src/app/elections/layout.tsx index b3418c1..e8e5a58 100644 --- a/frontend/src/app/elections/layout.tsx +++ b/frontend/src/app/elections/layout.tsx @@ -6,7 +6,7 @@ export default function ElectionsLayout({ children: React.ReactNode; }>) { return ( -
+
diff --git a/frontend/src/app/elections/page.tsx b/frontend/src/app/elections/page.tsx new file mode 100644 index 0000000..ae50c73 --- /dev/null +++ b/frontend/src/app/elections/page.tsx @@ -0,0 +1,22 @@ +import { Metadata } from "next"; +import AllElections from "./components/AllElections"; + +export const metadata: Metadata = { + title: "ABVS | Elections", +} + +export default function Elections() { + return ( +
+
+

+ Have a look at all elections +

+

+ Turpis non molestie amet tortor. Diam amet volutpat +

+
+ +
+ ); +} \ No newline at end of file diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index 777cfe0..90719e5 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -1,18 +1,17 @@ -"use client"; -import type { Metadata } from "next"; +import { Metadata } from "next"; import { Space_Grotesk, Roboto_Flex } from "next/font/google"; import "./globals.css"; import Header from "@/components/Header"; import Footer from "@/components/Footer"; -import { MetaMaskProvider } from "metamask-react"; +import WalletProvider from "@/components/WalletProvider"; const space_grotesk = Space_Grotesk({ subsets: ['latin'], variable: '--font-space-grotesk' }); const roboto_flex = Roboto_Flex({ subsets: ['latin'], variable: '--font-roboto-flex' }); -// export const metadata: Metadata = { -// title: "ABVS", -// description: "BUIDL with Mowblox", -// } +export const metadata: Metadata = { + title: "ABVS", + description: "BUIDL with Mowblox", +} export default function RootLayout({ children, @@ -21,13 +20,13 @@ export default function RootLayout({ }>) { return ( - - -
- {children} -
+ + +
+ {children} +
+ - ); } diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index 91b9729..921ee6d 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -18,7 +18,7 @@ export const metadata: Metadata = { export default function Landing() { return ( -
+
create election - +
diff --git a/frontend/src/components/ConnectButton.tsx b/frontend/src/components/ConnectButton.tsx deleted file mode 100644 index a002d66..0000000 --- a/frontend/src/components/ConnectButton.tsx +++ /dev/null @@ -1,23 +0,0 @@ -// app/components/ConnectButton.tsx - - -import { useMetaMask } from 'metamask-react'; - -const ConnectButton = () => { - const { status, connect, account } = useMetaMask(); - - const handleConnect = () => { - if (status === 'notConnected') { - connect(); - } - }; - - return ( -
- {status === 'connected' ? `Connected: ${account}` : 'Connect MetaMask'} - -
- ); -}; - -export default ConnectButton; diff --git a/frontend/src/components/Header.tsx b/frontend/src/components/Header.tsx index 593d5f0..ed6151d 100644 --- a/frontend/src/components/Header.tsx +++ b/frontend/src/components/Header.tsx @@ -1,5 +1,7 @@ +'use client'; import Image from "next/image"; -import ConnectButton from "./ConnectButton"; +import { MetaMaskButton } from "@metamask/sdk-react-ui"; +import Link from "next/link"; export default function Header() { @@ -7,20 +9,18 @@ export default function Header() {
diff --git a/frontend/src/components/PanelComponent.tsx b/frontend/src/components/PanelComponent.tsx index 020be13..e7c9a39 100644 --- a/frontend/src/components/PanelComponent.tsx +++ b/frontend/src/components/PanelComponent.tsx @@ -1,5 +1,4 @@ "use client" - import Image from "next/image"; import { Fragment, useState } from "react"; @@ -12,13 +11,13 @@ interface PanelProps { } // A Reusable panel component for ABVS which allows custom callbacks -export default function PanelComponent ({children, title, icon, index, callback}: PanelProps) { - const [panelState, setPanelState] = useState(index==1 ? true : false) +export default function PanelComponent({ children, title, icon, index, callback }: PanelProps) { + const [panelState, setPanelState] = useState(index == 1 ? true : false) const togglePanel = () => setPanelState(!panelState) - + // Function to call custom callback if exists else toggles panel const handlePanelClick = () => { - if(callback){ + if (callback) { return callback() } return togglePanel() @@ -41,9 +40,9 @@ export default function PanelComponent ({children, title, icon, index, callback} />
{ - children && panelState ? - {children} : - + children && panelState ? + {children} : + } ); diff --git a/frontend/src/components/SideBarWrapper.tsx b/frontend/src/components/SideBarWrapper.tsx index bdfc362..3475f46 100644 --- a/frontend/src/components/SideBarWrapper.tsx +++ b/frontend/src/components/SideBarWrapper.tsx @@ -7,6 +7,7 @@ export default function SideBarWrapper() { const pathname = usePathname(); const excludes = [ + `/elections`, `/elections/${id}/results`, ]; diff --git a/frontend/src/components/WalletProvider.tsx b/frontend/src/components/WalletProvider.tsx new file mode 100644 index 0000000..5648903 --- /dev/null +++ b/frontend/src/components/WalletProvider.tsx @@ -0,0 +1,21 @@ +"use client"; +import { MetaMaskUIProvider } from "@metamask/sdk-react-ui"; + +export default function WalletProvider({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + {children} + + ); +} \ No newline at end of file diff --git a/frontend/src/contracts/ElectionFactory.ts b/frontend/src/contracts/ElectionFactory.ts new file mode 100644 index 0000000..15ea6d9 --- /dev/null +++ b/frontend/src/contracts/ElectionFactory.ts @@ -0,0 +1,2 @@ +export const ELECTION_FACTORY_ADDRESS = '0x3e5213A1924b00f7aaAb88fBDF034a9910386FF1'; +export const ELECTION_FACTORY_ABI = [{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"electionAddress","type":"address"}],"name":"ElectionCreated","type":"event"},{"inputs":[{"internalType":"string","name":"_title","type":"string"},{"internalType":"string","name":"_description","type":"string"},{"internalType":"bool","name":"_isPublic","type":"bool"},{"internalType":"uint256","name":"_startDate","type":"uint256"},{"internalType":"uint256","name":"_endDate","type":"uint256"}],"name":"createElection","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_electionID","type":"uint256"}],"name":"deleteElection","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getElections","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getTotalElections","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}] \ No newline at end of file