Sync dev changes into release-1.0
This commit is contained in:
@@ -25,6 +25,9 @@ export default function UsersPage() {
|
||||
const [users, setUsers] = useState<AdminUser[]>([])
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [selected, setSelected] = useState<string[]>([])
|
||||
const [bulkAction, setBulkAction] = useState('block')
|
||||
const [bulkRole, setBulkRole] = useState('user')
|
||||
|
||||
const loadUsers = async () => {
|
||||
try {
|
||||
@@ -103,6 +106,43 @@ export default function UsersPage() {
|
||||
}
|
||||
}
|
||||
|
||||
const toggleSelect = (username: string, isChecked: boolean) => {
|
||||
setSelected((current) =>
|
||||
isChecked ? [...new Set([...current, username])] : current.filter((name) => name !== username)
|
||||
)
|
||||
}
|
||||
|
||||
const toggleSelectAll = (isChecked: boolean) => {
|
||||
setSelected(isChecked ? users.map((user) => user.username) : [])
|
||||
}
|
||||
|
||||
const runBulkAction = async () => {
|
||||
if (selected.length === 0) {
|
||||
setError('Select at least one user to run a bulk action.')
|
||||
return
|
||||
}
|
||||
try {
|
||||
const baseUrl = getApiBase()
|
||||
const response = await authFetch(`${baseUrl}/admin/users/bulk`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
action: bulkAction,
|
||||
role: bulkRole,
|
||||
usernames: selected,
|
||||
}),
|
||||
})
|
||||
if (!response.ok) {
|
||||
throw new Error('Bulk update failed')
|
||||
}
|
||||
setSelected([])
|
||||
await loadUsers()
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
setError('Could not run the bulk action.')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (!getToken()) {
|
||||
@@ -128,6 +168,35 @@ export default function UsersPage() {
|
||||
>
|
||||
<section className="admin-section">
|
||||
{error && <div className="error-banner">{error}</div>}
|
||||
{users.length > 0 && (
|
||||
<div className="summary-card user-bulk-bar">
|
||||
<label className="toggle">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selected.length === users.length}
|
||||
onChange={(event) => toggleSelectAll(event.target.checked)}
|
||||
/>
|
||||
<span>Select all</span>
|
||||
</label>
|
||||
<div className="user-bulk-actions">
|
||||
<select value={bulkAction} onChange={(event) => setBulkAction(event.target.value)}>
|
||||
<option value="block">Block access</option>
|
||||
<option value="unblock">Allow access</option>
|
||||
<option value="role">Set role</option>
|
||||
<option value="delete">Delete users</option>
|
||||
</select>
|
||||
{bulkAction === 'role' && (
|
||||
<select value={bulkRole} onChange={(event) => setBulkRole(event.target.value)}>
|
||||
<option value="user">User</option>
|
||||
<option value="admin">Admin</option>
|
||||
</select>
|
||||
)}
|
||||
<button type="button" onClick={runBulkAction}>
|
||||
Apply to {selected.length} selected
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{users.length === 0 ? (
|
||||
<div className="status-banner">No users found yet.</div>
|
||||
) : (
|
||||
@@ -135,6 +204,14 @@ export default function UsersPage() {
|
||||
{users.map((user) => (
|
||||
<div key={user.username} className="summary-card user-card">
|
||||
<div>
|
||||
<label className="toggle">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selected.includes(user.username)}
|
||||
onChange={(event) => toggleSelect(user.username, event.target.checked)}
|
||||
/>
|
||||
<span>Select</span>
|
||||
</label>
|
||||
<strong>{user.username}</strong>
|
||||
<span className="meta">Role: {user.role}</span>
|
||||
<span className="meta">Login type: {user.authProvider || 'local'}</span>
|
||||
|
||||
Reference in New Issue
Block a user