feat(auth): add loading state for user and role management
This commit is contained in:
@@ -9,7 +9,7 @@ import useFormatMessage from 'hooks/useFormatMessage';
|
||||
|
||||
type DeleteRoleButtonProps = {
|
||||
roleId: string;
|
||||
}
|
||||
};
|
||||
|
||||
export default function DeleteRoleButton(props: DeleteRoleButtonProps) {
|
||||
const { roleId } = props;
|
||||
|
@@ -9,7 +9,7 @@ import useFormatMessage from 'hooks/useFormatMessage';
|
||||
|
||||
type DeleteUserButtonProps = {
|
||||
userId: string;
|
||||
}
|
||||
};
|
||||
|
||||
export default function DeleteUserButton(props: DeleteUserButtonProps) {
|
||||
const { userId } = props;
|
||||
|
47
packages/web/src/components/ListLoader/index.tsx
Normal file
47
packages/web/src/components/ListLoader/index.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import {
|
||||
IconButton,
|
||||
Skeleton,
|
||||
Stack,
|
||||
TableCell,
|
||||
TableRow,
|
||||
} from '@mui/material';
|
||||
import EditIcon from '@mui/icons-material/Edit';
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
|
||||
type ListLoaderProps = {
|
||||
rowsNumber: number;
|
||||
cellNumber: number;
|
||||
};
|
||||
|
||||
const ListLoader = ({ rowsNumber, cellNumber }: ListLoaderProps) => {
|
||||
return (
|
||||
<>
|
||||
{[...Array(rowsNumber)].map((row, index) => (
|
||||
<TableRow
|
||||
key={index}
|
||||
sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
|
||||
>
|
||||
{[...Array(cellNumber)].map((cell, index) => (
|
||||
<TableCell key={index} scope="row">
|
||||
<Skeleton />
|
||||
</TableCell>
|
||||
))}
|
||||
|
||||
<TableCell>
|
||||
<Stack direction="row" gap={1} justifyContent="right">
|
||||
<IconButton size="small">
|
||||
<EditIcon />
|
||||
</IconButton>
|
||||
|
||||
<IconButton size="small">
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</Stack>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ListLoader;
|
@@ -13,15 +13,15 @@ import Typography from '@mui/material/Typography';
|
||||
import EditIcon from '@mui/icons-material/Edit';
|
||||
|
||||
import DeleteRoleButton from 'components/DeleteRoleButton/index.ee';
|
||||
import ListLoader from 'components/ListLoader';
|
||||
import useFormatMessage from 'hooks/useFormatMessage';
|
||||
import useRoles from 'hooks/useRoles.ee';
|
||||
import * as URLS from 'config/urls';
|
||||
|
||||
// TODO: introduce interaction feedback upon deletion (successful + failure)
|
||||
// TODO: introduce loading bar
|
||||
export default function RoleList(): React.ReactElement {
|
||||
const formatMessage = useFormatMessage();
|
||||
const { roles } = useRoles();
|
||||
const { roles, loading } = useRoles();
|
||||
|
||||
return (
|
||||
<TableContainer component={Paper}>
|
||||
@@ -50,23 +50,20 @@ export default function RoleList(): React.ReactElement {
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{roles.map((role) => (
|
||||
{loading ? (
|
||||
<ListLoader rowsNumber={3} cellNumber={2} />
|
||||
) : (
|
||||
roles.map((role) => (
|
||||
<TableRow
|
||||
key={role.id}
|
||||
sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
|
||||
>
|
||||
<TableCell scope="row">
|
||||
<Typography
|
||||
variant="subtitle2"
|
||||
>
|
||||
{role.name}
|
||||
</Typography>
|
||||
<Typography variant="subtitle2">{role.name}</Typography>
|
||||
</TableCell>
|
||||
|
||||
<TableCell scope="row">
|
||||
<Typography
|
||||
variant="subtitle2"
|
||||
>
|
||||
<Typography variant="subtitle2">
|
||||
{role.description}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
@@ -85,7 +82,8 @@ export default function RoleList(): React.ReactElement {
|
||||
</Stack>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
@@ -13,13 +13,13 @@ import Typography from '@mui/material/Typography';
|
||||
import EditIcon from '@mui/icons-material/Edit';
|
||||
|
||||
import DeleteUserButton from 'components/DeleteUserButton/index.ee';
|
||||
import ListLoader from 'components/ListLoader';
|
||||
import useUsers from 'hooks/useUsers';
|
||||
import useFormatMessage from 'hooks/useFormatMessage';
|
||||
import * as URLS from 'config/urls';
|
||||
|
||||
// TODO: introduce translation entries
|
||||
// TODO: introduce interaction feedback upon deletion (successful + failure)
|
||||
// TODO: introduce loading bar
|
||||
export default function UserList(): React.ReactElement {
|
||||
const formatMessage = useFormatMessage();
|
||||
const { users, loading } = useUsers();
|
||||
@@ -51,25 +51,20 @@ export default function UserList(): React.ReactElement {
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{users.map((user) => (
|
||||
{loading ? (
|
||||
<ListLoader rowsNumber={3} cellNumber={2} />
|
||||
) : (
|
||||
users.map((user) => (
|
||||
<TableRow
|
||||
key={user.id}
|
||||
sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
|
||||
>
|
||||
<TableCell scope="row">
|
||||
<Typography
|
||||
variant="subtitle2"
|
||||
>
|
||||
{user.fullName}
|
||||
</Typography>
|
||||
<Typography variant="subtitle2">{user.fullName}</Typography>
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<Typography
|
||||
variant="subtitle2"
|
||||
>
|
||||
{user.email}
|
||||
</Typography>
|
||||
<Typography variant="subtitle2">{user.email}</Typography>
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
@@ -86,7 +81,8 @@ export default function UserList(): React.ReactElement {
|
||||
</Stack>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
@@ -13,13 +13,13 @@ import PageTitle from 'components/PageTitle';
|
||||
import Form from 'components/Form';
|
||||
import TextField from 'components/TextField';
|
||||
import useFormatMessage from 'hooks/useFormatMessage';
|
||||
import { Skeleton } from '@mui/material';
|
||||
|
||||
type EditRoleParams = {
|
||||
roleId: string;
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: introduce interaction feedback upon deletion (successful + failure)
|
||||
// TODO: introduce loading bar
|
||||
export default function EditRole(): React.ReactElement {
|
||||
const formatMessage = useFormatMessage();
|
||||
const [updateRole, { loading }] = useMutation(UPDATE_ROLE);
|
||||
@@ -33,13 +33,11 @@ export default function EditRole(): React.ReactElement {
|
||||
id: roleId,
|
||||
name: roleData.name,
|
||||
description: roleData.description,
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
if (roleLoading) return <React.Fragment />;
|
||||
|
||||
return (
|
||||
<Container sx={{ py: 3, display: 'flex', justifyContent: 'center' }}>
|
||||
<Grid container item xs={12} sm={9} md={8} lg={6}>
|
||||
@@ -48,6 +46,13 @@ export default function EditRole(): React.ReactElement {
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} justifyContent="flex-end" sx={{ pt: 5 }}>
|
||||
{roleLoading ? (
|
||||
<Stack direction="column" gap={2}>
|
||||
<Skeleton variant="rounded" height={55} />
|
||||
<Skeleton variant="rounded" height={55} />
|
||||
<Skeleton variant="rounded" height={45} />
|
||||
</Stack>
|
||||
) : (
|
||||
<Form defaultValues={role} onSubmit={handleRoleUpdate}>
|
||||
<Stack direction="column" gap={2}>
|
||||
<TextField
|
||||
@@ -75,6 +80,7 @@ export default function EditRole(): React.ReactElement {
|
||||
</LoadingButton>
|
||||
</Stack>
|
||||
</Form>
|
||||
)}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Container>
|
||||
|
@@ -16,17 +16,17 @@ import Form from 'components/Form';
|
||||
import ControlledAutocomplete from 'components/ControlledAutocomplete';
|
||||
import TextField from 'components/TextField';
|
||||
import useFormatMessage from 'hooks/useFormatMessage';
|
||||
import { Skeleton } from '@mui/material';
|
||||
|
||||
type EditUserParams = {
|
||||
userId: string;
|
||||
}
|
||||
};
|
||||
|
||||
function generateRoleOptions(roles: IRole[]) {
|
||||
return roles?.map(({ name: label, id: value }) => ({ label, value }));
|
||||
}
|
||||
|
||||
// TODO: introduce interaction feedback upon deletion (successful + failure)
|
||||
// TODO: introduce loading bar
|
||||
export default function EditUser(): React.ReactElement {
|
||||
const formatMessage = useFormatMessage();
|
||||
const [updateUser, { loading }] = useMutation(UPDATE_USER);
|
||||
@@ -42,15 +42,13 @@ export default function EditUser(): React.ReactElement {
|
||||
fullName: userDataToUpdate.fullName,
|
||||
email: userDataToUpdate.email,
|
||||
role: {
|
||||
id: userDataToUpdate.role?.id
|
||||
}
|
||||
}
|
||||
}
|
||||
id: userDataToUpdate.role?.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
if (userLoading) return <React.Fragment />;
|
||||
|
||||
return (
|
||||
<Container sx={{ py: 3, display: 'flex', justifyContent: 'center' }}>
|
||||
<Grid container item xs={12} sm={9} md={8} lg={6}>
|
||||
@@ -59,6 +57,14 @@ export default function EditUser(): React.ReactElement {
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} justifyContent="flex-end" sx={{ pt: 5 }}>
|
||||
{userLoading ? (
|
||||
<Stack direction="column" gap={2}>
|
||||
<Skeleton variant="rounded" height={55} />
|
||||
<Skeleton variant="rounded" height={55} />
|
||||
<Skeleton variant="rounded" height={55} />
|
||||
<Skeleton variant="rounded" height={45} />
|
||||
</Stack>
|
||||
) : (
|
||||
<Form defaultValues={user} onSubmit={handleUserUpdate}>
|
||||
<Stack direction="column" gap={2}>
|
||||
<TextField
|
||||
@@ -81,7 +87,12 @@ export default function EditUser(): React.ReactElement {
|
||||
disablePortal
|
||||
disableClearable={true}
|
||||
options={generateRoleOptions(roles)}
|
||||
renderInput={(params) => <MuiTextField {...params} label={formatMessage('userForm.role')} />}
|
||||
renderInput={(params) => (
|
||||
<MuiTextField
|
||||
{...params}
|
||||
label={formatMessage('userForm.role')}
|
||||
/>
|
||||
)}
|
||||
loading={rolesLoading}
|
||||
/>
|
||||
|
||||
@@ -96,6 +107,7 @@ export default function EditUser(): React.ReactElement {
|
||||
</LoadingButton>
|
||||
</Stack>
|
||||
</Form>
|
||||
)}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Container>
|
||||
|
Reference in New Issue
Block a user