Files
qv/ui/create-election.html
2025-01-14 12:35:32 +01:00

177 lines
6.9 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Create New Election</title>
<link rel="stylesheet" href="/static/css/styles.css">
<script src="https://unpkg.com/alpinejs@3" defer></script>
</head>
<body>
<div x-data="electionForm" class="container">
<main>
<h1>Create New Election</h1>
<form @submit.prevent="createElection" class="form">
<div class="form-group">
<label for="name">Election Name</label>
<input type="text" id="name" x-model="election.name" required>
</div>
<div class="form-row">
<div class="form-group">
<label for="tokens">Tokens per Voter</label>
<input type="number" id="tokens" x-model.number="election.tokens" min="1" required>
</div>
<div class="form-group">
<label for="areVotersKnown">Voter Access</label>
<select id="areVotersKnown" x-model="election.areVotersKnown" required>
<option :value="true">Known voters only</option>
<option :value="false">Open to anyone</option>
</select>
<small>Known voters only = codes will be generated and you must give those to your voters</small>
</div>
</div>
<div class="form-group">
<label for="maxVoters">Maximum Number of Voters</label>
<input type="number" id="maxVoters" x-model.number="election.maxVoters" min="1">
<small>0 = unlimited</small>
<template x-if="election.areVotersKnown && election.maxVoters <= 0">
<span class="error-text">Maximum number of voters must be greater than 0 if voters are known</span>
</template>
</div>
<div class="form-group">
<label for="expiresAt">Expiration Date</label>
<input type="datetime-local" id="expiresAt" x-model="election.expiresAt" required>
</div>
<div class="form-group">
<label>Choices</label>
<div id="choices-container">
<template x-for="(choice, index) in election.choices" :key="index">
<div class="choice-input">
<input type="text" x-model="election.choices[index]" required>
<button type="button" class="remove-choice" @click="removeChoice(index)" x-show="election.choices.length > 2">×</button>
</div>
</template>
</div>
<button type="button" id="add-choice" @click="addChoice">Add Another Choice</button>
</div>
<div class="form-actions">
<button type="submit">Create Election</button>
</div>
</form>
<template x-if="createdElectionId > 0">
<div class="election-info">
<h2>Election Created Successfully</h2>
<div class="info-container">
<span class="info-label">Election ID:</span>
<span class="info-value" x-text="createdElectionId"></span>
<button @click="copyElectionId" class="copy-btn">
Copy
</button>
</div>
</div>
</template>
<template x-if="voterIdentities.length > 0">
<div class="voter-codes">
<h2>Voter Access Codes</h2>
<div class="codes-container">
<button @click="copyAllCodes" class="copy-all-btn">
Copy All Codes
</button>
<div class="codes-list">
<template x-for="(code, index) in voterIdentities" :key="code">
<div class="code-item">
<span class="code-number" x-text="index + 1"></span>.
<span class="code-text" x-text="code"></span>
<button @click="copyCode(code)" class="copy-btn">
Copy
</button>
</div>
</template>
</div>
</div>
</div>
</template>
</main>
</div>
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('electionForm', () => ({
election: {
name: "",
tokens: 100,
areVotersKnown: true,
maxVoters: 0,
expiresAt: "",
choices: ["", ""] // Start with two empty choices
},
createdElectionId: 0,
voterIdentities: [],
addChoice() {
this.election.choices.push(""); // Add a new empty choice
},
removeChoice(index) {
this.election.choices.splice(index, 1); // Remove choice by index
},
async copyCode(code) {
try {
await navigator.clipboard.writeText(code);
} catch (err) {
console.error('Failed to copy code:', err);
}
},
async copyAllCodes() {
try {
const allCodes = this.voterIdentities.join('\n');
await navigator.clipboard.writeText(allCodes);
} catch (err) {
console.error('Failed to copy codes:', err);
}
},
createElection() {
this.voterIdentities = [];
const payload = {
...this.election,
expiresAt: this.election.expiresAt + ":00Z", // Add timezone if necessary
choices: this.election.choices.filter(choice => choice.trim() !== "") // Filter out empty choices
};
fetch("/election", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(payload)
})
.then(response => {
const locationHeader = response.headers.get('Location');
this.createdElectionId = locationHeader.replace('/election/', '');
return response.json();
})
.then(data => {
this.voterIdentities = data.voterIdentities;
})
.catch(error => {
alert("Failed to create election.");
});
}
}));
});
</script>
</body>
</html>