From ee4aca58e193e04de58169ed276e7ffa555b83dc Mon Sep 17 00:00:00 2001 From: oliver Date: Wed, 10 Jun 2026 17:34:36 -0300 Subject: [PATCH] login --- app.js | 145 ++++++++++++++++++++++++++++++++++++++++++++++++++++- index.html | 123 +++++++++++++++------------------------------ styles.css | 4 ++ 3 files changed, 188 insertions(+), 84 deletions(-) diff --git a/app.js b/app.js index 987bd9f..b300df9 100644 --- a/app.js +++ b/app.js @@ -132,8 +132,8 @@ function switchAuthTab(tab) { document.getElementById("signin-error").style.display = "none"; document.getElementById("signup-error").style.display = "none"; document.getElementById("signup-success").style.display = "none"; - const _rpw = document.getElementById("reset-pw-wrap"); - if (_rpw) _rpw.style.display = "none"; + const note = document.getElementById("signin-reset-note"); + if (note) note.style.display = "none"; } /* ═══════════════════════════════════════════════════════════════ @@ -1294,3 +1294,144 @@ function exportInvoicesCSV() { document.body.removeChild(a); URL.revokeObjectURL(url); } + +/* ═══════════════════════════════════════════════════════════════ + HELPERS +═══════════════════════════════════════════════════════════════ */ +function escHtml(s) { + if (typeof s !== "string" && typeof s !== "number") return ""; + return String(s) + .replace(/&/g, "&") + .replace(/"/g, """) + .replace(/'/g, "'") + .replace(//g, ">"); +} + +function escAttr(s) { + if (typeof s !== "string" && typeof s !== "number") return ""; + return String(s) + .replace(/&/g, "&") + .replace(/"/g, """) + .replace(/'/g, "'"); +} + +function isValidEmail(e) { + return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e); +} + +function formatBackupDate(s) { + if (!s) return "—"; + const d = new Date(s); + if (isNaN(d.getTime())) return s; + return d.toLocaleString("en-US", { + month: "short", + day: "numeric", + year: "numeric", + hour: "2-digit", + minute: "2-digit", + }); +} + +function formatDate(s) { + if (!s) return "—"; + const d = new Date(s); + if (isNaN(d.getTime())) return s; + const month = String(d.getMonth() + 1).padStart(2, "0"); + const day = String(d.getDate()).padStart(2, "0"); + const year = d.getFullYear(); + const hour = String(d.getHours()).padStart(2, "0"); + const minute = String(d.getMinutes()).padStart(2, "0"); + return `${year}-${month}-${day} ${hour}:${minute}`; +} + +function show(id) { + const el = document.getElementById(id); + if (el) el.style.display = ""; +} + +function hide(id) { + const el = document.getElementById(id); + if (el) el.style.display = "none"; +} + +function btnLoad(btn, label) { + if (!btn) return; + btn._origHTML = btn.innerHTML; + btn.disabled = true; + btn.innerHTML = + label + + ' '; +} + +function btnReset(btn) { + if (!btn) return; + btn.disabled = false; + btn.innerHTML = btn._origHTML || btn.innerHTML; +} + +function showAuthError(id, msg) { + const el = document.getElementById(id); + if (!el) return; + el.innerHTML = `${escHtml(msg)}`; + el.style.display = "flex"; +} + +function hideAuthError(id) { + const el = document.getElementById(id); + if (!el) return; + el.style.display = "none"; +} + +function showResetOption() { + const note = document.getElementById("signin-reset-note"); + if (note) note.style.display = ""; +} + +async function sendPasswordReset() { + const email = document.getElementById("signin-email").value.trim(); + if (!isValidEmail(email)) { + toast("Please enter a valid email address.", "warning"); + return; + } + const btn = document.getElementById("signin-reset-btn"); + if (btn) btn.disabled = true; + try { + await fetch(PW_RESET_URL, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ email }), + }); + // Always show success — never reveal whether address exists + const note = document.getElementById("signin-reset-note"); + if (note) note.style.display = ""; + } catch (_) { + // silently ignore + } finally { + if (btn) btn.disabled = false; + } +} + +function setCookie(name, value, days) { + const expires = new Date(Date.now() + days * 864e5).toUTCString(); + document.cookie = `${name}=${encodeURIComponent(value)}; expires=${expires}; path=/; SameSite=Lax`; +} + +function getCookie(name) { + const k = `${name}=`; + const parts = document.cookie.split("; "); + for (const p of parts) { + if (p.startsWith(k)) return decodeURIComponent(p.slice(k.length)); + } + return null; +} + +function deleteCookie(name) { + document.cookie = `${name}=; max-age=0; path=/`; +} + +async function apiFetch(url, opts) { + const headers = { ...(opts?.headers || {}) }; + if (currentSession) headers["X-Session-Id"] = currentSession; + return fetch(url, { ...opts, headers }); +} diff --git a/index.html b/index.html index 8b15285..77f138d 100644 --- a/index.html +++ b/index.html @@ -132,93 +132,52 @@
- - + First time customer? A password reset email has + already been sent to your address. + diff --git a/styles.css b/styles.css index c612919..0489b7a 100644 --- a/styles.css +++ b/styles.css @@ -410,6 +410,10 @@ body::after { padding: 20px 24px 28px; } +.signin-footer { + margin-top: 4px; +} + .auth-error { display: none; align-items: flex-start;