QOL improvements
All checks were successful
Build Images and Deploy / Update-PROD-Stack (push) Successful in 28s

This commit is contained in:
2026-02-28 01:14:50 -05:00
parent 79ee7064a8
commit bdb6d5ee25
11 changed files with 264 additions and 4 deletions

View File

@@ -24,6 +24,34 @@
</a>
<% }) %>
<% } %>
<h2 style="margin-top: 2rem; margin-bottom: 1rem;">Password Reset</h2>
<div class="card">
<p style="color: var(--muted); font-size: 0.9rem; margin-bottom: 1rem;">Generate a one-time password reset link for a user. The link expires in 24 hours.</p>
<form method="POST" action="/admin/reset-password" style="display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: flex-end;">
<div class="form-group" style="margin-bottom: 0; flex: 1; min-width: 180px;">
<label>Username</label>
<select name="username" class="form-control" required>
<option value="">Select user...</option>
<% if (typeof users !== 'undefined' && users) { users.forEach(u => { %>
<option value="<%= u.username %>"><%= u.username %><%= u.is_admin ? ' (admin)' : '' %></option>
<% }); } %>
</select>
</div>
<button type="submit" class="btn btn-primary">Generate Reset Link</button>
</form>
<% if (typeof resetUrl !== 'undefined' && resetUrl) { %>
<div style="margin-top: 1rem; padding: 1rem; background: var(--body-bg); border-radius: 8px;">
<p style="margin: 0 0 0.5rem; font-weight: 600;">Reset link for <strong><%= resetUsername %></strong>:</p>
<div style="display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap;">
<input type="text" class="form-control" value="<%= resetUrl %>" id="reset-url" readonly style="font-family: monospace; font-size: 0.85rem; flex: 1; min-width: 200px;">
<button class="btn btn-sm btn-outline" onclick="document.getElementById('reset-url').select();navigator.clipboard.writeText(document.getElementById('reset-url').value).then(()=>{this.textContent='Copied!';setTimeout(()=>this.textContent='Copy',1500)})">Copy</button>
</div>
<p style="font-size: 0.8rem; color: var(--muted); margin: 0.5rem 0 0;">Send this link to the user. It expires in 24 hours and can only be used once.</p>
</div>
<% } %>
</div>
</div>
<%- include('../partials/footer') %>

View File

@@ -49,7 +49,7 @@
<th>Scans</th>
<th>First Scanner</th>
<th>Last Scanner</th>
<th>Link</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@@ -62,6 +62,7 @@
<td><% if (pkg.last_scanner_name) { %><a href="/player/<%= pkg.last_scanner_name %>"><%= pkg.last_scanner_name %></a><% } else { %>---<% } %></td>
<td>
<a href="/hunt/<%= hunt.short_name %>/<%= pkg.card_number %>" class="btn btn-sm btn-outline">View</a>
<button class="btn btn-sm btn-outline" onclick="navigator.clipboard.writeText('<%= baseUrl %>/loot/<%= hunt.short_name %>/<%= pkg.unique_code %>').then(()=>{this.textContent='Copied!';setTimeout(()=>this.textContent='Copy Link',1500)})">Copy Link</button>
</td>
</tr>
<% }) %>

29
src/views/auth/reset.ejs Normal file
View File

@@ -0,0 +1,29 @@
<%- include('../partials/header') %>
<div class="container-narrow">
<div class="card">
<h1 style="text-align: center; margin-bottom: 0.5rem;">Reset Password</h1>
<p style="text-align: center; color: var(--muted); margin-bottom: 1.5rem;">Choose a new password for <strong><%= username %></strong></p>
<% if (error) { %>
<div class="alert alert-danger"><%= error %></div>
<% } %>
<form method="POST" action="/auth/reset/<%= token %>">
<div class="form-group">
<label for="password">New Password</label>
<input type="password" id="password" name="password" class="form-control" required
minlength="6" autocomplete="new-password" autofocus>
<span class="form-hint">Minimum 6 characters</span>
</div>
<div class="form-group">
<label for="password_confirm">Confirm Password</label>
<input type="password" id="password_confirm" name="password_confirm" class="form-control" required
minlength="6" autocomplete="new-password">
</div>
<button type="submit" class="btn btn-primary" style="width: 100%;">Set New Password</button>
</form>
</div>
</div>
<%- include('../partials/footer') %>

View File

@@ -31,6 +31,21 @@
<div class="card">
<div class="card-header">&#x1F4F8; First Finder's Photo</div>
<img src="<%= pkg.first_scan_image %>" alt="Package photo" class="package-image">
<% if (isFirstScanner) { %>
<div style="display: flex; gap: 0.5rem; margin-top: 0.75rem; flex-wrap: wrap;">
<form method="POST" action="/loot/<%= pkg.hunt_short_name %>/<%= pkg.unique_code %>/image/delete" style="margin: 0;">
<button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('Remove your photo?')">Remove Photo</button>
</form>
<form method="POST" action="/loot/<%= pkg.hunt_short_name %>/<%= pkg.unique_code %>/image" enctype="multipart/form-data" style="display: flex; gap: 0.5rem; align-items: center; margin: 0;">
<input type="file" name="image" accept="image/*" class="form-control" style="max-width: 220px; padding: 0.3rem;" required>
<button type="submit" class="btn btn-sm btn-primary">Replace</button>
</form>
</div>
<% } else if (typeof isAdmin !== 'undefined' && isAdmin) { %>
<form method="POST" action="/loot/<%= pkg.hunt_short_name %>/<%= pkg.unique_code %>/image/delete" style="margin-top: 0.75rem;">
<button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('Admin: permanently delete this image?')">Admin Delete</button>
</form>
<% } %>
</div>
<% } %>
@@ -66,6 +81,11 @@
<button type="submit" class="btn btn-primary btn-sm">Save Hint</button>
</form>
<% } %>
<% if (typeof isAdmin !== 'undefined' && isAdmin && pkg.last_scan_hint) { %>
<form method="POST" action="/loot/<%= pkg.hunt_short_name %>/<%= pkg.unique_code %>/hint/delete" style="margin-top: 0.75rem;">
<button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('Admin: clear this hint?')">Admin Clear</button>
</form>
<% } %>
</div>
<% if (scanHistory.length > 0) { %>

View File

@@ -45,6 +45,21 @@
<div class="card">
<div class="card-header">First Finder's Photo</div>
<img src="<%= pkg.first_scan_image %>" alt="Package photo" class="package-image">
<% if (isFirstScanner) { %>
<div style="display: flex; gap: 0.5rem; margin-top: 0.75rem; flex-wrap: wrap;">
<form method="POST" action="/loot/<%= pkg.hunt_short_name %>/<%= pkg.unique_code %>/image/delete" style="margin: 0;">
<button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('Remove your photo?')">Remove Photo</button>
</form>
<form method="POST" action="/loot/<%= pkg.hunt_short_name %>/<%= pkg.unique_code %>/image" enctype="multipart/form-data" style="display: flex; gap: 0.5rem; align-items: center; margin: 0;">
<input type="file" name="image" accept="image/*" class="form-control" style="max-width: 220px; padding: 0.3rem;" required>
<button type="submit" class="btn btn-sm btn-primary">Replace</button>
</form>
</div>
<% } else if (typeof isAdmin !== 'undefined' && isAdmin) { %>
<form method="POST" action="/loot/<%= pkg.hunt_short_name %>/<%= pkg.unique_code %>/image/delete" style="margin-top: 0.75rem;">
<button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('Admin: permanently delete this image?')">Admin Delete</button>
</form>
<% } %>
</div>
<% } %>
@@ -81,6 +96,11 @@
<button type="submit" class="btn btn-primary btn-sm">Save Hint</button>
</form>
<% } %>
<% if (typeof isAdmin !== 'undefined' && isAdmin && pkg.last_scan_hint) { %>
<form method="POST" action="/loot/<%= pkg.hunt_short_name %>/<%= pkg.unique_code %>/hint/delete" style="margin-top: 0.75rem;">
<button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('Admin: clear this hint?')">Admin Clear</button>
</form>
<% } %>
</div>
<%/* Scan history */%>