feat(auth): add loading state for user and role management (#1188)
This commit is contained in:
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;
|
||||||
|
columnsNumber: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ListLoader = ({ rowsNumber, columnsNumber }: ListLoaderProps) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{[...Array(rowsNumber)].map((row, index) => (
|
||||||
|
<TableRow
|
||||||
|
key={index}
|
||||||
|
sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
|
||||||
|
>
|
||||||
|
{[...Array(columnsNumber)].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;
|
@@ -0,0 +1,61 @@
|
|||||||
|
import {
|
||||||
|
IconButton,
|
||||||
|
Skeleton,
|
||||||
|
Stack,
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableContainer,
|
||||||
|
TableHead,
|
||||||
|
TableRow,
|
||||||
|
Typography,
|
||||||
|
} from '@mui/material';
|
||||||
|
import SettingsIcon from '@mui/icons-material/Settings';
|
||||||
|
import ControlledCheckbox from 'components/ControlledCheckbox';
|
||||||
|
|
||||||
|
const PermissionCatalogFieldLoader = () => {
|
||||||
|
return (
|
||||||
|
<TableContainer>
|
||||||
|
<Table>
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell component="th" />
|
||||||
|
{[...Array(5)].map((row, index) => (
|
||||||
|
<TableCell key={index} component="th">
|
||||||
|
<Skeleton />
|
||||||
|
</TableCell>
|
||||||
|
))}
|
||||||
|
<TableCell component="th" />
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{[...Array(3)].map((row, index) => (
|
||||||
|
<TableRow key={index} sx={{ '&:last-child td': { border: 0 } }}>
|
||||||
|
<TableCell scope="row">
|
||||||
|
<Skeleton width={40} />
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
{[...Array(5)].map((action, index) => (
|
||||||
|
<TableCell key={index} align="center">
|
||||||
|
<Typography variant="subtitle2">
|
||||||
|
<ControlledCheckbox name="value" />
|
||||||
|
</Typography>
|
||||||
|
</TableCell>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<TableCell>
|
||||||
|
<Stack direction="row" gap={1} justifyContent="right">
|
||||||
|
<IconButton color="info" size="small">
|
||||||
|
<SettingsIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Stack>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PermissionCatalogFieldLoader;
|
@@ -14,17 +14,21 @@ import * as React from 'react';
|
|||||||
import ControlledCheckbox from 'components/ControlledCheckbox';
|
import ControlledCheckbox from 'components/ControlledCheckbox';
|
||||||
import usePermissionCatalog from 'hooks/usePermissionCatalog.ee';
|
import usePermissionCatalog from 'hooks/usePermissionCatalog.ee';
|
||||||
import PermissionSettings from './PermissionSettings.ee';
|
import PermissionSettings from './PermissionSettings.ee';
|
||||||
|
import PermissionCatalogFieldLoader from './PermissionCatalogFieldLoader';
|
||||||
|
|
||||||
type PermissionCatalogFieldProps = {
|
type PermissionCatalogFieldProps = {
|
||||||
name?: string;
|
name?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const PermissionCatalogField = ({ name = 'permissions', disabled = false }: PermissionCatalogFieldProps) => {
|
const PermissionCatalogField = ({
|
||||||
const permissionCatalog = usePermissionCatalog();
|
name = 'permissions',
|
||||||
|
disabled = false,
|
||||||
|
}: PermissionCatalogFieldProps) => {
|
||||||
|
const { permissionCatalog, loading } = usePermissionCatalog();
|
||||||
const [dialogName, setDialogName] = React.useState<string>();
|
const [dialogName, setDialogName] = React.useState<string>();
|
||||||
|
|
||||||
if (!permissionCatalog) return (<React.Fragment />);
|
if (loading) return <PermissionCatalogFieldLoader />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableContainer component={Paper}>
|
<TableContainer component={Paper}>
|
||||||
@@ -33,14 +37,14 @@ const PermissionCatalogField = ({ name = 'permissions', disabled = false }: Perm
|
|||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell component="th" />
|
<TableCell component="th" />
|
||||||
|
|
||||||
{permissionCatalog.actions.map(action => (
|
{permissionCatalog.actions.map((action) => (
|
||||||
<TableCell component="th" key={action.key}>
|
<TableCell component="th" key={action.key}>
|
||||||
<Typography
|
<Typography
|
||||||
variant="subtitle1"
|
variant="subtitle1"
|
||||||
align="center"
|
align="center"
|
||||||
sx={{
|
sx={{
|
||||||
color: 'text.secondary',
|
color: 'text.secondary',
|
||||||
fontWeight: 700
|
fontWeight: 700,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{action.label}
|
{action.label}
|
||||||
@@ -58,21 +62,12 @@ const PermissionCatalogField = ({ name = 'permissions', disabled = false }: Perm
|
|||||||
sx={{ '&:last-child td': { border: 0 } }}
|
sx={{ '&:last-child td': { border: 0 } }}
|
||||||
>
|
>
|
||||||
<TableCell scope="row">
|
<TableCell scope="row">
|
||||||
<Typography
|
<Typography variant="subtitle2">{subject.label}</Typography>
|
||||||
variant="subtitle2"
|
|
||||||
>
|
|
||||||
{subject.label}
|
|
||||||
</Typography>
|
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
||||||
{permissionCatalog.actions.map((action) => (
|
{permissionCatalog.actions.map((action) => (
|
||||||
<TableCell
|
<TableCell key={`${subject.key}.${action.key}`} align="center">
|
||||||
key={`${subject.key}.${action.key}`}
|
<Typography variant="subtitle2">
|
||||||
align="center"
|
|
||||||
>
|
|
||||||
<Typography
|
|
||||||
variant="subtitle2"
|
|
||||||
>
|
|
||||||
{action.subjects.includes(subject.key) && (
|
{action.subjects.includes(subject.key) && (
|
||||||
<ControlledCheckbox
|
<ControlledCheckbox
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
@@ -86,11 +81,7 @@ const PermissionCatalogField = ({ name = 'permissions', disabled = false }: Perm
|
|||||||
))}
|
))}
|
||||||
|
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Stack
|
<Stack direction="row" gap={1} justifyContent="right">
|
||||||
direction="row"
|
|
||||||
gap={1}
|
|
||||||
justifyContent="right"
|
|
||||||
>
|
|
||||||
<IconButton
|
<IconButton
|
||||||
color="info"
|
color="info"
|
||||||
size="small"
|
size="small"
|
||||||
@@ -116,7 +107,7 @@ const PermissionCatalogField = ({ name = 'permissions', disabled = false }: Perm
|
|||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
)
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default PermissionCatalogField;
|
export default PermissionCatalogField;
|
||||||
|
@@ -13,14 +13,14 @@ 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 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}>
|
||||||
@@ -49,7 +49,9 @@ export default function RoleList(): React.ReactElement {
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{roles.map((role) => (
|
{loading && <ListLoader rowsNumber={3} columnsNumber={2} />}
|
||||||
|
{!loading &&
|
||||||
|
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 } }}
|
||||||
@@ -59,7 +61,9 @@ export default function RoleList(): React.ReactElement {
|
|||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
||||||
<TableCell scope="row">
|
<TableCell scope="row">
|
||||||
<Typography variant="subtitle2">{role.description}</Typography>
|
<Typography variant="subtitle2">
|
||||||
|
{role.description}
|
||||||
|
</Typography>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
||||||
<TableCell>
|
<TableCell>
|
||||||
@@ -72,7 +76,10 @@ export default function RoleList(): React.ReactElement {
|
|||||||
<EditIcon />
|
<EditIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|
||||||
<DeleteRoleButton disabled={role.isAdmin} roleId={role.id} />
|
<DeleteRoleButton
|
||||||
|
disabled={role.isAdmin}
|
||||||
|
roleId={role.id}
|
||||||
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
@@ -13,11 +13,11 @@ 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 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();
|
||||||
@@ -49,7 +49,9 @@ export default function UserList(): React.ReactElement {
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{users.map((user) => (
|
{loading && <ListLoader rowsNumber={3} columnsNumber={2} />}
|
||||||
|
{!loading &&
|
||||||
|
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 } }}
|
||||||
|
@@ -3,24 +3,33 @@ import { IRole, IPermission } from '@automatisch/types';
|
|||||||
type ComputeAction = {
|
type ComputeAction = {
|
||||||
conditions: Record<string, boolean>;
|
conditions: Record<string, boolean>;
|
||||||
value: boolean;
|
value: boolean;
|
||||||
}
|
};
|
||||||
type ComputedActions = Record<string, ComputeAction>;
|
type ComputedActions = Record<string, ComputeAction>;
|
||||||
type ComputedPermissions = Record<string, ComputedActions>;
|
type ComputedPermissions = Record<string, ComputedActions>;
|
||||||
export type RoleWithComputedPermissions = IRole & { computedPermissions: ComputedPermissions };
|
export type RoleWithComputedPermissions = IRole & {
|
||||||
|
computedPermissions: ComputedPermissions;
|
||||||
|
};
|
||||||
|
|
||||||
export function getRoleWithComputedPermissions(role: IRole): RoleWithComputedPermissions {
|
export function getRoleWithComputedPermissions(
|
||||||
const computedPermissions = role.permissions.reduce((computedPermissions, permission) => ({
|
role?: IRole
|
||||||
|
): Partial<RoleWithComputedPermissions> {
|
||||||
|
if (!role) return {};
|
||||||
|
|
||||||
|
const computedPermissions = role.permissions.reduce(
|
||||||
|
(computedPermissions, permission) => ({
|
||||||
...computedPermissions,
|
...computedPermissions,
|
||||||
[permission.subject]: {
|
[permission.subject]: {
|
||||||
...(computedPermissions[permission.subject] || {}),
|
...(computedPermissions[permission.subject] || {}),
|
||||||
[permission.action]: {
|
[permission.action]: {
|
||||||
conditions: Object.fromEntries(permission
|
conditions: Object.fromEntries(
|
||||||
.conditions
|
permission.conditions.map((condition) => [condition, true])
|
||||||
.map(condition => [condition, true])),
|
),
|
||||||
value: true,
|
value: true,
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
}), {} as ComputedPermissions);
|
}),
|
||||||
|
{} as ComputedPermissions
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...role,
|
...role,
|
||||||
@@ -31,29 +40,27 @@ export function getRoleWithComputedPermissions(role: IRole): RoleWithComputedPer
|
|||||||
export function getPermissions(computedPermissions?: ComputedPermissions) {
|
export function getPermissions(computedPermissions?: ComputedPermissions) {
|
||||||
if (!computedPermissions) return [];
|
if (!computedPermissions) return [];
|
||||||
|
|
||||||
return Object
|
return Object.entries(computedPermissions).reduce(
|
||||||
.entries(computedPermissions)
|
(permissions, computedPermissionEntry) => {
|
||||||
.reduce((permissions, computedPermissionEntry) => {
|
|
||||||
const [subject, actionsWithConditions] = computedPermissionEntry;
|
const [subject, actionsWithConditions] = computedPermissionEntry;
|
||||||
|
|
||||||
for (const action in actionsWithConditions) {
|
for (const action in actionsWithConditions) {
|
||||||
const {
|
const { value: permitted, conditions = {} } =
|
||||||
value: permitted,
|
actionsWithConditions[action];
|
||||||
conditions = {},
|
|
||||||
} = actionsWithConditions[action];
|
|
||||||
|
|
||||||
if (permitted) {
|
if (permitted) {
|
||||||
permissions.push({
|
permissions.push({
|
||||||
action,
|
action,
|
||||||
subject,
|
subject,
|
||||||
conditions: Object
|
conditions: Object.entries(conditions)
|
||||||
.entries(conditions)
|
|
||||||
.filter(([, enabled]) => enabled)
|
.filter(([, enabled]) => enabled)
|
||||||
.map(([condition]) => condition),
|
.map(([condition]) => condition),
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return permissions;
|
return permissions;
|
||||||
}, [] as Partial<IPermission>[]);
|
},
|
||||||
|
[] as Partial<IPermission>[]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@@ -3,8 +3,13 @@ import { IPermissionCatalog } from '@automatisch/types';
|
|||||||
|
|
||||||
import { GET_PERMISSION_CATALOG } from 'graphql/queries/get-permission-catalog.ee';
|
import { GET_PERMISSION_CATALOG } from 'graphql/queries/get-permission-catalog.ee';
|
||||||
|
|
||||||
export default function usePermissionCatalog(): IPermissionCatalog {
|
type UsePermissionCatalogReturn = {
|
||||||
const { data } = useQuery(GET_PERMISSION_CATALOG);
|
permissionCatalog: IPermissionCatalog;
|
||||||
|
loading: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
return data?.getPermissionCatalog;
|
export default function usePermissionCatalog(): UsePermissionCatalogReturn {
|
||||||
|
const { data, loading } = useQuery(GET_PERMISSION_CATALOG);
|
||||||
|
|
||||||
|
return { permissionCatalog: data?.getPermissionCatalog, loading };
|
||||||
}
|
}
|
||||||
|
@@ -6,7 +6,7 @@ import { GET_ROLE } from 'graphql/queries/get-role.ee';
|
|||||||
|
|
||||||
type QueryResponse = {
|
type QueryResponse = {
|
||||||
getRole: IRole;
|
getRole: IRole;
|
||||||
}
|
};
|
||||||
|
|
||||||
export default function useRole(roleId?: string) {
|
export default function useRole(roleId?: string) {
|
||||||
const [getRole, { data, loading }] = useLazyQuery<QueryResponse>(GET_ROLE);
|
const [getRole, { data, loading }] = useLazyQuery<QueryResponse>(GET_ROLE);
|
||||||
@@ -15,14 +15,14 @@ export default function useRole(roleId?: string) {
|
|||||||
if (roleId) {
|
if (roleId) {
|
||||||
getRole({
|
getRole({
|
||||||
variables: {
|
variables: {
|
||||||
id: roleId
|
id: roleId,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [roleId]);
|
}, [roleId]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
role: data?.getRole,
|
role: data?.getRole,
|
||||||
loading
|
loading,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -3,6 +3,7 @@ import LoadingButton from '@mui/lab/LoadingButton';
|
|||||||
import Container from '@mui/material/Container';
|
import Container from '@mui/material/Container';
|
||||||
import Grid from '@mui/material/Grid';
|
import Grid from '@mui/material/Grid';
|
||||||
import Stack from '@mui/material/Stack';
|
import Stack from '@mui/material/Stack';
|
||||||
|
import Skeleton from '@mui/material/Skeleton';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { useNavigate, useParams } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
import { useSnackbar } from 'notistack';
|
import { useSnackbar } from 'notistack';
|
||||||
@@ -25,7 +26,6 @@ type EditRoleParams = {
|
|||||||
roleId: string;
|
roleId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 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);
|
||||||
@@ -61,8 +61,6 @@ export default function EditRole(): React.ReactElement {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (roleLoading || !role) return <React.Fragment />;
|
|
||||||
|
|
||||||
const roleWithComputedPermissions = getRoleWithComputedPermissions(role);
|
const roleWithComputedPermissions = getRoleWithComputedPermissions(role);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -78,6 +76,14 @@ export default function EditRole(): React.ReactElement {
|
|||||||
onSubmit={handleRoleUpdate}
|
onSubmit={handleRoleUpdate}
|
||||||
>
|
>
|
||||||
<Stack direction="column" gap={2}>
|
<Stack direction="column" gap={2}>
|
||||||
|
{roleLoading && (
|
||||||
|
<>
|
||||||
|
<Skeleton variant="rounded" height={55} />
|
||||||
|
<Skeleton variant="rounded" height={55} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{!roleLoading && role && (
|
||||||
|
<>
|
||||||
<TextField
|
<TextField
|
||||||
disabled={role.isAdmin}
|
disabled={role.isAdmin}
|
||||||
required={true}
|
required={true}
|
||||||
@@ -92,10 +98,12 @@ export default function EditRole(): React.ReactElement {
|
|||||||
label={formatMessage('roleForm.description')}
|
label={formatMessage('roleForm.description')}
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
<PermissionCatalogField
|
<PermissionCatalogField
|
||||||
name="computedPermissions"
|
name="computedPermissions"
|
||||||
disabled={role.isAdmin}
|
disabled={role?.isAdmin}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<LoadingButton
|
<LoadingButton
|
||||||
@@ -104,7 +112,7 @@ export default function EditRole(): React.ReactElement {
|
|||||||
color="primary"
|
color="primary"
|
||||||
sx={{ boxShadow: 2 }}
|
sx={{ boxShadow: 2 }}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
disabled={role.isAdmin}
|
disabled={role?.isAdmin || roleLoading}
|
||||||
>
|
>
|
||||||
{formatMessage('editRole.submit')}
|
{formatMessage('editRole.submit')}
|
||||||
</LoadingButton>
|
</LoadingButton>
|
||||||
|
@@ -5,6 +5,7 @@ import Container from '@mui/material/Container';
|
|||||||
import Grid from '@mui/material/Grid';
|
import Grid from '@mui/material/Grid';
|
||||||
import Stack from '@mui/material/Stack';
|
import Stack from '@mui/material/Stack';
|
||||||
import MuiTextField from '@mui/material/TextField';
|
import MuiTextField from '@mui/material/TextField';
|
||||||
|
import Skeleton from '@mui/material/Skeleton';
|
||||||
import LoadingButton from '@mui/lab/LoadingButton';
|
import LoadingButton from '@mui/lab/LoadingButton';
|
||||||
import { IUser, IRole } from '@automatisch/types';
|
import { IUser, IRole } from '@automatisch/types';
|
||||||
import { useSnackbar } from 'notistack';
|
import { useSnackbar } from 'notistack';
|
||||||
@@ -28,7 +29,6 @@ function generateRoleOptions(roles: IRole[]) {
|
|||||||
return roles?.map(({ name: label, id: value }) => ({ label, value }));
|
return roles?.map(({ name: label, id: value }) => ({ label, value }));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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);
|
||||||
@@ -63,8 +63,6 @@ export default function EditUser(): React.ReactElement {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
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}>
|
||||||
@@ -73,6 +71,16 @@ 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>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!userLoading && (
|
||||||
<Form defaultValues={user} onSubmit={handleUserUpdate}>
|
<Form defaultValues={user} onSubmit={handleUserUpdate}>
|
||||||
<Stack direction="column" gap={2}>
|
<Stack direction="column" gap={2}>
|
||||||
<TextField
|
<TextField
|
||||||
@@ -117,6 +125,7 @@ export default function EditUser(): React.ReactElement {
|
|||||||
</LoadingButton>
|
</LoadingButton>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Form>
|
</Form>
|
||||||
|
)}
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Container>
|
</Container>
|
||||||
|
Reference in New Issue
Block a user