From edca300d27bf51ffd1edc90c841e128e32d15d2c Mon Sep 17 00:00:00 2001 From: Zak Bearman Date: Mon, 7 Sep 2026 16:19:12 +1200 Subject: [PATCH] Add bulk enable invite access to user directory --- frontend/app/users/page.tsx | 41 ++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/frontend/app/users/page.tsx b/frontend/app/users/page.tsx index 1fc060a..be49c97 100644 --- a/frontend/app/users/page.tsx +++ b/frontend/app/users/page.tsx @@ -15,6 +15,7 @@ type AdminUser = { lastLoginAt?: string | null isBlocked?: boolean autoSearchEnabled?: boolean + inviteManagementEnabled?: boolean profileId?: number | null expiresAt?: string | null isExpired?: boolean @@ -88,6 +89,8 @@ export default function UsersPage() { const [jellyseerrSyncBusy, setJellyseerrSyncBusy] = useState(false) const [jellyseerrResyncBusy, setJellyseerrResyncBusy] = useState(false) const [bulkAutoSearchBusy, setBulkAutoSearchBusy] = useState(false) + const [bulkInvitesBusy, setBulkInvitesBusy] = useState(false) + const [inviteStatus, setInviteStatus] = useState(null) const loadUsers = async () => { try { @@ -116,6 +119,7 @@ export default function UsersPage() { lastLoginAt: user.last_login_at ?? null, isBlocked: Boolean(user.is_blocked), autoSearchEnabled: Boolean(user.auto_search_enabled ?? true), + inviteManagementEnabled: Boolean(user.invite_management_enabled), profileId: user.profile_id == null || Number.isNaN(Number(user.profile_id)) ? null @@ -192,6 +196,25 @@ export default function UsersPage() { } } + const enableInvitesForEveryone = async () => { + if (bulkInvitesBusy || !window.confirm('Enable invite access for all existing non-admin users, including users outside the current search? Existing invite limits, account blocks and expiry dates will not change.')) return + setBulkInvitesBusy(true) + setInviteStatus(null) + try { + const response = await authFetch(`${getApiBase()}/admin/users/invite-access/bulk`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ enabled: true }), + }) + if (!response.ok) throw new Error('Invite access update failed') + const data = await response.json() + setInviteStatus(`Invite access enabled for ${data.updated ?? 0} non-admin accounts. Existing limits are unchanged.`) + await loadUsers() + } catch { + setInviteStatus('Could not enable invites. Please reload the list to check the current permissions, then try again.') + } finally { setBulkInvitesBusy(false) } + } + const bulkUpdateAutoSearch = async (enabled: boolean) => { setBulkAutoSearchBusy(true) setJellyseerrSyncStatus(null) @@ -233,6 +256,7 @@ export default function UsersPage() { const nonAdminUsers = users.filter((user) => user.role !== 'admin') const autoSearchEnabledCount = nonAdminUsers.filter((user) => user.autoSearchEnabled !== false).length + const inviteEnabledCount = nonAdminUsers.filter((user) => user.inviteManagementEnabled).length const blockedCount = users.filter((user) => user.isBlocked).length const expiredCount = users.filter((user) => user.isExpired).length const adminCount = users.filter((user) => user.role === 'admin').length @@ -348,7 +372,7 @@ export default function UsersPage() {

Bulk controls

- Auto search/download can be enabled or disabled for all non-admin users. + Manage permissions for all existing non-admin users, not just the current search results.

@@ -378,6 +402,21 @@ export default function UsersPage() { +
+
+
+ Invite access + {inviteEnabledCount} of {nonAdminUsers.length} non-admin users enabled + Admins already have access. Existing invite limits, blocked accounts and expiry dates stay unchanged. +
+
+ +
+
+ {inviteStatus &&

{inviteStatus}

} +