[object Object]

← back to Robet Site

overnight loop item (g): admin UX — show/hide value toggle + live inline validation (disable Save until valid, name-format hint)

80f29c7a9e7f33f93ada8c29423596add2b2ba57 · 2026-06-29 20:09:16 -0700 · Steve

Files touched

Diff

commit 80f29c7a9e7f33f93ada8c29423596add2b2ba57
Author: Steve <steve@designerwallcoverings.com>
Date:   Mon Jun 29 20:09:16 2026 -0700

    overnight loop item (g): admin UX — show/hide value toggle + live inline validation (disable Save until valid, name-format hint)
---
 public/admin.html | 12 +++++++-----
 public/admin.js   | 34 +++++++++++++++++++++++++++++++---
 2 files changed, 38 insertions(+), 8 deletions(-)

diff --git a/public/admin.html b/public/admin.html
index 833a8b8..4d92c61 100644
--- a/public/admin.html
+++ b/public/admin.html
@@ -21,12 +21,14 @@
     </p>
 
     <!-- add / update -->
-    <div class="keyrow" style="grid-template-columns:1fr 1fr auto">
-      <input id="kName" placeholder="KEY NAME (e.g. OPENAI_API_KEY)" />
-      <input id="kVal" placeholder="value" type="password" />
-      <button class="btn" id="saveBtn">Save</button>
+    <div class="keyrow" style="grid-template-columns:1fr 1fr auto auto">
+      <input id="kName" placeholder="KEY NAME (e.g. OPENAI_API_KEY)" autocomplete="off" spellcheck="false" />
+      <input id="kVal" placeholder="value" type="password" autocomplete="off" spellcheck="false" />
+      <button type="button" class="btn" id="toggleVal" aria-pressed="false" aria-label="Show value" title="Show / hide value">show</button>
+      <button class="btn" id="saveBtn" disabled>Save</button>
     </div>
-    <div class="formmsg" id="msg" style="margin:12px 0 28px"></div>
+    <div class="formmsg" id="hint" style="margin:8px 0 0; opacity:.8">Name: letters, digits, underscores only. Value required.</div>
+    <div class="formmsg" id="msg" style="margin:8px 0 28px"></div>
 
     <!-- stored keys -->
     <div class="keytable" id="table"></div>
diff --git a/public/admin.js b/public/admin.js
index 7bd705b..0d25136 100644
--- a/public/admin.js
+++ b/public/admin.js
@@ -21,16 +21,44 @@ async function load() {
     }));
 }
 
+// ── show/hide value toggle ──
+const toggle = $('#toggleVal'), kVal = $('#kVal');
+toggle.addEventListener('click', () => {
+  const show = kVal.type === 'password';
+  kVal.type = show ? 'text' : 'password';
+  toggle.textContent = show ? 'hide' : 'show';
+  toggle.setAttribute('aria-pressed', String(show));
+  toggle.setAttribute('aria-label', show ? 'Hide value' : 'Show value');
+  kVal.focus();
+});
+
+// ── inline validation (live) ──
+const NAME_RE = /^[A-Za-z0-9_]+$/;
+const hint = $('#hint');
+function validate() {
+  const name = $('#kName').value.trim(), value = kVal.value;
+  let ok = true, m = 'Name: letters, digits, underscores only. Value required.';
+  if (name && !NAME_RE.test(name)) { ok = false; m = '✗ Name allows only letters, digits, underscores (no spaces).'; }
+  else if (!name || !value) { ok = false; }
+  $('#saveBtn').disabled = !ok;
+  hint.textContent = m;
+  hint.style.color = (name && !NAME_RE.test(name)) ? 'var(--accent2)' : '';
+  return ok;
+}
+$('#kName').addEventListener('input', validate);
+kVal.addEventListener('input', validate);
+
 $('#saveBtn').addEventListener('click', async () => {
-  const name = $('#kName').value.trim(), value = $('#kVal').value;
+  const name = $('#kName').value.trim(), value = kVal.value;
   const msg = $('#msg');
-  if (!name || !value) { msg.textContent = '✗ name and value required'; return; }
+  if (!validate()) { msg.textContent = '✗ Fix the highlighted field'; return; }
   const j = await api('/api/keys', {
     method: 'POST', headers: { 'Content-Type': 'application/json' },
     body: JSON.stringify({ name, value }),
   });
   msg.textContent = j.ok ? `✓ Saved ${j.name} (${j.masked})` : '✗ ' + (j.error || 'Failed');
-  if (j.ok) { $('#kName').value = ''; $('#kVal').value = ''; load(); }
+  if (j.ok) { $('#kName').value = ''; kVal.value = ''; validate(); load(); }
 });
 
+validate();
 load();

← 7e8855d loop ledger: item (d) done + deployed  ·  back to Robet Site  ·  loop ledger: item (g) done + deployed cf3d24b →