From f96883506ebdffd3a5202ff0a5d547849470b2c5 Mon Sep 17 00:00:00 2001 From: Ben Stull Date: Fri, 29 May 2026 22:32:47 -0700 Subject: [PATCH] v0.31.1: admin Users tab + header UX polish MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Visual-only patch atop v0.31.0. CSS plus markup/structure in Admin.jsx; no API, schema, config, overlay, or secret change — a plain frontend rebuild applies it. Two latent CSS defects fixed: - .invite-badge had no rule, so "(pending invite)" rendered as bare parenthetical text; it's now a quiet amber pill. - .btn-link-quiet never reset native + +

The pending bucket is the beta-access review queue (§6.1 / v0.8.0). Grant or revoke writes to permission_events @@ -190,17 +203,6 @@ function UsersTab() { retain their v0.7.0 semantics — promote to admin to remove a user's ability to write without silencing them.

- {/* v0.17.0 — roadmap item #16. The "Create user + invite" - affordance opens a modal that provisions a fresh users row - with the chosen role and sends an invite email with a - single-use claim link. */} -
- -
{error &&

{error}

} {inviteModalOpen && ( @@ -270,21 +272,27 @@ function UserRow({ user: u, busy, onChangeRole, onToggleMute, onFlipPermission } // the admin sees at a glance which rows are real users vs. unclaimed // invites. const pendingInvite = u.pending_invite + // When there's no gitea_login the handle already IS the email, so the + // subline would otherwise repeat it. Only append the email when it adds + // something the handle doesn't already show. + const showEmail = u.email && u.email !== handle return ( <>
- {handle} - {pendingInvite && ( - (pending invite) - )} +
+ {handle} + {pendingInvite && ( + pending invite + )} +
{fullName || u.display_name} - {u.email ? ` · ${u.email}` : ''} + {showEmail ? ` · ${u.email}` : ''}
@@ -317,8 +325,8 @@ function UserRow({ user: u, busy, onChangeRole, onToggleMute, onFlipPermission } N/A )} - {u.created_at || '—'} - {u.last_seen_at || '—'} + + {state === 'pending' && u.beta_request_reason ? ( @@ -334,6 +342,21 @@ function UserRow({ user: u, busy, onChangeRole, onToggleMute, onFlipPermission } ) } +// Render a "YYYY-MM-DD HH:MM:SS" timestamp as an intentional date-over-time +// stack (date prominent, time quiet below) rather than letting a narrow +// column wrap the value mid-string. Falls back to an em-dash when absent. +function TimeCell({ value }) { + if (!value) return — + const [date, ...rest] = String(value).split(' ') + const time = rest.join(' ') + return ( + + {date} + {time && {time}} + + ) +} + function PermissionCell({ user: u, busy, onFlipPermission }) { const state = u.permission_state || 'granted' const decidedSuffix = u.permission_decided_at