117 lines
4.6 KiB
Plaintext
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") %>
|