diff --git a/faq/index.html b/faq/index.html
index 215ee38..64b7b88 100644
--- a/faq/index.html
+++ b/faq/index.html
@@ -36,6 +36,12 @@
.msg{padding:12px 16px;border-radius:12px;margin-bottom:18px;font-size:.9rem;display:none}
.msg.show{display:block}
.msg.err{background:#fdf1ef;color:#8f2117;border:1px solid #f0c9c4}
+ .auth{background:#fff;border:1px solid var(--line);border-radius:14px;padding:18px 20px;display:flex;gap:12px;flex-wrap:wrap;align-items:flex-end}
+ .auth h2{font-family:Sora,sans-serif;font-size:.95rem;margin:0 0 12px;width:100%;color:var(--muted)}
+ .auth input{flex:1 1 160px;padding:11px 13px;border:1px solid var(--line);border-radius:10px;font:inherit;background:#fff}
+ .auth input:focus{outline:2px solid rgba(108,76,224,.22);border-color:var(--violet-soft)}
+ .auth button{font:inherit;font-weight:600;border-radius:10px;border:1px solid transparent;padding:11px 18px;cursor:pointer;background:var(--violet);color:#fff}
+ .auth .err{width:100%;margin:4px 0 0;font-size:.85rem;color:var(--py-red);font-weight:600;min-height:1em}
.area{margin:0 0 26px}
.area h2{font-family:Sora,sans-serif;font-size:.8rem;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);margin:0 0 10px}
.card{background:#fff;border:1px solid var(--line);border-radius:14px;margin-bottom:10px;overflow:hidden;transition:border-color .18s,box-shadow .18s}
@@ -67,6 +73,14 @@
+
+
@@ -87,6 +101,8 @@ const I18N = {
group: 'General', errRead: 'No se pudo leer el webhook',
errData: 'El webhook respondió sin datos',
hint: 'En n8n, poné el nodo Webhook en "Respond: When Last Node Finishes".',
+ loginTitle: 'Acceso restringido', user: 'Usuario', pass: 'Contraseña', enter: 'Entrar',
+ badCred: 'Usuario o contraseña incorrectos.',
},
en: {
title: 'Frequently asked questions', subtitle: 'OCA Paraguay · internal use', badge: 'not indexed',
@@ -95,6 +111,8 @@ const I18N = {
group: 'General', errRead: 'Could not read the webhook',
errData: 'The webhook returned no data',
hint: 'In n8n, set the Webhook node to "Respond: When Last Node Finishes".',
+ loginTitle: 'Restricted access', user: 'User', pass: 'Password', enter: 'Sign in',
+ badCred: 'Wrong user or password.',
}
};
@@ -209,12 +227,32 @@ function render() {
});
}
+const AUTH_KEY = 'ocaFaqAuth';
+function sGet(k) { try { return sessionStorage.getItem(k); } catch (e) { return null; } }
+function sSet(k, v) { try { sessionStorage.setItem(k, v); } catch (e) {} }
+function sDel(k) { try { sessionStorage.removeItem(k); } catch (e) {} }
+
+function showAuth(on, bad) {
+ el('auth').hidden = !on;
+ document.querySelector('.bar').hidden = on;
+ el('list').hidden = on;
+ el('auth-err').textContent = (on && bad) ? t('badCred') : '';
+ el('count').textContent = '';
+ if (on) setTimeout(() => el('au').focus(), 60);
+}
+
async function load() {
+ const cred = sGet(AUTH_KEY);
+ if (!cred) { showAuth(true); return; }
try {
- const res = await fetch(API, { headers: { Accept: 'application/json' } });
+ const res = await fetch(API, {
+ headers: { Accept: 'application/json', Authorization: 'Basic ' + cred },
+ });
+ if (res.status === 401) { sDel(AUTH_KEY); showAuth(true, true); return; }
if (!res.ok) throw new Error('HTTP ' + res.status);
const payload = await res.json();
state.items = normalize(payload);
+ showAuth(false);
hideMsg();
render();
if (!state.items.length && !Array.isArray(payload)) {
@@ -246,6 +284,16 @@ el('list').addEventListener('toggle', (ev) => {
el('q').addEventListener('input', render);
+el('auth').addEventListener('submit', (ev) => {
+ ev.preventDefault();
+ const u = el('au').value.trim();
+ const p = el('ap').value;
+ if (!u) { el('au').focus(); return; }
+ try { sSet(AUTH_KEY, btoa(u + ':' + p)); } catch (e) { sSet(AUTH_KEY, ''); }
+ el('ap').value = '';
+ load();
+});
+
applyLang();
load();