This commit is contained in:
2024-12-28 19:31:45 -05:00
parent 69db561a0c
commit 1503af0e28
6 changed files with 180 additions and 172 deletions

View File

@@ -10,6 +10,7 @@ app.set('view engine','ejs');
app.use(express.json()); app.use(express.json());
app.use(express.static('public')); app.use(express.static('public'));
const PORT = 3000; const PORT = 3000;
const MainURL ="http://localhost:3000";
const PWSalt ="!SaltyMagic7283715374"; const PWSalt ="!SaltyMagic7283715374";
const EmailSalt="!SaltyMagic3562196239"; const EmailSalt="!SaltyMagic3562196239";
const QRSalt ="!SaltyMagic5392370662"; const QRSalt ="!SaltyMagic5392370662";
@@ -26,8 +27,13 @@ const QRSalt ="!SaltyMagic5392370662";
// + Claim Any Tickets // + Claim Any Tickets
// + Purge Revoked Function (Admin) // + Purge Revoked Function (Admin)
// + Issue Tickets from Camp Admin Page // + Issue Tickets from Camp Admin Page
// See how much each camp/ticket has paid (Admin) // Make one Update button on manytickets (User)
// Make one Update button on editcamp (Admin)
// Send email when new tickets are issued/offered // Send email when new tickets are issued/offered
// Magic-link Login System
// Deactivate individual magic links (User)
// Deactivate individual magic links (Admin)
// See how much each camp/ticket has paid (Admin)
// //
// ToDo, Later // ToDo, Later
// + Use a templating engine // + Use a templating engine
@@ -53,7 +59,7 @@ const QRSalt ="!SaltyMagic5392370662";
function hashEmail(email) { function hashEmail(email) {
const hash0=crypto.createHash('sha256'); const hash0=crypto.createHash('sha256');
const usersalt=email in users ? (linksalt in users[email] ? users[email].linksalt : "") : ""; const usersalt=email in users ? (users[email].linksalt ? users[email].linksalt : "") : "";
const hash1=hash0.update(email+EmailSalt+usersalt); const hash1=hash0.update(email+EmailSalt+usersalt);
const hash=hash1.digest("base64"); const hash=hash1.digest("base64");
return(hash); return(hash);
@@ -73,25 +79,46 @@ function hashQR(t,ownername) {
return(hash); return(hash);
} }
function GetMagicLink(email) {
return MainURL+"/login?u="+email+"&h="+hashEmail(email);
}
function MagicLinkValid(email,hash) {
if (HasPW(email)) return false;
return hashEmail(email)==hash;
}
// //
// In-memory data structures // In-memory data structures
// //
// There are two ways to log in: with a username/password or with a token. // There are two ways to log in: with a username/password or with a token.
// //
const users = { "teppy@egenesis.com" : { password: hashPW("x6321872X.1"), superuser:true, linksalt:"" }, //let users = { "teppy@egenesis.com" : { password: hashPW("x6321872X.1"), superuser:true, linksalt:"" },
"fallsonfire@gmail.com" : { password: hashPW("indiantreeonfire"), superuser:false, linksalt:"boobsarefun" } // "fallsonfire@gmail.com" : { password: hashPW("indiantreeonfire"), superuser:false, linksalt:"boobsarefun" }
}; // };
//
// Status can be "i"=issued, "u"=used, "r"=revoked" // Status can be "i"=issued, "u"=used, "r"=revoked"
const tickets = { "habitat-1" : { owner: "teppyx@egenesis.com" , offered: "", paid: 0.00, status:"i" }, // const tickets = { "habitat-1" : { owner: "teppy@egenesis.com" , offered: "" , paid: 0.00, status:"i" },
"habitat-2" : { owner: "teppyx@egenesis.com" , offered: "", paid: 0.00, status:"u" }, // "habitat-2" : { owner: "teppy@egenesis.com" , offered: "" , paid: 0.00, status:"u" },
"habitat-3" : { owner: "ginachen94@gmail.com", offered: "teppyx@egenesis.com", paid: 0.00, status:"i" }, // "habitat-3" : { owner: "ginachen94@gmail.com", offered: "teppy@egenesis.com" , paid: 0.00, status:"i" },
"habitat-4" : { owner: "teppyx@egenesis.com" , offered: "noahstern00@gmail.com", paid: 0.00, status:"i" }, // "habitat-4" : { owner: "teppy@egenesis.com" , offered: "noahstern00@gmail.com", paid: 0.00, status:"i" },
"habitat-5" : { owner: "teppyx@egenesis.com" , offered: "", paid: 0.00, status:"r" }, // "habitat-5" : { owner: "teppy@egenesis.com" , offered: "" , paid: 0.00, status:"r" },
"habitat-6" : { owner: "teppyx@egenesis.com" , offered: "", paid: 0.00, status:"i" }, // "habitat-6" : { owner: "teppy@egenesis.com" , offered: "" , paid: 0.00, status:"i" },
}; // };
// const camps = { "habitat": { leader: "teppy@egenesis.com", lastid:6 } };
const camps = { "habitat": { leader: "teppy@egenesis.com", lastid:6 } }; const users={};
const tickets={};
const camps={};
function InitDatabase() {
for (const key in users ) delete users[key];
for (const key in tickets) delete tickets[key];
for (const key in camps ) delete camps[key];
users["teppy@egenesis.com" ]= { password: hashPW("x6321872X.1"), superuser:true, linksalt:"" };
users["fallsonfire@gmail.com"]= { password: hashPW("indiantreeonfire"), superuser:false, linksalt:"" };
}
InitDatabase();
// Middleware setup // Middleware setup
app.use(bodyParser.urlencoded({ extended: true })); app.use(bodyParser.urlencoded({ extended: true }));
@@ -164,7 +191,6 @@ app.get('/camps',requireSuperUser, (req,res) => {
}) })
app.post("/camps",requireSuperUser,(req,res) => { app.post("/camps",requireSuperUser,(req,res) => {
console.log("New camp: ");
const campname=req.body.campname; const campname=req.body.campname;
const leader=req.body.leader; const leader=req.body.leader;
const qty=Number(req.body.qty); const qty=Number(req.body.qty);
@@ -173,23 +199,11 @@ app.post("/camps",requireSuperUser,(req,res) => {
camps[campname].lastid+=1; // LOG camps[campname].lastid+=1; // LOG
tickets[campname+'-'+camps[campname].lastid]={ owner: "", offered: leader, status:"i" }; // LOG tickets[campname+'-'+camps[campname].lastid]={ owner: "", offered: leader, status:"i" }; // LOG
} }
EmailTickets(leader);
return res.redirect("/camps"); return res.redirect("/camps");
}) })
app.get('/camplead', requireLogin, (req,res) => {
let username=req.session.username;
const edit={ username:username, superuser:req.session.superuser, tickets: {} };
for (const t in tickets) if (tickets[t].owner==username && tickets[t].status!='r') {
edit.tickets[t]={};
edit.tickets[t].owner=tickets[t].owner;
edit.tickets[t].offered=tickets[t].offered;
edit.tickets[t].used=tickets[t].status=='u';
}
return res.render("camplead",edit);
})
app.get('/editcamp', requireSuperUser, (req,res) => { app.get('/editcamp', requireSuperUser, (req,res) => {
let campname=req.query.campname; let campname=req.query.campname;
const edit={ username:req.session.username, superuser:req.session.superuser, campname:campname, leader:camps[campname].leader, tickets: {} }; const edit={ username:req.session.username, superuser:req.session.superuser, campname:campname, leader:camps[campname].leader, tickets: {} };
@@ -210,7 +224,6 @@ app.get('/editcamp', requireSuperUser, (req,res) => {
app.post('/moretickets', requireSuperUser, (req,res) => { app.post('/moretickets', requireSuperUser, (req,res) => {
const qty=Number(req.body.qty); const qty=Number(req.body.qty);
const campname=req.body.campname; const campname=req.body.campname;
console.log("More tickets! ",campname in camps,qty);
if (campname in camps) for (let i=0; i<qty; i++) { if (campname in camps) for (let i=0; i<qty; i++) {
camps[campname].lastid++; camps[campname].lastid++;
tickets[campname+'-'+camps[campname].lastid]={ owner:"", offered:camps[campname].leader, paid:0, status:"i" }; tickets[campname+'-'+camps[campname].lastid]={ owner:"", offered:camps[campname].leader, paid:0, status:"i" };
@@ -232,7 +245,6 @@ app.get('/manytickets', requireLogin, (req,res) => {
app.get('/mytickets',requireLogin, async (req,res)=> { app.get('/mytickets',requireLogin, async (req,res)=> {
console.log("In mytickets req.session is ",req.session);
let username=req.session.username; let username=req.session.username;
let claimed=0; let claimed=0;
let owned=0; let owned=0;
@@ -247,43 +259,46 @@ app.get('/mytickets',requireLogin, async (req,res)=> {
edit.tickets[t].owner=tickets[t].owner; edit.tickets[t].owner=tickets[t].owner;
edit.tickets[t].offered=tickets[t].offered; edit.tickets[t].offered=tickets[t].offered;
} }
} }
if (owned==0) return res.render("message",{ username:username, superuser:req.session.superuser, message:"You have no unused tickets" }); let message="";
if (claimed>0) message="You have claimed "+claimed+" tickets.";
edit.message=message;
if (owned==0) return res.render("zerotickets",{ username:username, superuser:req.session.superuser, message:message });
else if (owned==1) { else if (owned==1) {
const hash0=crypto.createHash('sha256'); const hash0=crypto.createHash('sha256');
const hash1=hash0.update(theticket+QRSalt); const hash1=hash0.update(theticket+QRSalt);
const hash=hash1.digest("base64").slice(0,6); const hash=hash1.digest("base64").slice(0,6);
const dataURL=await QRCode.toDataURL('localhost:3000/useticket?t='+theticket+'&h='+hashQR(theticket,username)); const useurl='/useticket?t='+theticket+'&h='+hashQR(theticket,username);
return res.render("oneticket",{ username:username, superuser:req.session.superuser, ticket:theticket, offered:tickets[theticket].offered, qrcode:dataURL }); const dataURL=await QRCode.toDataURL(useurl);
return res.render("oneticket",{ username:username, superuser:req.session.superuser, message:message, ticket:theticket, offered:tickets[theticket].offered, qrcode:dataURL, useurl:useurl });
} }
else return res.render("manytickets",edit); else return res.render("manytickets",edit);
}); });
app.post('/mytickets',requireLogin, async (req,res)=> { app.post('/oneticket',requireLogin, async (req,res)=> {
let username=req.session.username; let username=req.session.username;
let theticket=req.body.ticket; let theticket=req.body.ticket;
let offered=req.body.offered; let offered=req.body.offered;
let message="";
if (tickets[theticket].owner==username && tickets[theticket].status=="i") { if (tickets[theticket].owner==username && tickets[theticket].status=="i") {
tickets[theticket].offered=offered; // LOG tickets[theticket].offered=offered; // LOG
} }
const hash0=crypto.createHash('sha256'); const hash0=crypto.createHash('sha256');
const hash1=hash0.update(theticket+QRSalt); const hash1=hash0.update(theticket+QRSalt);
const hash=hash1.digest("base64").slice(0,6); const hash=hash1.digest("base64").slice(0,6);
const dataURL=await QRCode.toDataURL('localhost:3000/useticket?t='+theticket+'&h='+hashQR(theticket,username)); const dataURL=await QRCode.toDataURL('/useticket?t='+theticket+'&h='+hashQR(theticket,username));
return res.render("oneticket", { username:username, superuser:req.session.superuser, ticket:theticket, offered:tickets[theticket].offered, qrcode:dataURL }); return res.render("oneticket", { username:username, superuser:req.session.superuser, message:message, magiclink:GetMagicLink(username), ticket:theticket, offered:tickets[theticket].offered, qrcode:dataURL });
}); });
app.post("/changestatus", requireSuperUser, (req,res) => { app.post("/changestatus", requireSuperUser, (req,res) => {
const ticket=req.body.ticket; const ticket=req.body.ticket;
console.log("Changestatus ",ticket);
tickets[ticket].status=req.body.status; // LOG tickets[ticket].status=req.body.status; // LOG
res.json({ message: 'Status received', status: req.body.status }); res.json({ message: 'Status received', status: req.body.status });
}) })
app.post("/updateoffered", requireLogin, (req,res) => { app.post("/updateoffered", requireLogin, (req,res) => {
console.log("UpdateOffered being rubn");
const ticket=req.body.ticket; const ticket=req.body.ticket;
const offered=req.body.offered; const offered=req.body.offered;
if (tickets[ticket].owner!=req.session.username) res.status(500).send("Ticket "+ticket+" owned by someone else"); if (tickets[ticket].owner!=req.session.username) res.status(500).send("Ticket "+ticket+" owned by someone else");
@@ -295,6 +310,20 @@ app.post("/updateoffered", requireLogin, (req,res) => {
} }
}) })
app.post("/updateoffered2", requireLogin, (req,res) => {
const changes={};
for (ticket in req.body) if (tickets[ticket] && tickets[ticket].owner==req.session.username && req.body[ticket]!=tickets[ticket].offered) {
if (!(req.body[ticket] in changes)) changes[req.body[ticket]]=0;
changes[req.body[ticket]]++;
}
// Ok to bail here if we need to
for (ticket in req.body) if (tickets[ticket] && tickets[ticket].owner==req.session.username && req.body[ticket]!=tickets[ticket].offered) {
tickets[ticket].offered=req.body[ticket];
}
for (email in changes) EmailTickets(email);
return res.redirect("manytickets");
})
app.post("/updateticketsu", requireSuperUser, (req,res) => { app.post("/updateticketsu", requireSuperUser, (req,res) => {
const ticket=req.body.ticket; const ticket=req.body.ticket;
@@ -307,43 +336,30 @@ app.post("/updateticketsu", requireSuperUser, (req,res) => {
app.get("/useticket",(req,res) => { app.get("/useticket",(req,res) => {
let ticket=req.t; let ticket=req.query.t;
let hash=req.h; let hash=req.query.h;
if (hashQR(ticket,req.session.username)!=hash) res.status(500).send("Ticket "+ticket+" was transferred to "+tickets[ticket].username); if (!tickets[ticket]) return res.status(500).send("Ticket "+ticket+" not found.");
else if (tickets[ticket].status!="i") res.status(500).send("Ticket "+ticket+" has already been used."); if (hashQR(ticket,tickets[ticket].owner)!=hash) return res.status(500).send("Ticket "+ticket+" was transferred to "+tickets[ticket].owner);
else { if (tickets[ticket].status!="i") return res.status(500).send("Ticket "+ticket+" has already been used.");
tickets[ticket].status="u"; // LOG tickets[ticket].status="u"; // LOG
res.send("<h1>Welcome, "+tickets[ticket].owner+" to Falls on Fire! Ticket "+ticket+" has now been used.</h1>"); return res.send("<h1>Welcome, "+tickets[ticket].owner+" to Falls on Fire! Ticket "+ticket+" has now been used.</h1>");
}
}) })
function knownEmail(email) { async function EmailTickets(email) {
if (users[email]) return true; let offered=0;
for (const key in tickets) for (const ticket in tickets) if (tickets[ticket].offered==email) offered++;
if (tickets[key].owner==email || tickets[key].offered==email) return true; if (offered==0) return;
return false; const textbody="You have been offered "+offered+" tickets to Falls On Fire! To claim them, visit this link:\n"+GetMagicLink(email);
} const htmlbody="You have been offered "+offered+" tickets to Falls On Fire! To claim them, <a href=\""+GetMagicLink(email)+"\">click here.</a>";
await client.sendEmail({ From: "tickets@fallsonfire.net",
app.post('emaillink',(req,res) => { To: email,
if (!knownUser(req.email)) return res.send("Unknown User"); Subject: "Falls on Fire: You've Got Tickets!",
const tok=createtoken(email); TextBody: textbody,
client.sendEmail({ From: 'tickets@fallsonfire.net', HTMLBody: htmlbody
To: 'teppy@egenesis.com', });
Subject: 'Falls on Fire Tickets, Login Link', }
TextBody: 'Your Login Link: http://www.fallonfire.net/longinlink?token='+tok,
HtmlBody: '<p>Your Login Link: </p>',
}).then(() => {
console.log('Email sent');
res.send("<h1>Email sent</h1>");
}).catch((error) => {
console.error('Error sending email:', error);
res.send("<h1>Email failed</h1>");
});
res.send("Email link sent");
})
app.get('/testemail', app.get('/testemail',
(req,res) => { (req,res) => {
@@ -369,10 +385,42 @@ app.get('/', (req, res) => {
return res.render("login", { superuser:false }); return res.render("login", { superuser:false });
}); });
function HasPW(username) {
if (!(username in users)) return false;
if (!users[username]) return false;
if (!users[username].password) return false;
if (users[username].password==0) return false;
if (users[username].password=="") return false;
return true;
}
app.get('/login',(req,res) => { app.get('/login',(req,res) => {
return res.render("login",{ superuser:false }); const username=req.query.u;
const hash=req.query.h;
if (MagicLinkValid(req.query.u,req.query.h)) return res.redirect("/mytickets");
return res.render("login",{ message: "Normal Login Required." });
}); });
app.post('/login', (req, res) => {
const { username, password, superuser } = req.body;
if (users[username] && users[username].password === hashPW(password)) {
req.session.username = username;
req.session.superuser = users[username].superuser;
const redir=req.session.returnTo;
delete req.session.returnTo;
return res.redirect(redir || "/mytickets");
}
res.send('Invalid username or password. <a href="/login">Try again</a>');
});
app.get('/logout', (req, res) => {
req.session.destroy(() => {
res.redirect('/');
});
});
app.get('/signup', (req, res) => { app.get('/signup', (req, res) => {
res.send(` res.send(`
<h1>Sign Up</h1> <h1>Sign Up</h1>
@@ -423,55 +471,35 @@ app.post('/changepassword', (req, res) => {
}); });
app.post('/login', (req, res) => {
const { username, password, superuser } = req.body;
if (users[username] && users[username].password === hashPW(password)) {
req.session.username = username;
req.session.superuser = users[username].superuser;
console.log("In post /login",req.session);
const redir=req.session.returnTo;
delete req.session.returnTo;
return res.redirect(redir || "/mytickets");
}
res.send('Invalid username or password. <a href="/login">Try again</a>');
});
app.get('/logout', (req, res) => {
req.session.destroy(() => {
res.redirect('/');
});
});
app.post('/qrcode',requireLogin,async (req,res) => { app.post('/qrcode',requireLogin,async (req,res) => {
const username=req.session.username; const username=req.session.username;
const ticket=req.body.ticket; const ticket=req.body.ticket;
if (tickets[ticket].owner!=username) return res.status(500).send("Only a ticket owner can generate a QR code"); if (tickets[ticket].owner!=username) return res.status(500).send("Only a ticket owner can generate a QR code");
const URL=await QRCode.toDataURL('localhost:3000/useticket?t='+ticket+'&h='+hashQR(ticket,username)); const URL=await QRCode.toDataURL('/useticket?t='+ticket+'&h='+hashQR(ticket,username));
return res.send({ owner:tickets[ticket].owner, qrcode: URL }); return res.send({ owner:username, qrcode: URL, magiclink:GetMagicLink(username) });
}) })
app.post('/qrcodesu',requireSuperUser,async (req,res) => { app.post('/qrcodesu',requireSuperUser,async (req,res) => {
const username=req.session.username;
const ticket=req.body.ticket; const ticket=req.body.ticket;
const URL=await QRCode.toDataURL('localhost:3000/useticket?t='+ticket+'&h='+hashQR(ticket,username)); const username=tickets[ticket].owner;
return res.send({ owner:tickets[ticket].owner, qrcode: URL }); const URL=await QRCode.toDataURL('/useticket?t='+ticket+'&h='+hashQR(ticket,username));
return res.send({ owner:username, qrcode: URL, magiclink:GetMagicLink(username) });
}) })
app.get('/settings',requireSuperUser, (req,res) => { app.get('/settings',requireSuperUser, (req,res) => {
res.render('settings',{ username:req.session.username, superuser:req.session.superuser, message: "" }) res.render('settings',{ username:req.session.username, superuser:req.session.superuser, message: "" })
}); });
app.post('/wipedb',requireSuperUser, (req,res) => { app.post('/wipedb',requireSuperUser, (req,res) => {
for (const key in users ) delete users[key]; InitDatabase();
for (const key in tickets) delete tickets[key];
for (const key in camps ) delete camps[key];
res.render('settings',{ username:req.session.username, superuser:req.session.superuser, message: "Wiped the database, but not the logfile." }) res.render('settings',{ username:req.session.username, superuser:req.session.superuser, message: "Wiped the database, but not the logfile." })
}); });
app.post('/purge',requireSuperUser, (req,res) => { app.post('/purge',requireSuperUser, (req,res) => {
let count=0; let count=0;
for (const t in tickets) if (tickets[t].status=='r') { count++; delete tickets[t]; } for (const t in tickets) if (tickets[t].status=='r') { count++; delete tickets[t]; }
res.render('message',{ username:req.session.username, superuser:req.session.superuser, message: "Purged "+count+" revoked tickets" }) res.render('settings',{ username:req.session.username, superuser:req.session.superuser, message: "Purged "+count+" revoked tickets" })
}); });

View File

@@ -24,7 +24,6 @@
.qrcode-image { .qrcode-image {
object-fit: contain; object-fit: contain;
grid-column:2;
} }
/* Close button */ /* Close button */
@@ -36,63 +35,30 @@
cursor: pointer; cursor: pointer;
} }
.grid {
}
/* General Reset */
body { body {
margin: 0;
font-family: Arial, sans-serif;
display: grid; display: grid;
grid-template-columns: 1fr 3fr; grid-template-columns: 240px auto;
height: 100vh; /* Full viewport height */ grid-template-rows: 40px auto;
font-family: Arial, sans-serif;
} }
/* Sidebar (Nav Links) */ .nav {
.nav-links {
background-color: #f4f4f4; /* Light gray background */
padding: 20px;
box-shadow: 2px 0 5px rgba(0, 0, 0, 0.1); /* Add a slight shadow for separation */
grid-column: 1; grid-column: 1;
min-height: 100vh; /* Stretch to full viewport height */ grid-row: 1 / 3;
background-color: #FFC0C0;
} }
.nav-links nav ul { .message {
list-style: none; /* Remove bullet points */ grid-column: 2;
padding: 0; grid-row: 1;
} background-color: #C0FFC0;
align-content: center;
.nav-links nav ul li { justify-items: center;
margin: 10px 0; /* Add spacing between links */
}
.nav-links nav ul li a {
text-decoration: none; /* Remove underline */
color: #333;
}
.nav-links nav ul li a:hover {
color: #007BFF; /* Change link color on hover */
} }
.content { .content {
grid-column: 2; grid-column: 2;
flex-grow: 1; /* Allow content to fill the remaining space */ grid-row: 2;
padding: 20px; background-color: #C0C0FF;
} }
.menu-icon {
cursor: pointer;
display: inline-block;
}
.menu-icon span {
display: block;
width: 25px;
height: 3px;
margin: 5px 0;
background: #fff;
transition: 0.4s;
}

View File

@@ -13,7 +13,7 @@
</div> </div>
</div> </div>
<div class="content"> <div class="content">
<form id="editor"> <form id="editor" method="POST" action="/updateoffered2">
<div id="server-response">Server Ready</div> <div id="server-response">Server Ready</div>
Tickets owned by <%=username%> Tickets owned by <%=username%>
<table border="1"> <table border="1">
@@ -25,13 +25,14 @@
<% for (const t in tickets) { %> <% for (const t in tickets) { %>
<tr> <tr>
<td><%=t%></td> <td><%=t%></td>
<td><input type="edit" value="<%=tickets[t].offered%>" id="<%=t%>-offered"></td> <td><input type="text" class="offered" value="<%=tickets[t].offered%>" name="<%=t%>"></td>
<td><button id="<%=t%>-action" type="button"> QRCode </button></td> <td><button id="<%=t%>-action" type="button"> QRCode </button></td>
</tr> </tr>
<% } %> <% } %>
<tr> <tr>
</tr> </tr>
</table> </table>
<button id="Update" type="submit">Update Offered</button>
</form> </form>
</div> </div>
<script> <script>
@@ -96,13 +97,10 @@ document.body.addEventListener("click", event => {
const offereds = document.querySelectorAll("[id$=-offered]"); const offereds = document.getElementsByClassName("offered");
offereds.forEach(el => { const MessageArea=document.getElementById("message");
const id0=el.id.slice(0,-8); console.log("Offereds is ",offereds[0]);
const OfferedEdit = document.getElementById(id0+"-offered"); for (let i=0; i<offereds.length; i++) offereds[i].addEventListener('input',(event)=>MessageArea.textContent= "Be sure to use the Update Offered button.");
const ActionButton = document.getElementById(id0+"-action" );
OfferedEdit.addEventListener('input', () => { ActionButton.textContent = "Update"; ooEdits[id0]=true; console.log("Changed OfferedEdit:",OfferedEdit.value); });
});
</script> </script>
</body> </body>

View File

@@ -8,8 +8,9 @@
<%- include('partials/nav') %> <%- include('partials/nav') %>
<div class="content"> <div class="content">
To use <%=ticket%>, scan this QR Code:<br> To use <%=ticket%>, scan this QR Code:<br>
<img class="qrcode-image" width=300 height=300 src="<%=qrcode%>" alt="QR Code"> <img class="qrcode-image" width=300 height=300 src="<%=qrcode%>" alt="QR Code"><br>
<form id="editor" method="POST"> Or visit this URL: "<%=useurl%>"
<form id="editor" method="POST" action="/oneticket">
Or transfer <%=ticket%> to:<br> Or transfer <%=ticket%> to:<br>
<input type="hidden" name="ticket" value="<%=ticket%>"> <input type="hidden" name="ticket" value="<%=ticket%>">
<input type="email" placeholder="yourfriend@xyz.com" value="<%=offered%>" name="offered"> <input type="email" placeholder="yourfriend@xyz.com" value="<%=offered%>" name="offered">

View File

@@ -1,14 +1,15 @@
<!-- views/partials/nav.ejs --> <!-- views/partials/nav.ejs -->
<aside> <div class="nav">
<nav class="nav-links"> Falls on Fire<br>
Falls on Fire <a href="/mytickets">View My Tickets</a><br>
<ul> <%if (typeof superuser!='undefined' && superuser) {%>
<li><a href="/mytickets">View My Tickets</a></li> <a href="/camps">View Camps (Admin)</a><br>
<%if (superuser) {%> <a href="/settings">Settings (Admin)</a><br>
<li><a href="/camps">View Camps (Admin)</a></li>
<li><a href="/settings">Settings (Admin)</a></li>
<%}%> <%}%>
<li><a href="/logout">Log Out</a></li> <a href="/logout">Log Out</a><br>
</ul> </div>
</nav> <div class="message" id="message">
</aside> <% if (typeof message !== 'undefined') { %>
<p><%= message %></p>
<% } %>
</div>

14
views/zerotickets.ejs Normal file
View File

@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<title>No Tickets</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<%- include('partials/nav') %>
<div class="content">
You don't have any tickets assigned to you.
</div>
</body>
</html>