Add menu enable toggle, user status, and mobile/UI polish

- Admin menu page: mobile item cards now put Actions in the top-right
  corner next to Status, matching the desktop row layout instead of
  stacking at the bottom; same treatment applied to announcements and
  users for consistency across all three admin lists
- Center button label text everywhere via .btn { justify-content: center }
  instead of one-off overrides
- Add a Disable/Enable Menu toggle to the admin menu toolbar; the public
  menu page shows its existing "menu being updated" message while disabled
- Add an Enabled/Disabled status to user accounts, with its own toggle
  badge, guardrails against disabling your own account or the last
  enabled account, and an immediate session kick for anyone disabled
  mid-session
- Fix the announcement banner / hero tagline "·" separators to hide only
  when their neighboring text actually wraps onto a different line,
  instead of a fixed breakpoint or always-on dot

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
David DyerandClaude Sonnet 5 committed 2026-09-01 14:10:08 -04:00
1 parent eab20899de
commit cd4c6c1969
13 files changed
+405 -50

No files matched your search

+37 -3
View File
@@ -152,10 +152,38 @@ document.getElementById('form-password').addEventListener('submit', async (e) =>
}
});
// Delegate row actions: Change Password, Delete
// Delegate row actions: toggle Status, Change Password, Delete
document.getElementById('users-rows').addEventListener('click', async (e) => {
const { target } = e;
// The status badge itself is the toggle button — same convention as the
// menu items and announcements lists. Self's badge renders as a plain
// (non-button) span, so this never matches your own row.
if (target.classList.contains('btn-toggle-user')) {
target.disabled = true;
const fd = new FormData();
fd.append('action', 'toggle_user_enabled');
fd.append('user_id', target.dataset.id);
fd.append('csrf_token', csrfToken);
try {
const data = await postForm(fd);
if (data.success) {
target.className = `status-badge btn-toggle-user ${data.status_class}`;
target.textContent = data.status_badge;
target.title = `Click to mark as ${data.is_enabled ? 'disabled' : 'enabled'}`;
} else {
alert(data.error || 'Failed to update user status.');
}
} catch {
alert('Network error. Please try again.');
} finally {
target.disabled = false;
}
return;
}
if (target.classList.contains('btn-change-password')) {
openPasswordModal(target.dataset.id, target.dataset.username);
return;
@@ -197,14 +225,20 @@ function insertUserRow(user) {
// data-username is set via dataset below, never interpolated into the
// HTML string — escapeHtml() only escapes for text-content positions,
// not attribute values, so it can't safely build an attribute string.
// A user you just created is never "you" — the current admin's own row
// already exists — so its Status badge always renders as the clickable
// toggle, never the plain self span (see users.php for that variant).
row.innerHTML = `
<div data-label="Status">
<button type="button" class="status-badge btn-toggle-user active" data-id="${user.id}" title="Click to mark as disabled">● Enabled</button>
</div>
<div data-label="Username"><strong>${escapeHtml(user.username)}</strong></div>
<div data-label="Role">${escapeHtml(user.role.charAt(0).toUpperCase() + user.role.slice(1))}</div>
<div class="font-mono timestamp-cell" data-label="Created">${escapeHtml(user.created_at)}</div>
<div data-label="Actions"><div class="row-actions">
<div data-label="Actions"><div class="row-actions"><div class="action-stack">
<button type="button" class="btn btn-outline btn-sm btn-change-password" data-id="${user.id}">Change Password</button>
<button type="button" class="btn btn-danger btn-sm btn-delete-user" data-id="${user.id}">Delete</button>
</div></div>
</div></div></div>
`;
row.querySelector('.btn-change-password').dataset.username = user.username;