125 lines
5.4 KiB
HTML
125 lines
5.4 KiB
HTML
<!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>
|