Spaces:
Sleeping
Sleeping
import { useState, useCallback } from 'react'; | |
import { Modal, Form, Button, Select, Tooltip } from 'antd'; | |
import debounce from 'lodash/debounce'; | |
import { userFilterUICall } from "@/components/networking"; | |
import { InfoCircleOutlined } from '@ant-design/icons'; | |
interface User { | |
user_id: string; | |
user_email: string; | |
role?: string; | |
} | |
interface UserOption { | |
label: string; | |
value: string; | |
user: User; | |
} | |
interface Role { | |
label: string; | |
value: string; | |
description: string; | |
} | |
interface FormValues { | |
user_email: string; | |
user_id: string; | |
role: string; | |
} | |
interface UserSearchModalProps { | |
isVisible: boolean; | |
onCancel: () => void; | |
onSubmit: (values: FormValues) => void; | |
accessToken: string | null; | |
title?: string; | |
roles?: Role[]; | |
defaultRole?: string; | |
} | |
const UserSearchModal: React.FC<UserSearchModalProps> = ({ | |
isVisible, | |
onCancel, | |
onSubmit, | |
accessToken, | |
title = "Add Team Member", | |
roles = [ | |
{ label: "admin", value: "admin", description: "Admin role. Can create team keys, add members, and manage settings." }, | |
{ label: "user", value: "user", description: "User role. Can view team info, but not manage it." } | |
], | |
defaultRole = "user" | |
}) => { | |
const [form] = Form.useForm<FormValues>(); | |
const [userOptions, setUserOptions] = useState<UserOption[]>([]); | |
const [loading, setLoading] = useState<boolean>(false); | |
const [selectedField, setSelectedField] = useState<'user_email' | 'user_id'>('user_email'); | |
const fetchUsers = async (searchText: string, fieldName: 'user_email' | 'user_id'): Promise<void> => { | |
if (!searchText) { | |
setUserOptions([]); | |
return; | |
} | |
setLoading(true); | |
try { | |
const params = new URLSearchParams(); | |
params.append(fieldName, searchText); | |
if (accessToken == null) { | |
return; | |
} | |
const response = await userFilterUICall(accessToken, params); | |
const data: User[] = response | |
const options: UserOption[] = data.map(user => ({ | |
label: fieldName === 'user_email' | |
? `${user.user_email}` | |
: `${user.user_id}`, | |
value: fieldName === 'user_email' ? user.user_email : user.user_id, | |
user | |
})); | |
setUserOptions(options); | |
} catch (error) { | |
console.error('Error fetching users:', error); | |
} finally { | |
setLoading(false); | |
} | |
}; | |
const debouncedSearch = useCallback( | |
debounce((text: string, fieldName: 'user_email' | 'user_id') => fetchUsers(text, fieldName), 300), | |
[] | |
); | |
const handleSearch = (value: string, fieldName: 'user_email' | 'user_id'): void => { | |
setSelectedField(fieldName); | |
debouncedSearch(value, fieldName); | |
}; | |
const handleSelect = (_value: string, option: UserOption): void => { | |
const selectedUser = option.user; | |
form.setFieldsValue({ | |
user_email: selectedUser.user_email, | |
user_id: selectedUser.user_id, | |
role: form.getFieldValue('role') // Preserve current role selection | |
}); | |
}; | |
const handleClose = (): void => { | |
form.resetFields(); | |
setUserOptions([]); | |
onCancel(); | |
}; | |
return ( | |
<Modal | |
title={title} | |
open={isVisible} | |
onCancel={handleClose} | |
footer={null} | |
width={800} | |
> | |
<Form<FormValues> | |
form={form} | |
onFinish={onSubmit} | |
labelCol={{ span: 8 }} | |
wrapperCol={{ span: 16 }} | |
labelAlign="left" | |
initialValues={{ | |
role: defaultRole, | |
}} | |
> | |
<Form.Item | |
label="Email" | |
name="user_email" | |
className="mb-4" | |
> | |
<Select | |
showSearch | |
className="w-full" | |
placeholder="Search by email" | |
filterOption={false} | |
onSearch={(value) => handleSearch(value, 'user_email')} | |
onSelect={(value, option) => handleSelect(value, option as UserOption)} | |
options={selectedField === 'user_email' ? userOptions : []} | |
loading={loading} | |
allowClear | |
/> | |
</Form.Item> | |
<div className="text-center mb-4">OR</div> | |
<Form.Item | |
label="User ID" | |
name="user_id" | |
className="mb-4" | |
> | |
<Select | |
showSearch | |
className="w-full" | |
placeholder="Search by user ID" | |
filterOption={false} | |
onSearch={(value) => handleSearch(value, 'user_id')} | |
onSelect={(value, option) => handleSelect(value, option as UserOption)} | |
options={selectedField === 'user_id' ? userOptions : []} | |
loading={loading} | |
allowClear | |
/> | |
</Form.Item> | |
<Form.Item | |
label="Member Role" | |
name="role" | |
className="mb-4" | |
> | |
<Select defaultValue={defaultRole}> | |
{roles.map(role => ( | |
<Select.Option key={role.value} value={role.value}> | |
<Tooltip title={role.description}> | |
<span className="font-medium">{role.label}</span> | |
<span className="ml-2 text-gray-500 text-sm">- {role.description}</span> | |
</Tooltip> | |
</Select.Option> | |
))} | |
</Select> | |
</Form.Item> | |
<div className="text-right mt-4"> | |
<Button type="default" htmlType="submit"> | |
Add Member | |
</Button> | |
</div> | |
</Form> | |
</Modal> | |
); | |
}; | |
export default UserSearchModal; |