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();