login
This commit is contained in:
@@ -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, "<")
|
||||
.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 +
|
||||
' <span class="spinner-sm" style="display:inline-block;vertical-align:middle;margin-left:4px"></span>';
|
||||
}
|
||||
|
||||
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 = `<span>${escHtml(msg)}</span>`;
|
||||
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 });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user