Implement reliability improvements across frontend

- api.js: add exponential backoff retry (3 attempts, 500/1000/2000ms) for
  GET requests on network errors and 5xx responses; mutating methods are
  not retried since they are not idempotent
- api.js: add offline indicator — fixed pill banner appears at bottom of
  page when navigator goes offline, disappears when back online
- style.css: add styles for offline banner and session expiry warning
- auth.js: show amber warning banner below nav when session expires within
  24 hours (with exact hours remaining); dismissible with X button
- auth.js: fix password min-length client-side check from 6 to 10 to
  match the backend
- log.js, flock.js, budget.js: disable submit button during async request
  and re-enable in finally block to prevent double-submits and make loading
  state visible
- dashboard.js: fix chart date labels to use user's configured timezone
  instead of the browser's local timezone

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-18 00:09:36 -07:00
parent 60fed6d464
commit ce1e9c5134
7 changed files with 122 additions and 17 deletions

View File

@@ -87,10 +87,26 @@ function buildTimezoneOptions(selected) {
// ── Nav + settings modal ──────────────────────────────────────────────────────
function _checkSessionExpiry(user) {
const msLeft = (user.exp * 1000) - Date.now();
const hoursLeft = msLeft / (1000 * 60 * 60);
if (hoursLeft > 24) return;
const warning = document.createElement('div');
warning.id = 'session-warning';
const label = hoursLeft < 1
? 'Your session expires in less than an hour — please log out and back in.'
: `Your session expires in ${Math.floor(hoursLeft)} hours — please log out and back in.`;
warning.innerHTML = `<span>${label}</span><button title="Dismiss" onclick="this.parentElement.remove()">&#x2715;</button>`;
document.querySelector('.nav')?.insertAdjacentElement('afterend', warning);
}
function initNav() {
const user = Auth.requireAuth();
if (!user) return;
_checkSessionExpiry(user);
const nav = document.querySelector('.nav');
if (!nav) return;
@@ -201,8 +217,8 @@ async function submitPasswordChange() {
msgEl.className = 'message error visible';
return;
}
if (newPw.length < 6) {
msgEl.textContent = 'Password must be at least 6 characters';
if (newPw.length < 10) {
msgEl.textContent = 'Password must be at least 10 characters';
msgEl.className = 'message error visible';
return;
}