Skip to content

Commit

Permalink
Create election (#53)
Browse files Browse the repository at this point in the history
* Switching over to @metamask/sdk-react-ui

* A very basic web3 contract call and send
  • Loading branch information
mickeymond authored Sep 15, 2024
1 parent fa3339a commit e72db92
Show file tree
Hide file tree
Showing 16 changed files with 266 additions and 155 deletions.
8 changes: 8 additions & 0 deletions blockchain/hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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],
},
}
};

Expand Down
5 changes: 3 additions & 2 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 1 addition & 2 deletions frontend/src/app/elections/[id]/results/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
'use client';
import { useState } from 'react';


export default function ElectionResults() {
const totalVoters = 42; // Total eligible voters
Expand Down
132 changes: 132 additions & 0 deletions frontend/src/app/elections/add/components/AddElectionForm.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLFormElement>) => {
// 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 (
<form onSubmit={createElection}>
<div className="mb-10">
<label
className="block text-[16px] mb-4 text-white font-space-grotesk"
htmlFor="title"
>
Election Title
</label>
<input
className="w-full py-3 border-b border-gray-300 bg-[#070707] text-subtle-text placeholder:text-subtle-text placeholder:text-[12px] lg:placeholder:text-[16px] focus:border-gray-300 focus:outline-none"
type="text"
id="title"
name="title"
placeholder="Eg. 2024 SRC President - UG" />
</div>

<div className="mb-10">
<label
className="block text-[16px] mb-4 text-white font-space-grotesk"
htmlFor="description"
>
Description
</label>
<textarea
className="w-full py-3 border-b border-gray-300 bg-[#070707] text-subtle-text placeholder:text-subtle-text placeholder:text-[12px] lg:placeholder:text-[16px] focus:border-gray-300 focus:outline-none"
id="description"
name="description"
rows={1}
placeholder="Write a brief summary about the purpose of the election"
></textarea>
</div>

<div className="mb-12">
<label className="block text-[16px] mb-4 text-white font-space-grotesk">
Election Period
</label>
<div className="flex flex-col sm:flex-row items-start sm:items-center gap-6">
{/* Start Date Picker */}
<input
type="date"
name="startDate"
className="w-full sm:w-auto py-3 border border-gray-300 bg-[#070707] text-subtle-text rounded-lg px-4 focus:border-gray-300 focus:outline-none" />

{/* End Date Picker */}
<input
type="date"
name="endDate"
className="w-full sm:w-auto py-3 border border-gray-300 bg-[#070707] text-subtle-text rounded-lg px-4 focus:border-gray-300 focus:outline-none" />
</div>
</div>

<div className="mb-4">
<label className="block text-[16px] mb-2 text-white font-space-grotesk">
Election Type
</label>
<div className="flex flex-col sm:flex-row gap-4 sm:gap-8">
<label className="flex items-center text-subtle-text">
<input
className="mr-2 appearance-none w-4 h-4 border border-gray-300 rounded-full bg-[#070707] checked:bg-secondary cursor-pointer"
type="radio"
name="electionType"
value="public"
style={{
accentColor: "#4C9FE4",
}} />
Public
</label>
<label className="flex items-center text-subtle-text">
<input
className="mr-2 appearance-none w-4 h-4 border border-gray-300 rounded-full bg-[#070707] checked:bg-secondary cursor-pointer"
type="radio"
name="electionType"
value="private"
style={{
accentColor: "#4C9FE4",
}} />
Private
</label>
</div>
</div>

<button
className="mt-6 bg-gradient-to-r from-primary to-[#4595DF] hover:from-[#4595DF] hover:to-primary cursor-pointer text-white px-14 py-3 rounded-3xl w-full sm:w-auto float-none sm:float-right"
type="submit"
>
next
</button>
</form>
);
}
102 changes: 7 additions & 95 deletions frontend/src/app/elections/add/page.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<>
<div className="mb-8">
<TopNav disabled />
</div>
<div className="w-full mb-28">
<form>
<div className="mb-10">
<label
className="block text-[16px] mb-4 text-white font-space-grotesk"
htmlFor="title"
>
Election Title
</label>
<input
className="w-full py-3 border-b border-gray-300 bg-[#070707] text-subtle-text placeholder:text-subtle-text placeholder:text-[12px] lg:placeholder:text-[16px] focus:border-gray-300 focus:outline-none"
type="text"
id="title"
placeholder="Eg. 2024 SRC President - UG" />
</div>

<div className="mb-10">
<label
className="block text-[16px] mb-4 text-white font-space-grotesk"
htmlFor="description"
>
Description
</label>
<textarea
className="w-full py-3 border-b border-gray-300 bg-[#070707] text-subtle-text placeholder:text-subtle-text placeholder:text-[12px] lg:placeholder:text-[16px] focus:border-gray-300 focus:outline-none"
id="description"
rows={1}
placeholder="Write a brief summary about the purpose of the election"
></textarea>
</div>

<div className="mb-12">
<label className="block text-[16px] mb-4 text-white font-space-grotesk">
Election Period
</label>
<div className="flex flex-col sm:flex-row items-start sm:items-center gap-6">
{/* Start Date Picker */}
<input
type="date"
className="w-full sm:w-auto py-3 border border-gray-300 bg-[#070707] text-subtle-text rounded-lg px-4 focus:border-gray-300 focus:outline-none"
value={startDate}
onChange={(e) => setStartDate(e.target.value)} />

{/* End Date Picker */}
<input
type="date"
className="w-full sm:w-auto py-3 border border-gray-300 bg-[#070707] text-subtle-text rounded-lg px-4 focus:border-gray-300 focus:outline-none"
value={endDate}
onChange={(e) => setEndDate(e.target.value)} />
</div>
</div>

<div className="mb-4">
<label className="block text-[16px] mb-2 text-white font-space-grotesk">
Election Type
</label>
<div className="flex flex-col sm:flex-row gap-4 sm:gap-8">
<label className="flex items-center text-subtle-text">
<input
className="mr-2 appearance-none w-4 h-4 border border-gray-300 rounded-full bg-[#070707] checked:bg-secondary cursor-pointer"
type="radio"
name="election-type"
value="public"
style={{
accentColor: "#4C9FE4",
}} />
Public
</label>
<label className="flex items-center text-subtle-text">
<input
className="mr-2 appearance-none w-4 h-4 border border-gray-300 rounded-full bg-[#070707] checked:bg-secondary cursor-pointer"
type="radio"
name="election-type"
value="private"
style={{
accentColor: "#4C9FE4",
}} />
Private
</label>
</div>
</div>

<Link
href={'/elections/1'}
className="mt-6 bg-gradient-to-r from-primary to-[#4595DF] hover:from-[#4595DF] hover:to-primary cursor-pointer text-white px-14 py-3 rounded-3xl w-full sm:w-auto float-none sm:float-right"
type="submit"
>
next
</Link>
</form>
<AddElectionForm />
</div>
</>
);
Expand Down
38 changes: 38 additions & 0 deletions frontend/src/app/elections/components/AllElections.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div>
{elections.map(address => (
<div key={address}>
<Link href={`/elections/${address}`}>{address}</Link>
</div>
))}
</div>
);
}
2 changes: 1 addition & 1 deletion frontend/src/app/elections/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export default function ElectionsLayout({
children: React.ReactNode;
}>) {
return (
<div className="h-full px-4 lg:px-0">
<div className="h-full px-4 lg:px-0 text-text">
<div className="md:w-[80%] m-auto flex flex-col lg:flex-row mt-14 gap-14">
<SideBarWrapper />
<div className="mt-8 lg:mt-0 lg:ml-10 w-full lg:w-full">
Expand Down
22 changes: 22 additions & 0 deletions frontend/src/app/elections/page.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="p-4 md:p-6 lg:p-8 mb-32 lg:mb-0">
<div className="flex flex-col items-center text-center mb-6">
<h1 className="gradient-text-vertical text-[32px] md:text-[40px] lg:text-[50px] font-bold font-space-grotesk">
Have a look at all elections
</h1>
<p className="text-subtle-text text-[16px] md:text-[18px]">
Turpis non molestie amet tortor. Diam amet volutpat
</p>
</div>
<AllElections />
</div>
);
}
Loading

0 comments on commit e72db92

Please sign in to comment.