Lumi/src/web/views/admin-users.ejs
2026-06-15 23:58:24 +02:00

117 lines
4.6 KiB
Plaintext

<%- include("partials/layout-top", { title }) %>
<section class="card">
<%- include("partials/page-header", {
eyebrow: "Community",
pageTitle: "Users",
description: "Review linked identities, notes, and internal usernames."
}) %>
<% if (!users.length) { %>
<div class="empty-state">No users yet.</div>
<% } else { %>
<div class="table-tools">
<input
type="search"
class="table-search"
placeholder="Search users or identities"
data-table-filter="user-list"
/>
</div>
<div class="table-wrap">
<table class="table" data-table="user-list">
<thead>
<tr>
<th data-sort="username">Internal username</th>
<th data-sort="identities">Identities</th>
<th>Notes</th>
<% if (isAdmin) { %>
<th>Update username</th>
<% } %>
</tr>
</thead>
<tbody>
<% users.forEach((user) => { %>
<% const notes = (notesByUser && notesByUser[user.id]) ? notesByUser[user.id] : []; %>
<tr
data-username="<%= user.internal_username.toLowerCase() %>"
data-identities="<%= user.identities.length %>"
data-search="<%= [user.internal_username, ...user.identities.map((identity) => identity.display_name || identity.provider_user_id)].join(' ').toLowerCase() %>"
>
<td><%= user.internal_username %></td>
<td>
<% if (!user.identities.length) { %>
<span>None</span>
<% } else { %>
<ul class="identity-list">
<% user.identities.forEach((identity) => { %>
<li>
<strong><%= identity.provider %></strong>
<span><%= identity.display_name || identity.provider_user_id %></span>
</li>
<% }) %>
</ul>
<% } %>
</td>
<td>
<% if (notes.length) { %>
<button
type="button"
class="button subtle"
data-notes-open="<%= user.id %>"
>
Notes (<%= notes.length %>)
</button>
<% } else { %>
<span class="badge">No notes</span>
<% } %>
</td>
<% if (isAdmin) { %>
<td>
<form method="post" action="/admin/users/<%= user.id %>/username" class="inline-form">
<input name="internal_username" value="<%= user.internal_username %>" />
<button type="submit" class="button subtle">Save</button>
</form>
</td>
<% } %>
</tr>
<% }) %>
</tbody>
</table>
</div>
<% } %>
</section>
<div class="modal-backdrop" data-notes-modal aria-hidden="true">
<div class="modal">
<div class="modal-header">
<h2>User notes</h2>
<button type="button" class="icon-button" data-notes-close aria-label="Close">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6l-12 12" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
</button>
</div>
<div data-notes-body></div>
<div class="modal-actions">
<button type="button" class="button subtle" data-notes-close>Close</button>
</div>
</div>
</div>
<script>
(() => {
const notesByUser = <%- JSON.stringify(notesByUser || {}) %>;
const modal = document.querySelector("[data-notes-modal]");
const body = modal?.querySelector("[data-notes-body]");
const closeButtons = modal?.querySelectorAll("[data-notes-close]") || [];
if (!modal || !body) {
return;
}
const openButtons = document.querySelectorAll("[data-notes-open]");
const openModal = (userId) => {
const notes = notesByUser[userId] || [];
body.innerHTML = notes.length
? notes
.map(
(note) =>
`<div class=\"moderation-note\">\n<strong>${note.created_by_name || "Staff"}</strong>\n<p>${note.note}</p>\n<span class=\"hint\">${new Date(note.created_at).toLocaleString()}</span>\n</div>`
)
.join(\"\\n\")
: \"<p>No notes recorded.</p>\";\n modal.classList.add(\"is-open\");\n modal.setAttribute(\"aria-hidden\", \"false\");\n };\n const closeModal = () => {\n modal.classList.remove(\"is-open\");\n modal.setAttribute(\"aria-hidden\", \"true\");\n };\n openButtons.forEach((button) => {\n button.addEventListener(\"click\", () => openModal(button.dataset.notesOpen));\n });\n closeButtons.forEach((button) => {\n button.addEventListener(\"click\", closeModal);\n });\n modal.addEventListener(\"click\", (event) => {\n if (event.target === modal) {\n closeModal();\n }\n });\n })();\n</script>
<%- include("partials/layout-bottom") %>