import { useState } from 'react'; import { Card, Button, Table, Tag, Popconfirm, Modal, Input, Space, message } from 'antd'; import type { ColumnsType } from 'antd/es/table'; import { PlusOutlined, EditOutlined, DeleteOutlined, KeyOutlined, ApartmentOutlined } from '@ant-design/icons'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { listUsers, createUser, updateUser, deleteUser, resetUserPassword, type UserListItem, type CreateUserPayload, type UpdateUserPayload, } from '../api/users'; import { UserFormDrawer } from '../components/users/UserFormDrawer'; import { CustomerAccessModal } from '../components/users/CustomerAccessModal'; import { useAppInfo } from '../hooks/useAppInfo'; type DrawerMode = { kind: 'create' } | { kind: 'edit'; user: UserListItem }; export function UsersPage() { const qc = useQueryClient(); const { isAdmin: isAdminMode } = useAppInfo(); const [drawerMode, setDrawerMode] = useState(null); const [drawerError, setDrawerError] = useState(null); const [resetTarget, setResetTarget] = useState(null); const [resetValue, setResetValue] = useState(''); const [accessTarget, setAccessTarget] = useState(null); const { data: users = [], isLoading } = useQuery({ queryKey: ['admin', 'users'], queryFn: listUsers, }); const invalidate = () => qc.invalidateQueries({ queryKey: ['admin', 'users'] }); const createMut = useMutation({ mutationFn: (payload: CreateUserPayload) => createUser(payload), onSuccess: () => { message.success('User created'); setDrawerMode(null); setDrawerError(null); invalidate(); }, onError: (err: unknown) => setDrawerError(errorMessage(err)), }); const updateMut = useMutation({ mutationFn: ({ id, payload }: { id: string; payload: UpdateUserPayload }) => updateUser(id, payload), onSuccess: () => { message.success('User updated'); setDrawerMode(null); setDrawerError(null); invalidate(); }, onError: (err: unknown) => setDrawerError(errorMessage(err)), }); const deleteMut = useMutation({ mutationFn: (id: string) => deleteUser(id), onSuccess: () => { message.success('User deleted'); invalidate(); }, onError: (err: unknown) => message.error(errorMessage(err)), }); const resetMut = useMutation({ mutationFn: ({ id, newPassword }: { id: string; newPassword: string }) => resetUserPassword(id, newPassword), onSuccess: () => { message.success('Password reset'); setResetTarget(null); setResetValue(''); }, onError: (err: unknown) => message.error(errorMessage(err)), }); const handleSubmit = async (values: CreateUserPayload | UpdateUserPayload) => { setDrawerError(null); if (drawerMode?.kind === 'edit') { await updateMut.mutateAsync({ id: drawerMode.user.id, payload: values as UpdateUserPayload }); } else { await createMut.mutateAsync(values as CreateUserPayload); } }; const columns: ColumnsType = [ { title: 'Email', dataIndex: 'email', key: 'email' }, { title: 'Name', dataIndex: 'displayName', key: 'displayName' }, { title: 'Roles', dataIndex: 'roles', key: 'roles', render: (roles: string[]) => roles.length === 0 ? User : roles.map((r) => {r}), }, { title: 'Active', dataIndex: 'isActive', key: 'isActive', render: (active: boolean) => active ? Active : Disabled, }, { title: 'Created', dataIndex: 'createdAt', key: 'createdAt', render: (v: string) => new Date(v).toLocaleString(), }, { title: 'Actions', key: 'actions', render: (_, user) => ( {isAdminMode && user.roles.includes('RestrictedAdmin') && ( )} deleteMut.mutate(user.id)} > ), }, ]; return ( } onClick={() => { setDrawerError(null); setDrawerMode({ kind: 'create' }); }} > New user } > rowKey="id" columns={columns} dataSource={users} loading={isLoading} pagination={{ pageSize: 20 }} /> { setDrawerMode(null); setDrawerError(null); }} onSubmit={handleSubmit} /> { setResetTarget(null); setResetValue(''); }} onOk={() => resetTarget && resetMut.mutate({ id: resetTarget.id, newPassword: resetValue })} okText="Reset" okButtonProps={{ disabled: resetValue.length < 8 }} > setResetValue(e.target.value)} autoComplete="new-password" /> setAccessTarget(null)} /> ); } function errorMessage(err: unknown): string { if (typeof err === 'object' && err !== null && 'response' in err) { const data = (err as { response?: { data?: { error?: string; errors?: string[] } } }).response?.data; if (data?.error) return data.error; if (data?.errors?.length) return data.errors.join('; '); } return 'Request failed.'; }