diff --git a/client/index.html b/client/index.html index 91a8d2a..eb50c9d 100644 --- a/client/index.html +++ b/client/index.html @@ -4,7 +4,7 @@ - Voting System + Online Voting System
diff --git a/client/public/vite.svg b/client/public/vite.svg deleted file mode 100644 index e7b8dfb..0000000 --- a/client/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/client/src/App.jsx b/client/src/App.jsx index d9a6e60..a2b94d7 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -1,22 +1,39 @@ -import { BrowserRouter, Route, Routes } from "react-router"; +import { Route, Routes, useLocation } from "react-router"; import "./App.css"; import Login from "./components/Login.jsx"; import Register from "./components/Register.jsx"; -import NavSideBar from "./components/NavSideBar.jsx"; import Dashboard from "./components/Dashboard.jsx"; import CandidateList from "./components/CandidateList.jsx"; +import VoterList from "./components/VoterList.jsx"; +import Position from "./components/Position.jsx"; +import NavSideBar from "./components/NavSideBar.jsx"; +import BallotPosition from "./components/BallotPosition.jsx"; +import Header from "./components/Header.jsx"; function App() { + const location = useLocation(); + + // Paths where the sidebar should not be shown + const noSidebarPaths = ["/", "/login", "/register"]; + const noHeaderPaths = ["/", "/login", "/register"]; + return ( - - - - } /> - } /> - } /> - } /> - - + <> +
+ {!noHeaderPaths.includes(location.pathname) &&
} + {!noSidebarPaths.includes(location.pathname) && } + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + +
+ ); } diff --git a/client/src/assets/gov_nepal.png b/client/src/assets/gov_nepal.png new file mode 100644 index 0000000..57b722a Binary files /dev/null and b/client/src/assets/gov_nepal.png differ diff --git a/client/src/assets/react.svg b/client/src/assets/react.svg deleted file mode 100644 index 6c87de9..0000000 --- a/client/src/assets/react.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/client/src/assets/voting.png b/client/src/assets/voting.png new file mode 100644 index 0000000..5eeef32 Binary files /dev/null and b/client/src/assets/voting.png differ diff --git a/client/src/components/BallotPosition.jsx b/client/src/components/BallotPosition.jsx new file mode 100644 index 0000000..83d4222 --- /dev/null +++ b/client/src/components/BallotPosition.jsx @@ -0,0 +1,40 @@ +import { CANDIDATE_DATA } from "./Data"; +import { POSITION_DATA } from "./Data"; + +function BallotPosition() { + const candidates = CANDIDATE_DATA; + const positions = POSITION_DATA; + + return ( +
+

Ballot Position

+
+ {positions.map((position) => ( +
+ {position.description} +
+ {candidates.map((candidate) => { + if (candidate.position === position.description) + return ( + + ); + })} +
+
+ ))} + +
+
+ ); +} + +export default BallotPosition; diff --git a/client/src/components/CandidateList.jsx b/client/src/components/CandidateList.jsx index 9427e32..ca9d18b 100644 --- a/client/src/components/CandidateList.jsx +++ b/client/src/components/CandidateList.jsx @@ -1,15 +1,63 @@ +import { useState } from "react"; import { CANDIDATE_DATA } from "./Data"; +import ModalForm from "./ModalForm"; function CandidateList() { - const candidates = CANDIDATE_DATA; - const candidateList = candidates.map((candidate) => ( - + const [modalOpen, setModalOpen] = useState(false); + const [candidates, setCandidates] = useState(CANDIDATE_DATA); + const [candidateToEdit, setCandidateToEdit] = useState(null); + + const fields = [ + { name: "firstName", label: "First Name", type: "text" }, + { name: "lastName", label: "Last Name", type: "text" }, + { name: "position", label: "Position", type: "text" }, + ]; + + const validateCandidateForm = (formState) => { + const errors = []; + for (const field of fields) { + if (!formState[field.name]) { + errors.push(`${field.label}`); + } + } + return errors.length > 0 ? errors : null; + }; + + const handleDeleteCandidate = (targetIndex) => { + setCandidates(candidates.filter((_, idx) => idx !== targetIndex)); + }; + + const handleEditCandidate = (idx) => { + setCandidateToEdit(idx); + setModalOpen(true); + }; + + const handleSubmit = (newCandidate) => { + candidateToEdit === null + ? setCandidates([...candidates, newCandidate]) + : setCandidates( + candidates.map((currCandidate, idx) => { + if (idx !== candidateToEdit) return currCandidate; + return newCandidate; + }) + ); + }; + + const candidateList = candidates.map((candidate, idx) => ( + {candidate.firstName} {candidate.lastName} {candidate.position} - - + + )); @@ -17,15 +65,43 @@ function CandidateList() { return ( <>
+
Candidates
- - - - - - - {candidateList} + + + + + + + + + + {candidates.length > 0 ? ( + candidateList + ) : ( + + + + )} +
First NameLast NamePositionTools
First NameLast NamePositionTools
+ There are no candidates to display. +
+ + {modalOpen && ( + { + setModalOpen(false); + setCandidateToEdit(null); + }} + fields={fields} + onSubmit={handleSubmit} + defaultValue={ + candidateToEdit !== null && candidates[candidateToEdit] + } + validateFormCustom={validateCandidateForm} + /> + )}
); diff --git a/client/src/components/Dashboard.jsx b/client/src/components/Dashboard.jsx index 3eea708..07b0af6 100644 --- a/client/src/components/Dashboard.jsx +++ b/client/src/components/Dashboard.jsx @@ -1,32 +1,33 @@ -import { useState } from "react"; +import { VOTER_DATA } from "./Data"; +import { CANDIDATE_DATA } from "./Data"; +import { POSITION_DATA } from "./Data"; function Dashboard() { - const [positionNum] = useState(0); - const [candidateNum] = useState(0); - const [voterNum] = useState(0); + const voterNum = VOTER_DATA.length; + const candidateNum = CANDIDATE_DATA.length; + const positionNum = POSITION_DATA.length; return ( -
-
-
-

{positionNum}

-

No. of Positions

-
+
+
+
Dashboard
+
+
+

{positionNum}

+

No. of Positions

+
-
-

{candidateNum}

-

No. of Candidates

-
+
+

{candidateNum}

+

No. of Candidates

+
-
-

{voterNum}

-

Total Voters

+
+

{voterNum}

+

Total Voters

+
- -
-

Voting Tally

-
); } diff --git a/client/src/components/Data.js b/client/src/components/Data.js index be50210..a01b550 100644 --- a/client/src/components/Data.js +++ b/client/src/components/Data.js @@ -7,16 +7,56 @@ const CANDIDATE_DATA = [ }, { id: "c-1", + firstName: "Salmon", + lastName: "Kurosaki", + position: "President", + }, + { + id: "c-2", firstName: "Salman", lastName: "Prajapati", position: "Vice President", }, { - id: "c-2", + id: "c-3", firstName: "Surja", lastName: "Shrestha", position: "Vice President", }, ]; +const VOTER_DATA = [ + { + id: "v-0", + firstName: "Ranjita", + lastName: "Shrestha", + }, + { + id: "v-1", + firstName: "Salma", + lastName: "Prajapati", + }, + { + id: "v-2", + firstName: "Suraj", + lastName: "Shrestha", + }, + { + id: "v-3", + firstName: "John", + lastName: "Shrestha", + }, +]; + +const POSITION_DATA = [ + { + id: "p-0", + description: "President", + maxVote: 1, + }, + { id: "p-1", description: "Vice President", maxVote: 1 }, +]; + export { CANDIDATE_DATA }; +export { VOTER_DATA }; +export { POSITION_DATA }; diff --git a/client/src/components/Header.jsx b/client/src/components/Header.jsx new file mode 100644 index 0000000..ec9686c --- /dev/null +++ b/client/src/components/Header.jsx @@ -0,0 +1,14 @@ +function Header() { + return ( +
+ {/*
e-Voting System
*/} +
TOGGLE
+
+
Voter Account
+ +
+
+ ); +} + +export default Header; diff --git a/client/src/components/Login.jsx b/client/src/components/Login.jsx index f74e831..809d4bb 100644 --- a/client/src/components/Login.jsx +++ b/client/src/components/Login.jsx @@ -1,73 +1,74 @@ import { useState } from "react"; import { Link, useNavigate } from "react-router"; +import gov_nepal from "../assets/gov_nepal.png"; function Login() { - const [email, setEmail] = useState(""); const [voterid, setVoterid] = useState(""); const [password, setPassword] = useState(""); const navigate = useNavigate(); - function handleSubmit(e) { + const handleSubmit = (e) => { e.preventDefault(); - const userData = { email, voterid, password }; + const userData = { voterid, password }; console.log(userData); navigate("/"); - } + }; return ( <> -
Voting System
-
-
-

Login

- {/* - */} - - - setVoterid(e.target.value)} - > - - - setEmail(e.target.value)} - > +
+ Government of Nepal Emblem +
+

+ E-voting System +

+

+ Login +

+
+
+ + setVoterid(e.target.value)} + className="border border-gray-400 rounded-lg p-2 mb-4 w-full focus:outline-none focus:ring-2 focus:ring-blue-500 text-[19px]" + placeholder="Voter ID " + > - - setPassword(e.target.value)} - > + setPassword(e.target.value)} + className="border border-gray-400 rounded-md p-2 mb-4 w-full focus:outline-none focus:ring-2 focus:ring-blue-500 text-[19px]" + placeholder="Password" + > - - Register +
+ + + Don't have an account? Register Here! + +
+ +
+
- +
); } diff --git a/client/src/components/ModalForm.jsx b/client/src/components/ModalForm.jsx new file mode 100644 index 0000000..73574c5 --- /dev/null +++ b/client/src/components/ModalForm.jsx @@ -0,0 +1,80 @@ +import { useState } from "react"; + +function ModalForm({ + onSubmit, + fields, + defaultValue, + closeModal, + validateFormCustom, +}) { + const [formState, setFormState] = useState(defaultValue); + const [errors, setErrors] = useState(""); + + // Generic form validation + const validateForm = () => { + if (validateFormCustom) { + // Use custom validation if provided + const validationErrors = validateFormCustom(formState); + console.log(validationErrors); + if (validationErrors) { + setErrors(validationErrors.join(", ")); + return false; + } + } else { + const errorFields = Object.entries(formState) + .filter(([key, value]) => !value) + .map(([key]) => key); + if (errorFields.length > 0) { + setErrors(errorFields.join(", ")); + return false; + } + } + + setErrors(""); + return true; + }; + + const handleChange = (e) => { + setFormState({ ...formState, [e.target.name]: e.target.value }); + }; + + const handleSubmit = (e) => { + e.preventDefault(); + if (!validateForm()) { + return; + } + onSubmit(formState); + closeModal(); + }; + + return ( +
{ + if (e.target.className === "modal-container") closeModal(); + }} + > +
+
+ {fields.map((field) => ( +
+ + +
+ ))} + {errors &&
{`Please include: ${errors}`}
} + +
+
+
+ ); +} + +export default ModalForm; diff --git a/client/src/components/NavSideBar.jsx b/client/src/components/NavSideBar.jsx index d5b1ed0..eec859e 100644 --- a/client/src/components/NavSideBar.jsx +++ b/client/src/components/NavSideBar.jsx @@ -3,29 +3,34 @@ import { Link } from "react-router"; function NavSideBar() { return (
-

Voting System

-
Voter Account
+

e-Voting System

+ {/*
Voter Account
*/}

REPORTS

- - Dashboard + +
  • Dashboard
  • - Votes

    MANAGE

    - Voters - Positions - - Candidates + +
  • Voters
  • + + +
  • Positions
  • + + +
  • Candidates
  • SETTINGS

    - Ballot Position - Election Title + +
  • Ballot Position
  • + +
  • Election Title
  • ); diff --git a/client/src/components/Position.jsx b/client/src/components/Position.jsx new file mode 100644 index 0000000..47d7abf --- /dev/null +++ b/client/src/components/Position.jsx @@ -0,0 +1,105 @@ +import { useState } from "react"; +import ModalForm from "./ModalForm"; +import { POSITION_DATA } from "./Data"; + +function Position() { + const [modalOpen, setModalOpen] = useState(false); + const [positions, setPositions] = useState(POSITION_DATA); + const [positionToEdit, setPositionToEdit] = useState(null); + + const fields = [ + { name: "description", label: "Description", type: "text" }, + { name: "maxVote", label: "Maximum Vote", type: "number" }, + ]; + + const validatePositionForm = (formState) => { + const errors = []; + for (const field of fields) { + if (!formState[field.name]) { + errors.push(`${field.label}`); + } + } + return errors.length > 0 ? errors : null; + }; + + const handleDeletePosition = (targetIndex) => { + setPositions(positions.filter((_, idx) => idx !== targetIndex)); + }; + + const handleEditPosition = (idx) => { + setPositionToEdit(idx); + setModalOpen(true); + }; + + const handleSubmit = (newPosition) => { + positionToEdit === null + ? setPositions([...positions, newPosition]) + : setPositions( + positions.map((currPosition, idx) => { + if (idx !== positionToEdit) return currPosition; + return newPosition; + }) + ); + }; + + const positionList = positions.map((position, idx) => ( + + {position.description} + {position.maxVote} + + + + + + )); + + return ( + <> +
    +
    Positions
    + + + + + + + + + + {positions.length > 0 ? ( + positionList + ) : ( + + + + )} + +
    DescriptionMaximum VoteTools
    + There are no positions to display. +
    + + {modalOpen && ( + { + setModalOpen(false); + setPositionToEdit(null); + }} + fields={fields} + onSubmit={handleSubmit} + validateFormCustom={validatePositionForm} + defaultValue={positionToEdit !== null && positions[positionToEdit]} + /> + )} +
    + + ); +} + +export default Position; diff --git a/client/src/components/Register.jsx b/client/src/components/Register.jsx index 391ab1c..2ae8f8b 100644 --- a/client/src/components/Register.jsx +++ b/client/src/components/Register.jsx @@ -1,5 +1,7 @@ import { useState } from "react"; import { Link, useNavigate } from "react-router"; +import voting from "../assets/voting.png"; +import gov_nepal from "../assets/gov_nepal.png"; function Register() { const [fname, setFname] = useState(""); @@ -10,134 +12,192 @@ function Register() { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); - const Navigate = useNavigate(); + const navigate = useNavigate(); - function handleSubmit(e) { + async function handleSubmit(e) { e.preventDefault(); const userData = { - fname, - lname, - citizenNum, - address, - phoneNo, - email, + username: `${fname} ${lname}`, + voter_id: phoneNo, password, + password2: password, }; - console.log(userData); - Navigate("/Dashboard"); + + try { + const response = await fetch("http://127.0.0.1:8000/register/", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(userData), + }); + + const data = await response.json(); + + if (response.ok) { + console.log("Candidates:", data); + } else { + console.error("Error getting candidates:", data); + } + } catch (err) { + console.log(err); + } } return ( -
    -
    -

    Register

    - - setFname(e.target.value)} - > - - - setLname(e.target.value)} - > - - - setCitizenNum(e.target.value)} - > - - - setAddress(e.target.value)} - > - - - setPhoneNo(e.target.value)} - > - - - setEmail(e.target.value)} - > - - - setPassword(e.target.value)} - > - - - { - if (e.target.value !== password) { - alert("Enter the same password!"); - return false; - } - }} - > - -
    - - - +
    + Government of Nepal Emblem +

    Register

    + +
    +
    + + setFname(e.target.value)} + /> +
    + +
    + + setLname(e.target.value)} + /> +
    + +
    + + setCitizenNum(e.target.value)} + /> +
    + +
    + + setAddress(e.target.value)} + /> +
    + +
    + + setPhoneNo(e.target.value)} + /> +
    + +
    + + setEmail(e.target.value)} + /> +
    + +
    + + setPassword(e.target.value)} + /> +
    + +
    + + +
    +
    + +
    + +
    + + + +
    +
    + +
    + Already Registered? Login Here +
    + +
    +
    - Already Registered? Login Here - -
    - + + + Electronic Voting + ); } diff --git a/client/src/components/VoterList.jsx b/client/src/components/VoterList.jsx index 2c162b6..fe49c77 100644 --- a/client/src/components/VoterList.jsx +++ b/client/src/components/VoterList.jsx @@ -1,3 +1,135 @@ -function VoterList() {} +import { useState } from "react"; +import { VOTER_DATA } from "./Data"; +import ModalForm from "./ModalForm"; + +function VoterList() { + // const [isEditing, setIsEditing] = useState(false); + // const [isAdding, setIsAdding] = useState(false); + // const [voters, setVoters] = useState(VOTER_DATA); + // const [activeVoterForEdit, setActiveVoterForEdit] = useState(); + + const [modalOpen, setModalOpen] = useState(false); + const [voters, setVoters] = useState(VOTER_DATA); + const [voterToEdit, setVoterToEdit] = useState(null); + + const fields = [ + { name: "firstName", label: "First Name", type: "text" }, + { name: "lastName", label: "Last Name", type: "text" }, + { name: "id", label: "Voter ID", type: "text" }, + ]; + + const validateVoterForm = (formState) => { + const errors = []; + for (const field of fields) { + if (!formState[field.name]) { + errors.push(`${field.label}`); + } + } + return errors.length > 0 ? errors : null; + }; + + const handleDeleteVoter = (targetIndex) => { + setVoters(voters.filter((_, idx) => idx !== targetIndex)); + }; + + const handleEditVoter = (idx) => { + setVoterToEdit(idx); + setModalOpen(true); + }; + + const handleSubmit = (newVoter) => { + voterToEdit === null + ? setVoters([...voters, newVoter]) + : setVoters( + voters.map((currVoter, idx) => { + if (idx !== voterToEdit) return currVoter; + return newVoter; + }) + ); + }; + + const voterList = voters.map((voter, idx) => ( + + {voter.firstName} + {voter.lastName} + {voter.id} + + + + + + )); + + // const addVoter = (firstName, lastName, id) => { + // const newVoter = { id, firstName, lastName }; + // setVoters([...voters, newVoter]); + // }; + + // const deleteVoter = (id) => { + // const remainingVoters = voters.filter((voter) => id !== voter.id); + // setVoters(remainingVoters); + // }; + + // const editVoter = (firstName, lastName, id) => { + // const editedVoterList = voters.map((voter) => { + // if (id === voter.id) { + // return { ...voter, firstName, lastName }; + // } + // return voter; + // }); + // setVoters(editedVoterList); + // }; + + return ( + <> +
    +
    Voters
    + + + + + + + + + + + {voters.length > 0 ? ( + voterList + ) : ( + + + + )} + +
    First NameLast NameVoter IDTools
    + There are no voters to display. +
    + +
    + {modalOpen && ( + { + setModalOpen(false); + setVoterToEdit(null); + }} + fields={fields} + onSubmit={handleSubmit} + validateFormCustom={validateVoterForm} + defaultValue={voterToEdit !== null && voters[voterToEdit]} + /> + )} + + ); +} export default VoterList; diff --git a/client/src/main.jsx b/client/src/main.jsx index b9a1a6d..ac3c6ad 100644 --- a/client/src/main.jsx +++ b/client/src/main.jsx @@ -1,10 +1,13 @@ -import { StrictMode } from 'react' -import { createRoot } from 'react-dom/client' -import './index.css' -import App from './App.jsx' +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import { BrowserRouter } from "react-router"; +import "./index.css"; +import App from "./App.jsx"; -createRoot(document.getElementById('root')).render( +createRoot(document.getElementById("root")).render( - - , -) + + + + +); diff --git a/client/tailwind.config.js b/client/tailwind.config.js index 9ac5fd3..89a305e 100644 --- a/client/tailwind.config.js +++ b/client/tailwind.config.js @@ -1,11 +1,11 @@ /** @type {import('tailwindcss').Config} */ -module.exports = { +export default { content: [ - "./src/**/*.{js,jsx,ts,tsx}", + "./index.html", + "./src/**/*.{js,ts,jsx,tsx}", ], theme: { extend: {}, }, plugins: [], -}; - +} \ No newline at end of file