2025-01-13 10:56:36 +01:00
<!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 >
2025-01-13 19:58:42 +01:00
< link rel = "stylesheet" href = "/static/css/styles.css" >
< script src = "https://unpkg.com/vue@3" > < / script >
2025-01-13 10:56:36 +01:00
< / head >
< body >
2025-01-13 19:58:42 +01:00
< div id = "app" class = "container" >
2025-01-13 10:56:36 +01:00
< main >
< h1 > Create New Election< / h1 >
2025-01-13 19:58:42 +01:00
< form @ submit . prevent = "createElection" class = "form" >
2025-01-13 10:56:36 +01:00
< div class = "form-group" >
< label for = "name" > Election Name< / label >
2025-01-13 19:58:42 +01:00
< input type = "text" id = "name" v-model = "election.name" required >
2025-01-13 10:56:36 +01:00
< / div >
< div class = "form-row" >
< div class = "form-group" >
< label for = "tokens" > Tokens per Voter< / label >
2025-01-13 19:58:42 +01:00
< input type = "number" id = "tokens" v-model . number = "election.tokens" min = "1" required >
2025-01-13 10:56:36 +01:00
< / div >
< div class = "form-group" >
2025-01-13 19:58:42 +01:00
< label for = "areVotersKnown" > Voter Access< / label >
< select id = "areVotersKnown" v-model = "election.areVotersKnown" required >
< option :value = "true" > Known voters only< / option >
< option :value = "false" > Open to anyone< / option >
2025-01-13 10:56:36 +01:00
< / select >
2025-01-13 19:58:42 +01:00
< small > Known voters only = codes will be generated and you must give those to your voters< / small >
2025-01-13 10:56:36 +01:00
< / div >
< / div >
2025-01-13 19:58:42 +01:00
< div class = "form-group" >
< label for = "maxVoters" > Maximum Number of Voters< / label >
< input type = "number" id = "maxVoters" v-model . number = "election.maxVoters" min = "1" >
< small > 0 = unlimited< / small >
< span v-if = "election.areVotersKnown && election.maxVoters <= 0" class = "error-text" > Maximum number of voters must be greater than 0 if voters are known< / span >
2025-01-13 10:56:36 +01:00
< / div >
< div class = "form-group" >
2025-01-13 19:58:42 +01:00
< label for = "expiresAt" > Expiration Date< / label >
< input type = "datetime-local" id = "expiresAt" v-model = "election.expiresAt" required >
2025-01-13 10:56:36 +01:00
< / div >
< div class = "form-group" >
< label > Choices< / label >
< div id = "choices-container" >
2025-01-13 19:58:42 +01:00
< div v-for = "(choice, index) in election.choices" :key = "index" class = "choice-input" >
< input type = "text" v-model = "election.choices[index]" required >
< button type = "button" class = "remove-choice" @ click = "removeChoice(index)" v-show = "election.choices.length > 2" > × < / button >
2025-01-13 10:56:36 +01:00
< / div >
< / div >
2025-01-13 19:58:42 +01:00
< button type = "button" id = "add-choice" @ click = "addChoice" > Add Another Choice< / button >
2025-01-13 10:56:36 +01:00
< / div >
< div class = "form-actions" >
< button type = "submit" > Create Election< / button >
< / div >
< / form >
2025-01-13 19:58:42 +01:00
< div v-if = "voterIdentities.length > 0" 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" >
< div v-for = "(code, index) in voterIdentities"
:key="code"
class="code-item">
< span class = "code-number" > {{ index + 1 }}.< / span >
< span class = "code-text" > {{ code }}< / span >
< button @ click = "copyCode(code)" class = "copy-btn" >
Copy
< / button >
< / div >
< / div >
< / div >
< / div >
2025-01-13 10:56:36 +01:00
< / main >
< / div >
2025-01-13 19:58:42 +01:00
< script >
const app = Vue.createApp({
data() {
return {
election: {
name: "",
tokens: 100,
areVotersKnown: true,
maxVoters: 0,
expiresAt: "",
choices: ["", ""] // Start with two empty choices
},
voterIdentities: []
};
},
methods: {
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);
// Optional: Add visual feedback that copy succeeded
} catch (err) {
console.error('Failed to copy code:', err);
}
},
async copyAllCodes() {
try {
const allCodes = this.voterIdentities.join('\n');
await navigator.clipboard.writeText(allCodes);
// Optional: Add visual feedback that copy succeeded
} 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 => response.json())
.then(data => {
this.voterIdentities = data.voterIdentities;
})
.catch(error => {
alert("Failed to create election.");
});
}
}
});
app.mount("#app");
< / script >
2025-01-13 10:56:36 +01:00
< / body >
< / html >