Skip to content

Commit 83fc33a

Browse files
waleedlatif1claude
andcommitted
feat(admin): add user search by email and ID, remove table border
- Replace Load Users button with a live search input; query fires on any input - Email search uses listUsers with contains operator - User ID search (UUID format) uses admin.getUser directly for exact lookup - Remove outer border on user table that rendered white in dark mode - Reset pagination to page 0 on new search Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 67478bb commit 83fc33a

File tree

2 files changed

+60
-37
lines changed

2 files changed

+60
-37
lines changed

apps/sim/app/workspace/[workspaceId]/settings/components/admin/admin.tsx

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,15 @@ export function Admin() {
3131

3232
const [workflowId, setWorkflowId] = useState('')
3333
const [usersOffset, setUsersOffset] = useState(0)
34-
const [usersEnabled, setUsersEnabled] = useState(false)
34+
const [searchQuery, setSearchQuery] = useState('')
3535
const [banUserId, setBanUserId] = useState<string | null>(null)
3636
const [banReason, setBanReason] = useState('')
3737

3838
const {
3939
data: usersData,
4040
isLoading: usersLoading,
4141
error: usersError,
42-
refetch: refetchUsers,
43-
} = useAdminUsers(usersOffset, PAGE_SIZE, usersEnabled)
42+
} = useAdminUsers(usersOffset, PAGE_SIZE, searchQuery)
4443

4544
const totalPages = useMemo(
4645
() => Math.ceil((usersData?.total ?? 0) / PAGE_SIZE),
@@ -62,14 +61,6 @@ export function Admin() {
6261
)
6362
}
6463

65-
const handleLoadUsers = () => {
66-
if (usersEnabled) {
67-
refetchUsers()
68-
} else {
69-
setUsersEnabled(true)
70-
}
71-
}
72-
7364
const pendingUserIds = useMemo(() => {
7465
const ids = new Set<string>()
7566
if (setUserRole.isPending && (setUserRole.variables as { userId?: string })?.userId)
@@ -136,12 +127,15 @@ export function Admin() {
136127
<div className='h-px bg-[var(--border-secondary)]' />
137128

138129
<div className='flex flex-col gap-[12px]'>
139-
<div className='flex items-center justify-between'>
140-
<p className='font-medium text-[14px] text-[var(--text-primary)]'>User Management</p>
141-
<Button variant='active' onClick={handleLoadUsers} disabled={usersLoading}>
142-
{usersLoading ? 'Loading...' : usersEnabled ? 'Refresh' : 'Load Users'}
143-
</Button>
144-
</div>
130+
<p className='font-medium text-[14px] text-[var(--text-primary)]'>User Management</p>
131+
<EmcnInput
132+
value={searchQuery}
133+
onChange={(e) => {
134+
setSearchQuery(e.target.value)
135+
setUsersOffset(0)
136+
}}
137+
placeholder='Search by email or paste a user ID...'
138+
/>
145139

146140
{usersError && (
147141
<p className='text-[13px] text-[var(--text-error)]'>
@@ -166,7 +160,7 @@ export function Admin() {
166160

167161
{usersData && (
168162
<>
169-
<div className='flex flex-col gap-[2px] rounded-[8px] border border-[var(--border-secondary)]'>
163+
<div className='flex flex-col gap-[2px]'>
170164
<div className='flex items-center gap-[12px] border-[var(--border-secondary)] border-b px-[12px] py-[8px] text-[12px] text-[var(--text-tertiary)]'>
171165
<span className='w-[200px]'>Name</span>
172166
<span className='flex-1'>Email</span>
@@ -176,7 +170,7 @@ export function Admin() {
176170
</div>
177171

178172
{usersData.users.length === 0 && (
179-
<div className='px-[12px] py-[16px] text-center text-[13px] text-[var(--text-tertiary)]'>
173+
<div className='py-[16px] text-center text-[13px] text-[var(--text-tertiary)]'>
180174
No users found.
181175
</div>
182176
)}

apps/sim/hooks/queries/admin-users.ts

Lines changed: 47 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ const logger = createLogger('AdminUsersQuery')
77
export const adminUserKeys = {
88
all: ['adminUsers'] as const,
99
lists: () => [...adminUserKeys.all, 'list'] as const,
10-
list: (offset: number, limit: number) => [...adminUserKeys.lists(), offset, limit] as const,
10+
list: (offset: number, limit: number, searchQuery: string) =>
11+
[...adminUserKeys.lists(), offset, limit, searchQuery] as const,
1112
}
1213

1314
interface AdminUser {
@@ -24,31 +25,59 @@ interface AdminUsersResponse {
2425
total: number
2526
}
2627

27-
async function fetchAdminUsers(offset: number, limit: number): Promise<AdminUsersResponse> {
28+
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
29+
30+
function mapUser(u: {
31+
id: string
32+
name: string
33+
email: string
34+
role?: string | null
35+
banned?: boolean | null
36+
banReason?: string | null
37+
}): AdminUser {
38+
return {
39+
id: u.id,
40+
name: u.name || '',
41+
email: u.email,
42+
role: u.role ?? 'user',
43+
banned: u.banned ?? false,
44+
banReason: u.banReason ?? null,
45+
}
46+
}
47+
48+
async function fetchAdminUsers(
49+
offset: number,
50+
limit: number,
51+
searchQuery: string
52+
): Promise<AdminUsersResponse> {
53+
if (UUID_REGEX.test(searchQuery.trim())) {
54+
const { data, error } = await client.admin.getUser({ query: { id: searchQuery.trim() } })
55+
if (error) throw new Error(error.message ?? 'Failed to fetch user')
56+
if (!data) return { users: [], total: 0 }
57+
return { users: [mapUser(data)], total: 1 }
58+
}
59+
2860
const { data, error } = await client.admin.listUsers({
29-
query: { limit, offset },
61+
query: {
62+
limit,
63+
offset,
64+
searchField: 'email',
65+
searchValue: searchQuery,
66+
searchOperator: 'contains',
67+
},
3068
})
31-
if (error) {
32-
throw new Error(error.message ?? 'Failed to fetch users')
33-
}
69+
if (error) throw new Error(error.message ?? 'Failed to fetch users')
3470
return {
35-
users: (data?.users ?? []).map((u) => ({
36-
id: u.id,
37-
name: u.name || '',
38-
email: u.email,
39-
role: u.role ?? 'user',
40-
banned: u.banned ?? false,
41-
banReason: u.banReason ?? null,
42-
})),
71+
users: (data?.users ?? []).map(mapUser),
4372
total: data?.total ?? 0,
4473
}
4574
}
4675

47-
export function useAdminUsers(offset: number, limit: number, enabled: boolean) {
76+
export function useAdminUsers(offset: number, limit: number, searchQuery: string) {
4877
return useQuery({
49-
queryKey: adminUserKeys.list(offset, limit),
50-
queryFn: () => fetchAdminUsers(offset, limit),
51-
enabled,
78+
queryKey: adminUserKeys.list(offset, limit, searchQuery),
79+
queryFn: () => fetchAdminUsers(offset, limit, searchQuery),
80+
enabled: searchQuery.length > 0,
5281
staleTime: 30 * 1000,
5382
placeholderData: keepPreviousData,
5483
})

0 commit comments

Comments
 (0)