Added credential persistance
This commit is contained in:
+77
-14
@@ -6,7 +6,7 @@
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, user-scalable=no"
|
||||
/>
|
||||
<title>Health Routine – Product Scanner</title>
|
||||
<title>Health Routine – Package Scanner</title>
|
||||
<style>
|
||||
*,
|
||||
*::before,
|
||||
@@ -808,7 +808,7 @@
|
||||
</div>
|
||||
<span class="brand-name">Health Routine</span>
|
||||
<div class="brand-divider"></div>
|
||||
<span class="brand-subtitle">Product Scanner</span>
|
||||
<span class="brand-subtitle">Package Scanner</span>
|
||||
</div>
|
||||
|
||||
<!-- Controls bar -->
|
||||
@@ -898,7 +898,7 @@
|
||||
</div>
|
||||
<div class="empty-title">No scans yet</div>
|
||||
<div class="empty-hint">
|
||||
Select a user, then scan or type a product code and
|
||||
Select a user, then scan or type a package code and
|
||||
press Enter
|
||||
</div>
|
||||
</div>
|
||||
@@ -994,6 +994,51 @@
|
||||
const modalError = document.getElementById("modalError");
|
||||
const toast = document.getElementById("toast");
|
||||
|
||||
// ── Cookie helpers ────────────────────────────────────────────────────────
|
||||
function saveCreds(user, pass) {
|
||||
const val = btoa(
|
||||
encodeURIComponent(JSON.stringify({ user, pass })),
|
||||
);
|
||||
document.cookie = `hr_auth=${val}; max-age=${30 * 24 * 3600}; SameSite=Strict; path=/`;
|
||||
}
|
||||
|
||||
function loadCredsFromCookie() {
|
||||
const match = document.cookie.match(
|
||||
/(?:^|;\s*)hr_auth=([^;]+)/,
|
||||
);
|
||||
if (match) {
|
||||
try {
|
||||
return JSON.parse(decodeURIComponent(atob(match[1])));
|
||||
} catch (_) {}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function clearCredsCookie() {
|
||||
document.cookie = "hr_auth=; max-age=0; path=/";
|
||||
}
|
||||
|
||||
// ── JSON parser (handles array, single object, and NDJSON) ───────────────
|
||||
async function parseJson(res) {
|
||||
const text = await res.text();
|
||||
// Standard JSON (array or object)
|
||||
try {
|
||||
const parsed = JSON.parse(text);
|
||||
return parsed;
|
||||
} catch (_) {}
|
||||
// NDJSON — n8n sometimes returns one object per line
|
||||
try {
|
||||
const lines = text
|
||||
.trim()
|
||||
.split("\n")
|
||||
.filter((l) => l.trim());
|
||||
if (lines.length > 0) {
|
||||
return lines.map((l) => JSON.parse(l));
|
||||
}
|
||||
} catch (_) {}
|
||||
throw new Error("Could not parse server response");
|
||||
}
|
||||
|
||||
// ── Helpers ──────────────────────────────────────────────────────────────
|
||||
function esc(str) {
|
||||
return String(str ?? "")
|
||||
@@ -1052,10 +1097,16 @@
|
||||
function needAuth(retryCb) {
|
||||
pendingAuthCb = retryCb;
|
||||
modalError.style.display = "none";
|
||||
authUser.value = "";
|
||||
authPass.value = "";
|
||||
// Pre-fill from saved cookie so user only needs to confirm
|
||||
const saved = loadCredsFromCookie();
|
||||
authUser.value = saved?.user || "";
|
||||
authPass.value = saved?.pass || "";
|
||||
authModal.classList.remove("hidden");
|
||||
setTimeout(() => authUser.focus(), 50);
|
||||
// Focus password if username already filled, otherwise username
|
||||
setTimeout(
|
||||
() => (saved ? authPass.focus() : authUser.focus()),
|
||||
50,
|
||||
);
|
||||
}
|
||||
|
||||
function cancelAuth() {
|
||||
@@ -1075,6 +1126,7 @@
|
||||
}
|
||||
|
||||
creds = { user: u, pass: p };
|
||||
saveCreds(u, p); // persist to cookie
|
||||
authBtn.classList.add("loading");
|
||||
authBtn.disabled = true;
|
||||
modalError.style.display = "none";
|
||||
@@ -1120,7 +1172,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
const data = await parseJson(res);
|
||||
buildDropdown(data);
|
||||
hideBanner();
|
||||
} catch (err) {
|
||||
@@ -1164,7 +1216,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
showBanner("Looking up product…", "loading");
|
||||
showBanner("Looking up package…", "loading");
|
||||
|
||||
try {
|
||||
const res = await fetch(WEBHOOK, {
|
||||
@@ -1182,16 +1234,24 @@
|
||||
}
|
||||
|
||||
if (!res.ok) {
|
||||
showBanner(
|
||||
`Scan failed (HTTP ${res.status}).`,
|
||||
"error",
|
||||
5000,
|
||||
);
|
||||
// Try to extract a descriptive error from the response body
|
||||
let errMsg = `Scan failed (HTTP ${res.status}).`;
|
||||
try {
|
||||
const errData = await parseJson(res);
|
||||
if (errData?.error) errMsg = errData.error;
|
||||
} catch (_) {}
|
||||
showBanner(errMsg, "error", 0); // persistent until next action
|
||||
scanInput.select();
|
||||
return;
|
||||
}
|
||||
|
||||
const item = await res.json();
|
||||
const item = await parseJson(res);
|
||||
// Webhook may return 200 but carry an error field
|
||||
if (item?.error) {
|
||||
showBanner(item.error, "error", 0);
|
||||
scanInput.select();
|
||||
return;
|
||||
}
|
||||
hideBanner();
|
||||
addRow(item);
|
||||
showToast(
|
||||
@@ -1265,6 +1325,9 @@
|
||||
|
||||
// ── Init ─────────────────────────────────────────────────────────────────
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
// Restore saved credentials before the first network call
|
||||
const saved = loadCredsFromCookie();
|
||||
if (saved) creds = saved;
|
||||
loadUsers();
|
||||
scanInput.focus();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user