Implement page to vote and fix trigger
This commit is contained in:
124
ui/election.html
Normal file
124
ui/election.html
Normal file
@ -0,0 +1,124 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Vote in Election</title>
|
||||
<script src="/static/js/tailwind.min.js"></script>
|
||||
<script src="/static/js/alpine.min.js" defer></script>
|
||||
</head>
|
||||
<body class="bg-gray-100 font-sans leading-normal tracking-normal">
|
||||
<div x-data="votePage" class="container mx-auto p-6 bg-white rounded-lg shadow-lg">
|
||||
<main>
|
||||
<h1 class="text-3xl font-bold mb-6" x-text="election.name || 'Loading...'"></h1>
|
||||
|
||||
<form @submit.prevent="submitVote" class="space-y-6">
|
||||
<!-- Code Field -->
|
||||
<template x-if="election.areVotersKnown">
|
||||
<div>
|
||||
<label for="code" class="block text-sm font-medium text-gray-700">Access Code</label>
|
||||
<input type="text" id="code" x-model="code" required
|
||||
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Voting Choices -->
|
||||
<template x-if="election.choices.length > 0">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Distribute Your Tokens</label>
|
||||
<div class="space-y-2 mt-2">
|
||||
<template x-for="(choice, index) in election.choices" :key="index">
|
||||
<div>
|
||||
<label class="block text-gray-700" x-text="choice"></label>
|
||||
<input type="number" min="0"
|
||||
x-model.number="votes[index]"
|
||||
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500">
|
||||
</div>
|
||||
</template>
|
||||
<p class="text-sm text-gray-500 mt-2">Tokens remaining: <span x-text="remainingTokens"></span></p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- No Choices Message -->
|
||||
<template x-if="!election.choices.length">
|
||||
<p class="text-gray-700">No choices available for this election.</p>
|
||||
</template>
|
||||
|
||||
<button type="submit"
|
||||
:disabled="remainingTokens < 0"
|
||||
class="w-full inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50">
|
||||
Submit Vote
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div x-show="message" class="mt-6 p-4 rounded-md" :class="{'bg-green-100 text-green-700': success, 'bg-red-100 text-red-700': !success}">
|
||||
<p x-text="message"></p>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener("alpine:init", () => {
|
||||
Alpine.data("votePage", () => ({
|
||||
election: { name: "", choices: [], tokens: 0, areVotersKnown: false },
|
||||
code: "",
|
||||
votes: [],
|
||||
message: "",
|
||||
success: false,
|
||||
|
||||
get remainingTokens() {
|
||||
return this.election.tokens - this.votes.reduce((sum, v) => sum + v, 0);
|
||||
},
|
||||
|
||||
async init() {
|
||||
const electionId = window.location.pathname.split("/").pop();
|
||||
try {
|
||||
const response = await fetch(`/api/election/${electionId}`);
|
||||
if (!response.ok) throw new Error("Failed to load election details.");
|
||||
const data = await response.json();
|
||||
this.election = data;
|
||||
this.votes = Array(data.choices.length).fill(0);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
this.message = "Failed to load election details.";
|
||||
this.success = false;
|
||||
}
|
||||
},
|
||||
|
||||
async submitVote() {
|
||||
if (this.remainingTokens < 0) {
|
||||
this.message = "You have exceeded the number of available tokens.";
|
||||
this.success = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const electionId = window.location.pathname.split("/").pop();
|
||||
const payload = {
|
||||
voterIdentity: this.election.areVotersKnown ? this.code : null,
|
||||
choices: this.election.choices.map((choice, index) => ({
|
||||
choiceText: choice,
|
||||
tokens: this.votes[index] || 0,
|
||||
})),
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/election/${electionId}/votes`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
if (!response.ok) throw new Error("Failed to submit vote.");
|
||||
this.message = "Vote submitted successfully!";
|
||||
this.success = true;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
this.message = "Failed to submit vote.";
|
||||
this.success = false;
|
||||
}
|
||||
}
|
||||
}));
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Reference in New Issue
Block a user