Manage Profiles: make the login-password state visible (clarity)

The per-member login password was easy to miss — the lock button had no dedicated
styling and nothing showed who was actually stranded. Now, when login mode is on:

- a banner explains every member needs a login password (+ to use the lock button)
- each member row shows a status pill: "⚠ No login password" (red) or "🔒 Login
  ready" (green) — so you can see at a glance who can't sign in yet
- the lock button is properly styled (it had none) and pulses red when that member
  has no password, so the action you need is obvious

When login mode is off, none of this shows (no noise). Pure UI/clarity — no
behavior change.
This commit is contained in:
BoulderBadgeDad 2026-06-11 21:10:02 -07:00
parent 5b52d579c5
commit d9cda0c31c
2 changed files with 81 additions and 1 deletions

View file

@ -1873,6 +1873,22 @@ async function loadProfileManageList() {
const data = await res.json();
const profiles = data.profiles || [];
// Login-mode aware: when it's on, surface which members can't sign in yet
// (no login password) so the lock button's purpose is obvious.
let loginMode = false;
try { loginMode = !!(await (await fetch('/api/profiles/current')).json()).login_mode; } catch (e) { /* ignore */ }
// Banner when login mode is on (explains the password requirement up front).
const banner = document.getElementById('profile-manage-login-banner');
if (banner) banner.remove();
if (loginMode) {
const b = document.createElement('div');
b.id = 'profile-manage-login-banner';
b.className = 'profile-manage-login-banner';
b.textContent = '🔐 Login mode is on — every member needs a login password to sign in. Use the lock button to set one.';
list.parentNode.insertBefore(b, list);
}
list.innerHTML = '';
profiles.forEach(p => {
const item = document.createElement('div');
@ -1896,6 +1912,12 @@ async function loadProfileManageList() {
if (p.is_admin) pills.push({ text: 'Admin', cls: 'profile-role-pill--admin' });
if (p.can_download === false) pills.push({ text: 'No Downloads', cls: '' });
if (p.allowed_pages) pills.push({ text: `${p.allowed_pages.length} pages`, cls: '' });
// Login-password status (only meaningful while login mode is on).
if (loginMode && !p.is_admin) {
pills.push(p.has_password
? { text: '🔒 Login ready', cls: 'profile-role-pill--ok' }
: { text: '⚠ No login password', cls: 'profile-role-pill--warn' });
}
if (pills.length) {
const roleDiv = document.createElement('div');
roleDiv.className = 'role';
@ -1931,7 +1953,9 @@ async function loadProfileManageList() {
// used when "Require login" is on). A member with no password can't
// sign in and can't self-bootstrap one, so the admin sets it here.
const pwBtn = document.createElement('button');
pwBtn.className = 'profile-password-btn' + (p.has_password ? ' has-password' : '');
// Pulse the button when login's on and this member can't sign in yet.
const needsPw = loginMode && !p.has_password;
pwBtn.className = 'profile-password-btn' + (p.has_password ? ' has-password' : '') + (needsPw ? ' needs-password' : '');
pwBtn.dataset.id = p.id;
pwBtn.dataset.name = p.name;
pwBtn.dataset.hasPassword = p.has_password ? '1' : '0';

View file

@ -68187,3 +68187,59 @@ body.em-scroll-lock { overflow: hidden; }
body.app-locked > *:not(#launch-pin-overlay):not(#login-overlay):not(script):not(style):not(noscript) {
display: none !important;
}
/* Login-password clarity in Manage Profiles (#login-mode) */
.profile-manage-login-banner {
margin: 0 0 12px;
padding: 9px 12px;
border-radius: 10px;
font-size: 12px;
line-height: 1.4;
color: #fcd9a8;
background: rgba(245, 158, 11, 0.10);
border: 1px solid rgba(245, 158, 11, 0.30);
}
.profile-role-pill--warn {
background: rgba(239, 68, 68, 0.16);
color: #fca5a5;
border-color: rgba(239, 68, 68, 0.40);
text-transform: none;
}
.profile-role-pill--ok {
background: rgba(16, 185, 129, 0.14);
color: #6ee7b7;
border-color: rgba(16, 185, 129, 0.30);
text-transform: none;
}
/* The set-login-password button (lock icon) + an attention pulse when a member
can't sign in yet (login on, no password). */
.profile-manage-item .profile-password-btn {
background: rgba(255, 255, 255, 0.04);
border: 1px solid rgba(255, 255, 255, 0.07);
color: #9ca3af;
border-radius: 8px;
padding: 6px;
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
transition: all .15s ease;
}
.profile-manage-item .profile-password-btn svg { width: 15px; height: 15px; }
.profile-manage-item .profile-password-btn:hover {
background: rgba(99, 102, 241, 0.18);
border-color: rgba(99, 102, 241, 0.4);
color: #c7d2fe;
}
.profile-manage-item .profile-password-btn.has-password { color: #6ee7b7; }
.profile-manage-item .profile-password-btn.needs-password {
color: #fca5a5;
border-color: rgba(239, 68, 68, 0.45);
background: rgba(239, 68, 68, 0.12);
animation: arecPwPulse 1.8s ease-in-out infinite;
}
@keyframes arecPwPulse {
0%, 100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.0); }
50% { box-shadow: 0 0 0 4px rgba(239, 68, 68, 0.18); }
}