feat(auth): add loading state for user and role management

This commit is contained in:
Rıdvan Akca
2023-07-31 17:56:31 +03:00
parent 9e64af4793
commit d3bc3a796b
7 changed files with 204 additions and 145 deletions

View File

@@ -9,7 +9,7 @@ import useFormatMessage from 'hooks/useFormatMessage';
type DeleteRoleButtonProps = { type DeleteRoleButtonProps = {
roleId: string; roleId: string;
} };
export default function DeleteRoleButton(props: DeleteRoleButtonProps) { export default function DeleteRoleButton(props: DeleteRoleButtonProps) {
const { roleId } = props; const { roleId } = props;

View File

@@ -9,7 +9,7 @@ import useFormatMessage from 'hooks/useFormatMessage';
type DeleteUserButtonProps = { type DeleteUserButtonProps = {
userId: string; userId: string;
} };
export default function DeleteUserButton(props: DeleteUserButtonProps) { export default function DeleteUserButton(props: DeleteUserButtonProps) {
const { userId } = props; const { userId } = props;

View 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;

View File

@@ -13,15 +13,15 @@ import Typography from '@mui/material/Typography';
import EditIcon from '@mui/icons-material/Edit'; import EditIcon from '@mui/icons-material/Edit';
import DeleteRoleButton from 'components/DeleteRoleButton/index.ee'; import DeleteRoleButton from 'components/DeleteRoleButton/index.ee';
import ListLoader from 'components/ListLoader';
import useFormatMessage from 'hooks/useFormatMessage'; import useFormatMessage from 'hooks/useFormatMessage';
import useRoles from 'hooks/useRoles.ee'; import useRoles from 'hooks/useRoles.ee';
import * as URLS from 'config/urls'; import * as URLS from 'config/urls';
// TODO: introduce interaction feedback upon deletion (successful + failure) // TODO: introduce interaction feedback upon deletion (successful + failure)
// TODO: introduce loading bar
export default function RoleList(): React.ReactElement { export default function RoleList(): React.ReactElement {
const formatMessage = useFormatMessage(); const formatMessage = useFormatMessage();
const { roles } = useRoles(); const { roles, loading } = useRoles();
return ( return (
<TableContainer component={Paper}> <TableContainer component={Paper}>
@@ -50,23 +50,20 @@ export default function RoleList(): React.ReactElement {
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
{roles.map((role) => ( {loading ? (
<ListLoader rowsNumber={3} cellNumber={2} />
) : (
roles.map((role) => (
<TableRow <TableRow
key={role.id} key={role.id}
sx={{ '&:last-child td, &:last-child th': { border: 0 } }} sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
> >
<TableCell scope="row"> <TableCell scope="row">
<Typography <Typography variant="subtitle2">{role.name}</Typography>
variant="subtitle2"
>
{role.name}
</Typography>
</TableCell> </TableCell>
<TableCell scope="row"> <TableCell scope="row">
<Typography <Typography variant="subtitle2">
variant="subtitle2"
>
{role.description} {role.description}
</Typography> </Typography>
</TableCell> </TableCell>
@@ -85,7 +82,8 @@ export default function RoleList(): React.ReactElement {
</Stack> </Stack>
</TableCell> </TableCell>
</TableRow> </TableRow>
))} ))
)}
</TableBody> </TableBody>
</Table> </Table>
</TableContainer> </TableContainer>

View File

@@ -13,13 +13,13 @@ import Typography from '@mui/material/Typography';
import EditIcon from '@mui/icons-material/Edit'; import EditIcon from '@mui/icons-material/Edit';
import DeleteUserButton from 'components/DeleteUserButton/index.ee'; import DeleteUserButton from 'components/DeleteUserButton/index.ee';
import ListLoader from 'components/ListLoader';
import useUsers from 'hooks/useUsers'; import useUsers from 'hooks/useUsers';
import useFormatMessage from 'hooks/useFormatMessage'; import useFormatMessage from 'hooks/useFormatMessage';
import * as URLS from 'config/urls'; import * as URLS from 'config/urls';
// TODO: introduce translation entries // TODO: introduce translation entries
// TODO: introduce interaction feedback upon deletion (successful + failure) // TODO: introduce interaction feedback upon deletion (successful + failure)
// TODO: introduce loading bar
export default function UserList(): React.ReactElement { export default function UserList(): React.ReactElement {
const formatMessage = useFormatMessage(); const formatMessage = useFormatMessage();
const { users, loading } = useUsers(); const { users, loading } = useUsers();
@@ -51,25 +51,20 @@ export default function UserList(): React.ReactElement {
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
{users.map((user) => ( {loading ? (
<ListLoader rowsNumber={3} cellNumber={2} />
) : (
users.map((user) => (
<TableRow <TableRow
key={user.id} key={user.id}
sx={{ '&:last-child td, &:last-child th': { border: 0 } }} sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
> >
<TableCell scope="row"> <TableCell scope="row">
<Typography <Typography variant="subtitle2">{user.fullName}</Typography>
variant="subtitle2"
>
{user.fullName}
</Typography>
</TableCell> </TableCell>
<TableCell> <TableCell>
<Typography <Typography variant="subtitle2">{user.email}</Typography>
variant="subtitle2"
>
{user.email}
</Typography>
</TableCell> </TableCell>
<TableCell> <TableCell>
@@ -86,7 +81,8 @@ export default function UserList(): React.ReactElement {
</Stack> </Stack>
</TableCell> </TableCell>
</TableRow> </TableRow>
))} ))
)}
</TableBody> </TableBody>
</Table> </Table>
</TableContainer> </TableContainer>

View File

@@ -13,13 +13,13 @@ import PageTitle from 'components/PageTitle';
import Form from 'components/Form'; import Form from 'components/Form';
import TextField from 'components/TextField'; import TextField from 'components/TextField';
import useFormatMessage from 'hooks/useFormatMessage'; import useFormatMessage from 'hooks/useFormatMessage';
import { Skeleton } from '@mui/material';
type EditRoleParams = { type EditRoleParams = {
roleId: string; roleId: string;
} };
// TODO: introduce interaction feedback upon deletion (successful + failure) // TODO: introduce interaction feedback upon deletion (successful + failure)
// TODO: introduce loading bar
export default function EditRole(): React.ReactElement { export default function EditRole(): React.ReactElement {
const formatMessage = useFormatMessage(); const formatMessage = useFormatMessage();
const [updateRole, { loading }] = useMutation(UPDATE_ROLE); const [updateRole, { loading }] = useMutation(UPDATE_ROLE);
@@ -33,13 +33,11 @@ export default function EditRole(): React.ReactElement {
id: roleId, id: roleId,
name: roleData.name, name: roleData.name,
description: roleData.description, description: roleData.description,
} },
} },
}); });
}; };
if (roleLoading) return <React.Fragment />;
return ( return (
<Container sx={{ py: 3, display: 'flex', justifyContent: 'center' }}> <Container sx={{ py: 3, display: 'flex', justifyContent: 'center' }}>
<Grid container item xs={12} sm={9} md={8} lg={6}> <Grid container item xs={12} sm={9} md={8} lg={6}>
@@ -48,6 +46,13 @@ export default function EditRole(): React.ReactElement {
</Grid> </Grid>
<Grid item xs={12} justifyContent="flex-end" sx={{ pt: 5 }}> <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}> <Form defaultValues={role} onSubmit={handleRoleUpdate}>
<Stack direction="column" gap={2}> <Stack direction="column" gap={2}>
<TextField <TextField
@@ -75,6 +80,7 @@ export default function EditRole(): React.ReactElement {
</LoadingButton> </LoadingButton>
</Stack> </Stack>
</Form> </Form>
)}
</Grid> </Grid>
</Grid> </Grid>
</Container> </Container>

View File

@@ -16,17 +16,17 @@ import Form from 'components/Form';
import ControlledAutocomplete from 'components/ControlledAutocomplete'; import ControlledAutocomplete from 'components/ControlledAutocomplete';
import TextField from 'components/TextField'; import TextField from 'components/TextField';
import useFormatMessage from 'hooks/useFormatMessage'; import useFormatMessage from 'hooks/useFormatMessage';
import { Skeleton } from '@mui/material';
type EditUserParams = { type EditUserParams = {
userId: string; userId: string;
} };
function generateRoleOptions(roles: IRole[]) { function generateRoleOptions(roles: IRole[]) {
return roles?.map(({ name: label, id: value }) => ({ label, value })); return roles?.map(({ name: label, id: value }) => ({ label, value }));
} }
// TODO: introduce interaction feedback upon deletion (successful + failure) // TODO: introduce interaction feedback upon deletion (successful + failure)
// TODO: introduce loading bar
export default function EditUser(): React.ReactElement { export default function EditUser(): React.ReactElement {
const formatMessage = useFormatMessage(); const formatMessage = useFormatMessage();
const [updateUser, { loading }] = useMutation(UPDATE_USER); const [updateUser, { loading }] = useMutation(UPDATE_USER);
@@ -42,15 +42,13 @@ export default function EditUser(): React.ReactElement {
fullName: userDataToUpdate.fullName, fullName: userDataToUpdate.fullName,
email: userDataToUpdate.email, email: userDataToUpdate.email,
role: { role: {
id: userDataToUpdate.role?.id id: userDataToUpdate.role?.id,
} },
} },
} },
}); });
}; };
if (userLoading) return <React.Fragment />;
return ( return (
<Container sx={{ py: 3, display: 'flex', justifyContent: 'center' }}> <Container sx={{ py: 3, display: 'flex', justifyContent: 'center' }}>
<Grid container item xs={12} sm={9} md={8} lg={6}> <Grid container item xs={12} sm={9} md={8} lg={6}>
@@ -59,6 +57,14 @@ export default function EditUser(): React.ReactElement {
</Grid> </Grid>
<Grid item xs={12} justifyContent="flex-end" sx={{ pt: 5 }}> <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}> <Form defaultValues={user} onSubmit={handleUserUpdate}>
<Stack direction="column" gap={2}> <Stack direction="column" gap={2}>
<TextField <TextField
@@ -81,7 +87,12 @@ export default function EditUser(): React.ReactElement {
disablePortal disablePortal
disableClearable={true} disableClearable={true}
options={generateRoleOptions(roles)} options={generateRoleOptions(roles)}
renderInput={(params) => <MuiTextField {...params} label={formatMessage('userForm.role')} />} renderInput={(params) => (
<MuiTextField
{...params}
label={formatMessage('userForm.role')}
/>
)}
loading={rolesLoading} loading={rolesLoading}
/> />
@@ -96,6 +107,7 @@ export default function EditUser(): React.ReactElement {
</LoadingButton> </LoadingButton>
</Stack> </Stack>
</Form> </Form>
)}
</Grid> </Grid>
</Grid> </Grid>
</Container> </Container>