/* ========================================================================== DRE Customer Portal — shared JS helpers Dependency-free. Relative /api/* calls only (same-origin; Caddy proxies). ========================================================================== */ (function (global) { "use strict"; var SESSION_KEY = "dre_session_token"; /** * Perform a JSON fetch against the API. * @param {string} path - relative API path, e.g. "/api/claims" * @param {object} opts - { method, body, auth, headers } * Returns a Promise resolving to { ok, status, data } where data is the * parsed JSON body (success payload or {error:{code,message}}). */ function apiFetch(path, opts) { opts = opts || {}; var headers = Object.assign({}, opts.headers || {}); var fetchOpts = { method: opts.method || "GET", headers: headers }; if (opts.body !== undefined && !(opts.body instanceof FormData)) { headers["Content-Type"] = "application/json"; fetchOpts.body = JSON.stringify(opts.body); } else if (opts.body instanceof FormData) { fetchOpts.body = opts.body; // browser sets multipart boundary } if (opts.auth) { var token = getSessionToken(); if (token) { headers["Authorization"] = "Bearer " + token; } } return fetch(path, fetchOpts) .then(function (res) { return res .json() .catch(function () { return {}; }) .then(function (data) { return { ok: res.ok, status: res.status, data: data }; }); }) .catch(function (err) { return { ok: false, status: 0, data: { error: { code: "network_error", message: "Could not reach the server. Check your connection and try again.", }, }, }; }); } function getErrorMessage(result, fallback) { if (result && result.data && result.data.error && result.data.error.message) { return result.data.error.message; } return fallback || "Something went wrong. Please try again."; } function getErrorCode(result) { if (result && result.data && result.data.error && result.data.error.code) { return result.data.error.code; } return null; } function getSessionToken() { try { return localStorage.getItem(SESSION_KEY); } catch (e) { return null; } } function setSessionToken(token) { try { localStorage.setItem(SESSION_KEY, token); } catch (e) { /* localStorage unavailable; session will not persist across reload */ } } function clearSessionToken() { try { localStorage.removeItem(SESSION_KEY); } catch (e) { /* ignore */ } } function requireSessionOrRedirect(loginUrl) { var token = getSessionToken(); if (!token) { window.location.href = loginUrl || "login.html"; return null; } return token; } function formatCentsUSD(cents) { var n = typeof cents === "number" ? cents : parseInt(cents, 10) || 0; return "$" + (n / 100).toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 }); } function formatDate(iso) { if (!iso) return "--"; try { var d = new Date(iso); if (isNaN(d.getTime())) return iso; return d.toLocaleDateString("en-US", { year: "numeric", month: "short", day: "numeric" }); } catch (e) { return iso; } } function formatDateTime(iso) { if (!iso) return "--"; try { var d = new Date(iso); if (isNaN(d.getTime())) return iso; return d.toLocaleString("en-US", { year: "numeric", month: "short", day: "numeric", hour: "numeric", minute: "2-digit", }); } catch (e) { return iso; } } function formatBytes(n) { if (!n && n !== 0) return ""; if (n < 1024) return n + " B"; if (n < 1024 * 1024) return (n / 1024).toFixed(1) + " KB"; return (n / (1024 * 1024)).toFixed(1) + " MB"; } function showAlert(el, message, isVisible) { if (!el) return; el.textContent = message || ""; if (isVisible === false) { el.classList.remove("is-visible"); } else { el.classList.add("is-visible"); } } function hideAlert(el) { if (!el) return; el.classList.remove("is-visible"); el.textContent = ""; } function setLoading(button, loading, loadingText, normalText) { if (!button) return; if (loading) { button.disabled = true; button.dataset.originalText = button.dataset.originalText || button.innerHTML; button.innerHTML = ' ' + (loadingText || "Please wait..."); } else { button.disabled = false; button.innerHTML = normalText || button.dataset.originalText || button.innerHTML; } } function badgeClass(status) { return "dre-badge dre-badge-" + (status || "").toLowerCase(); } // Escape helper for any spot where we must build markup with dynamic text. // Prefer textContent everywhere; this exists only as a defensive fallback. function escapeHtml(str) { var div = document.createElement("div"); div.textContent = str === undefined || str === null ? "" : String(str); return div.innerHTML; } global.DRE = { SESSION_KEY: SESSION_KEY, apiFetch: apiFetch, getErrorMessage: getErrorMessage, getErrorCode: getErrorCode, getSessionToken: getSessionToken, setSessionToken: setSessionToken, clearSessionToken: clearSessionToken, requireSessionOrRedirect: requireSessionOrRedirect, formatCentsUSD: formatCentsUSD, formatDate: formatDate, formatDateTime: formatDateTime, formatBytes: formatBytes, showAlert: showAlert, hideAlert: hideAlert, setLoading: setLoading, badgeClass: badgeClass, escapeHtml: escapeHtml, }; })(window);