fix: remaining audit items — anim counter, API 404s, graceful shutdown, null checks

- anim(): guarantee final frame shows exact target value (not target-1)
- API: /api/* catch-all returns 404 JSON instead of SPA fallback HTML
- Server: SIGTERM handler closes pool + server gracefully
- renderTeamBar: null-check allBtn before DOM manipulation
This commit is contained in:
2026-03-10 07:19:21 +08:00
parent 53d8324fb4
commit 337cbb8ade
2 changed files with 20 additions and 4 deletions
+3 -1
View File
@@ -403,8 +403,10 @@ function renderTeamBar(stats) {
// Clear existing member pills (keep the "Everyone" button) // Clear existing member pills (keep the "Everyone" button)
const allBtn = bar.querySelector('[data-member="all"]'); const allBtn = bar.querySelector('[data-member="all"]');
bar.innerHTML = ''; bar.innerHTML = '';
if (allBtn) {
if (activeMember === 'all') allBtn.classList.add('active'); else allBtn.classList.remove('active'); if (activeMember === 'all') allBtn.classList.add('active'); else allBtn.classList.remove('active');
bar.appendChild(allBtn); bar.appendChild(allBtn);
}
stats.members.forEach(m => { stats.members.forEach(m => {
const btn = document.createElement('button'); const btn = document.createElement('button');
@@ -419,7 +421,7 @@ function renderTeamBar(stats) {
function fmtDate(str){try{const d=new Date(str+'T00:00:00');return d.toLocaleDateString('en-GB',{weekday:'short',day:'numeric',month:'short',year:'numeric'});}catch{return str;}} function fmtDate(str){try{const d=new Date(str+'T00:00:00');return d.toLocaleDateString('en-GB',{weekday:'short',day:'numeric',month:'short',year:'numeric'});}catch{return str;}}
function esc(s){const d=document.createElement('div');d.textContent=s;return d.innerHTML;} function esc(s){const d=document.createElement('div');d.textContent=s;return d.innerHTML;}
function anim(el,target,dur){if(!el)return;const t0=performance.now();(function f(now){const p=Math.min((now-t0)/dur,1);el.textContent=Math.floor(target*(1-Math.pow(1-p,3)));if(p<1)requestAnimationFrame(f);})(t0);} function anim(el,target,dur){if(!el)return;const t0=performance.now();(function f(now){const p=Math.min((now-t0)/dur,1);el.textContent=p>=1?target:Math.floor(target*(1-Math.pow(1-p,3)));if(p<1)requestAnimationFrame(f);})(t0);}
document.querySelector('.lv-pill[data-filter="all"]').onclick=()=>filterTo('all'); document.querySelector('.lv-pill[data-filter="all"]').onclick=()=>filterTo('all');
init(); init();
+15 -1
View File
@@ -233,12 +233,26 @@ app.get('/work/', (req, res) => res.redirect(301, '/work/charityright/'));
// Serve static files from html/ directory // Serve static files from html/ directory
app.use(express.static(path.join(__dirname, 'html'))); app.use(express.static(path.join(__dirname, 'html')));
// API 404 — don't let SPA fallback swallow bad API routes
app.all('/api/*', (req, res) => {
res.status(404).json({ error: 'not found' });
});
// SPA fallback: send index.html for unmatched routes // SPA fallback: send index.html for unmatched routes
app.get('*', (req, res) => { app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'html', 'index.html')); res.sendFile(path.join(__dirname, 'html', 'index.html'));
}); });
const PORT = process.env.PORT || 3000; const PORT = process.env.PORT || 3000;
app.listen(PORT, '0.0.0.0', () => { const server = app.listen(PORT, '0.0.0.0', () => {
console.log(`QuikCue Site server listening on :${PORT}`); console.log(`QuikCue Site server listening on :${PORT}`);
}); });
// Graceful shutdown
process.on('SIGTERM', () => {
console.log('SIGTERM received, shutting down...');
server.close(() => {
if (pool) pool.end();
process.exit(0);
});
});