Adding page to create votes and view the votes, rearanging some methods

This commit is contained in:
2026-01-23 13:54:05 -05:00
parent 0f6e8a8350
commit 3b0a8625bc
10 changed files with 479 additions and 152 deletions

View File

@@ -12,6 +12,7 @@
"@emotion/styled": "^11.14.1",
"@mui/icons-material": "^7.3.7",
"@mui/material": "^7.3.7",
"axios": "^1.13.2",
"cra-template-pwa": "2.0.0",
"react": "^19.2.3",
"react-dom": "^19.2.3",
@@ -4763,6 +4764,31 @@
"node": ">=4"
}
},
"node_modules/axios": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
"integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.4",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/axios/node_modules/form-data": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/axobject-query": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
@@ -12713,6 +12739,11 @@
"node": ">= 0.10"
}
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
},
"node_modules/psl": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz",

View File

@@ -7,6 +7,7 @@
"@emotion/styled": "^11.14.1",
"@mui/icons-material": "^7.3.7",
"@mui/material": "^7.3.7",
"axios": "^1.13.2",
"cra-template-pwa": "2.0.0",
"react": "^19.2.3",
"react-dom": "^19.2.3",

View File

@@ -36,3 +36,38 @@
transform: rotate(360deg);
}
}
.dropdown {
display: inline-block;
}
.dropbtn {
background-color: #f5f5f5;
border: none;
cursor: pointer;
}
.dropdown-content {
display: none; /* Hidden by default */
position: absolute;
background-color: #f9f9f9;
min-width: 160px;
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
}
.dropdown-content a {
color: black;
padding: 12px 16px;
text-decoration: none;
display: block;
}
/* Show the dropdown menu on hover */
.dropdown:hover .dropdown-content {
display: block;
}
/* Add zebra striping to tables */
table tr:nth-child(odd) {
background-color: #f5f5f5;
}

View File

@@ -4,22 +4,42 @@ import Home from "./pages/Home";
import AdminLogin from "./pages/AdminLogin";
import AdminMembers from "./pages/AdminMembers";
import AdminMembersView from "./pages/AdminMembersView";
import AdminCreateVote from "./pages/AdminCreateVote";
import PollList from "./pages/PollList";
import './App.css';
export default function App() {
return (
<BrowserRouter>
<nav style={{ padding: "1rem", background: "#f5f5f5" }}>
<Link to="/">Home</Link> |
<Link to="/admin-login">Admin Login</Link> |
<Link to="/admin-members">Admin Members</Link> |
<Link to="/admin-members-view">View Members</Link>
<Link to="/">Home</Link> |
<Link to="/admin-login">Admin Login</Link> |
{/* Member dropdown */}
<div className="dropdown">
<button className="dropbtn">Member </button>
<div className="dropdown-content">
<a href="/admin-members">Upload Members</a>
<a href="/admin-members-view">View Members</a>
</div>
</div> |
<div className="dropdown">
<button className="dropbtn">Vote </button>
<div className="dropdown-content">
<a href="/create-vote">Create Vote</a>
<a href="/polls">Poll List</a>
</div>
</div>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/admin-login" element={<AdminLogin />} />
{/* Member routes */}
<Route path="/admin-members" element={<AdminMembers />} />
<Route path="/admin-members-view" element={<AdminMembersView />} />
{/* Vote routes */}
<Route path="/create-vote" element={<AdminCreateVote />} />
<Route path="/polls" element={<PollList />} />
</Routes>
</BrowserRouter>
);

View File

@@ -0,0 +1,67 @@
import React, { useState } from 'react';
function AdminCreateVote() {
const [question, setQuestion] = useState('');
const [expiresInHours, setExpiresInHours] = useState('');
const [status, setStatus] = useState("");
const handleSubmit = async (e) => {
e.preventDefault();
const formData = new FormData();
formData.append("question", question);
formData.append("expiresInHours", expiresInHours);
try {
const resp = await fetch("/api/admin/new-vote", {
method: "POST",
body: formData,
});
const data = await resp.json();
if (data.success) {
setStatus(`✅ Vote created with ID`);
} else {
setStatus(`❌ Server error: ${data.error}`);
}
} catch (error) {
setStatus("❌ Failed to create vote. Please try again: " + (error.response?.data?.error || error.message));
}
};
return (
<div>
<h1>Create New Vote</h1>
<form onSubmit={handleSubmit}>
<div>
<label>Question:</label>
<input
type="text"
value={question}
onChange={(e) => setQuestion(e.target.value)}
required
/>
</div>
<div>
<label>Expires In (hours):</label>
<input
type="number"
min="1"
value={expiresInHours}
onChange={(e) => setExpiresInHours(e.target.value)}
required
/>
</div>
<button type="submit">Create Vote</button>
</form>
{status && <p style={{ marginTop: "1rem" }}>{status}</p>}
</div>
);
}
export default AdminCreateVote;

View File

@@ -0,0 +1,46 @@
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import Table from '@mui/material/Table';
export default function PollList() {
const [polls, setPolls] = useState([]);
useEffect(() => {
fetchPolls();
}, []);
const fetchPolls = async () => {
try {
const response = await axios.post('/api/admin/view-votes');
setPolls(response.data);
} catch (error) {
console.error('Error fetching polls:', error);
}
};
return (
<div>
<h1>Poll List</h1>
<Table>
<thead>
<tr>
<th>Created At</th>
<th>Question</th>
<th>Member Yes Votes</th>
<th>Member No Votes</th>
</tr>
</thead>
<tbody>
{polls.map((poll) => (
<tr key={poll.id}>
<td>{new Date(poll.created_at).toLocaleString()}</td>
<td>{poll.question}</td>
<td>{poll.member_yes}</td>
<td>{poll.member_no}</td>
</tr>
))}
</tbody>
</Table>
</div>
);
}