diff --git a/packages/web/src/components/ListLoader/index.tsx b/packages/web/src/components/ListLoader/index.tsx
new file mode 100644
index 00000000..43945185
--- /dev/null
+++ b/packages/web/src/components/ListLoader/index.tsx
@@ -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) => (
+
+ {[...Array(columnsNumber)].map((cell, index) => (
+
+
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ))}
+ >
+ );
+};
+
+export default ListLoader;
diff --git a/packages/web/src/components/PermissionCatalogField/PermissionCatalogFieldLoader/index.tsx b/packages/web/src/components/PermissionCatalogField/PermissionCatalogFieldLoader/index.tsx
new file mode 100644
index 00000000..79bce0b5
--- /dev/null
+++ b/packages/web/src/components/PermissionCatalogField/PermissionCatalogFieldLoader/index.tsx
@@ -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 (
+
+
+
+
+
+ {[...Array(5)].map((row, index) => (
+
+
+
+ ))}
+
+
+
+
+ {[...Array(3)].map((row, index) => (
+
+
+
+
+
+ {[...Array(5)].map((action, index) => (
+
+
+
+
+
+ ))}
+
+
+
+
+
+
+
+
+
+ ))}
+
+
+
+ );
+};
+
+export default PermissionCatalogFieldLoader;
diff --git a/packages/web/src/components/PermissionCatalogField/index.ee.tsx b/packages/web/src/components/PermissionCatalogField/index.ee.tsx
index b3ace5e1..6180104a 100644
--- a/packages/web/src/components/PermissionCatalogField/index.ee.tsx
+++ b/packages/web/src/components/PermissionCatalogField/index.ee.tsx
@@ -14,17 +14,21 @@ import * as React from 'react';
import ControlledCheckbox from 'components/ControlledCheckbox';
import usePermissionCatalog from 'hooks/usePermissionCatalog.ee';
import PermissionSettings from './PermissionSettings.ee';
+import PermissionCatalogFieldLoader from './PermissionCatalogFieldLoader';
type PermissionCatalogFieldProps = {
name?: string;
disabled?: boolean;
};
-const PermissionCatalogField = ({ name = 'permissions', disabled = false }: PermissionCatalogFieldProps) => {
- const permissionCatalog = usePermissionCatalog();
+const PermissionCatalogField = ({
+ name = 'permissions',
+ disabled = false,
+}: PermissionCatalogFieldProps) => {
+ const { permissionCatalog, loading } = usePermissionCatalog();
const [dialogName, setDialogName] = React.useState();
- if (!permissionCatalog) return ();
+ if (loading) return ;
return (
@@ -33,14 +37,14 @@ const PermissionCatalogField = ({ name = 'permissions', disabled = false }: Perm
- {permissionCatalog.actions.map(action => (
+ {permissionCatalog.actions.map((action) => (
{action.label}
@@ -58,21 +62,12 @@ const PermissionCatalogField = ({ name = 'permissions', disabled = false }: Perm
sx={{ '&:last-child td': { border: 0 } }}
>
-
- {subject.label}
-
+ {subject.label}
{permissionCatalog.actions.map((action) => (
-
-
+
+
{action.subjects.includes(subject.key) && (
-
+
- )
+ );
};
export default PermissionCatalogField;
diff --git a/packages/web/src/components/RoleList/index.ee.tsx b/packages/web/src/components/RoleList/index.ee.tsx
index 94d02b53..285873ac 100644
--- a/packages/web/src/components/RoleList/index.ee.tsx
+++ b/packages/web/src/components/RoleList/index.ee.tsx
@@ -13,14 +13,14 @@ 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 loading bar
export default function RoleList(): React.ReactElement {
const formatMessage = useFormatMessage();
- const { roles } = useRoles();
+ const { roles, loading } = useRoles();
return (
@@ -49,34 +49,41 @@ export default function RoleList(): React.ReactElement {
- {roles.map((role) => (
-
-
- {role.name}
-
+ {loading && }
+ {!loading &&
+ roles.map((role) => (
+
+
+ {role.name}
+
-
- {role.description}
-
+
+
+ {role.description}
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
- ))}
+
+
+
+
+ ))}
diff --git a/packages/web/src/components/UserList/index.tsx b/packages/web/src/components/UserList/index.tsx
index f394936c..956c2ad3 100644
--- a/packages/web/src/components/UserList/index.tsx
+++ b/packages/web/src/components/UserList/index.tsx
@@ -13,11 +13,11 @@ 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 loading bar
export default function UserList(): React.ReactElement {
const formatMessage = useFormatMessage();
const { users, loading } = useUsers();
@@ -49,34 +49,36 @@ export default function UserList(): React.ReactElement {
- {users.map((user) => (
-
-
- {user.fullName}
-
+ {loading && }
+ {!loading &&
+ users.map((user) => (
+
+
+ {user.fullName}
+
-
- {user.email}
-
+
+ {user.email}
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
- ))}
+
+
+
+
+ ))}
diff --git a/packages/web/src/helpers/computePermissions.ee.ts b/packages/web/src/helpers/computePermissions.ee.ts
index f57b4cd8..6313ec77 100644
--- a/packages/web/src/helpers/computePermissions.ee.ts
+++ b/packages/web/src/helpers/computePermissions.ee.ts
@@ -3,24 +3,33 @@ import { IRole, IPermission } from '@automatisch/types';
type ComputeAction = {
conditions: Record;
value: boolean;
-}
+};
type ComputedActions = Record;
type ComputedPermissions = Record;
-export type RoleWithComputedPermissions = IRole & { computedPermissions: ComputedPermissions };
+export type RoleWithComputedPermissions = IRole & {
+ computedPermissions: ComputedPermissions;
+};
-export function getRoleWithComputedPermissions(role: IRole): RoleWithComputedPermissions {
- const computedPermissions = role.permissions.reduce((computedPermissions, permission) => ({
- ...computedPermissions,
- [permission.subject]: {
- ...(computedPermissions[permission.subject] || {}),
- [permission.action]: {
- conditions: Object.fromEntries(permission
- .conditions
- .map(condition => [condition, true])),
- value: true,
+export function getRoleWithComputedPermissions(
+ role?: IRole
+): Partial {
+ if (!role) return {};
+
+ const computedPermissions = role.permissions.reduce(
+ (computedPermissions, permission) => ({
+ ...computedPermissions,
+ [permission.subject]: {
+ ...(computedPermissions[permission.subject] || {}),
+ [permission.action]: {
+ conditions: Object.fromEntries(
+ permission.conditions.map((condition) => [condition, true])
+ ),
+ value: true,
+ },
},
- }
- }), {} as ComputedPermissions);
+ }),
+ {} as ComputedPermissions
+ );
return {
...role,
@@ -31,29 +40,27 @@ export function getRoleWithComputedPermissions(role: IRole): RoleWithComputedPer
export function getPermissions(computedPermissions?: ComputedPermissions) {
if (!computedPermissions) return [];
- return Object
- .entries(computedPermissions)
- .reduce((permissions, computedPermissionEntry) => {
+ return Object.entries(computedPermissions).reduce(
+ (permissions, computedPermissionEntry) => {
const [subject, actionsWithConditions] = computedPermissionEntry;
for (const action in actionsWithConditions) {
- const {
- value: permitted,
- conditions = {},
- } = actionsWithConditions[action];
+ const { value: permitted, conditions = {} } =
+ actionsWithConditions[action];
if (permitted) {
permissions.push({
action,
subject,
- conditions: Object
- .entries(conditions)
+ conditions: Object.entries(conditions)
.filter(([, enabled]) => enabled)
.map(([condition]) => condition),
- })
+ });
}
}
return permissions;
- }, [] as Partial[]);
+ },
+ [] as Partial[]
+ );
}
diff --git a/packages/web/src/hooks/usePermissionCatalog.ee.ts b/packages/web/src/hooks/usePermissionCatalog.ee.ts
index d9496ceb..0292e4f0 100644
--- a/packages/web/src/hooks/usePermissionCatalog.ee.ts
+++ b/packages/web/src/hooks/usePermissionCatalog.ee.ts
@@ -3,8 +3,13 @@ import { IPermissionCatalog } from '@automatisch/types';
import { GET_PERMISSION_CATALOG } from 'graphql/queries/get-permission-catalog.ee';
-export default function usePermissionCatalog(): IPermissionCatalog {
- const { data } = useQuery(GET_PERMISSION_CATALOG);
+type UsePermissionCatalogReturn = {
+ permissionCatalog: IPermissionCatalog;
+ loading: boolean;
+};
- return data?.getPermissionCatalog;
+export default function usePermissionCatalog(): UsePermissionCatalogReturn {
+ const { data, loading } = useQuery(GET_PERMISSION_CATALOG);
+
+ return { permissionCatalog: data?.getPermissionCatalog, loading };
}
diff --git a/packages/web/src/hooks/useRole.ee.ts b/packages/web/src/hooks/useRole.ee.ts
index 5263254d..ff6c63bb 100644
--- a/packages/web/src/hooks/useRole.ee.ts
+++ b/packages/web/src/hooks/useRole.ee.ts
@@ -6,7 +6,7 @@ import { GET_ROLE } from 'graphql/queries/get-role.ee';
type QueryResponse = {
getRole: IRole;
-}
+};
export default function useRole(roleId?: string) {
const [getRole, { data, loading }] = useLazyQuery(GET_ROLE);
@@ -15,14 +15,14 @@ export default function useRole(roleId?: string) {
if (roleId) {
getRole({
variables: {
- id: roleId
- }
+ id: roleId,
+ },
});
}
}, [roleId]);
return {
role: data?.getRole,
- loading
+ loading,
};
}
diff --git a/packages/web/src/pages/EditRole/index.ee.tsx b/packages/web/src/pages/EditRole/index.ee.tsx
index 64915cfd..58cd878a 100644
--- a/packages/web/src/pages/EditRole/index.ee.tsx
+++ b/packages/web/src/pages/EditRole/index.ee.tsx
@@ -3,6 +3,7 @@ import LoadingButton from '@mui/lab/LoadingButton';
import Container from '@mui/material/Container';
import Grid from '@mui/material/Grid';
import Stack from '@mui/material/Stack';
+import Skeleton from '@mui/material/Skeleton';
import * as React from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { useSnackbar } from 'notistack';
@@ -25,7 +26,6 @@ type EditRoleParams = {
roleId: string;
};
-// TODO: introduce loading bar
export default function EditRole(): React.ReactElement {
const formatMessage = useFormatMessage();
const [updateRole, { loading }] = useMutation(UPDATE_ROLE);
@@ -61,8 +61,6 @@ export default function EditRole(): React.ReactElement {
}
};
- if (roleLoading || !role) return ;
-
const roleWithComputedPermissions = getRoleWithComputedPermissions(role);
return (
@@ -78,24 +76,34 @@ export default function EditRole(): React.ReactElement {
onSubmit={handleRoleUpdate}
>
-
+ {roleLoading && (
+ <>
+
+
+ >
+ )}
+ {!roleLoading && role && (
+ <>
+
-
+
+ >
+ )}
{formatMessage('editRole.submit')}
diff --git a/packages/web/src/pages/EditUser/index.tsx b/packages/web/src/pages/EditUser/index.tsx
index 8c663868..c4253b07 100644
--- a/packages/web/src/pages/EditUser/index.tsx
+++ b/packages/web/src/pages/EditUser/index.tsx
@@ -5,6 +5,7 @@ import Container from '@mui/material/Container';
import Grid from '@mui/material/Grid';
import Stack from '@mui/material/Stack';
import MuiTextField from '@mui/material/TextField';
+import Skeleton from '@mui/material/Skeleton';
import LoadingButton from '@mui/lab/LoadingButton';
import { IUser, IRole } from '@automatisch/types';
import { useSnackbar } from 'notistack';
@@ -28,7 +29,6 @@ function generateRoleOptions(roles: IRole[]) {
return roles?.map(({ name: label, id: value }) => ({ label, value }));
}
-// TODO: introduce loading bar
export default function EditUser(): React.ReactElement {
const formatMessage = useFormatMessage();
const [updateUser, { loading }] = useMutation(UPDATE_USER);
@@ -63,8 +63,6 @@ export default function EditUser(): React.ReactElement {
}
};
- if (userLoading) return ;
-
return (
@@ -73,50 +71,61 @@ export default function EditUser(): React.ReactElement {
-
+ )}
+
+ {!userLoading && (
+
+ )}