diff --git a/packages/core/admin/admin/src/pages/Users/ListPage/Table/TableHead/index.js b/packages/core/admin/admin/src/pages/Users/ListPage/DynamicTable/TableHead/index.js
similarity index 98%
rename from packages/core/admin/admin/src/pages/Users/ListPage/Table/TableHead/index.js
rename to packages/core/admin/admin/src/pages/Users/ListPage/DynamicTable/TableHead/index.js
index a1797904ee..22812a5110 100644
--- a/packages/core/admin/admin/src/pages/Users/ListPage/Table/TableHead/index.js
+++ b/packages/core/admin/admin/src/pages/Users/ListPage/DynamicTable/TableHead/index.js
@@ -21,7 +21,7 @@ const TableHead = ({
withBulkActions,
}) => {
const [{ query }, setQuery] = useQueryParams();
- const sort = query.sort;
+ const sort = query.sort || '';
const [sortBy, sortOrder] = sort.split(':');
const isIndeterminate = !areAllEntriesSelected && entriesToDelete.length;
diff --git a/packages/core/admin/admin/src/pages/Users/ListPage/Table/TableRows/index.js b/packages/core/admin/admin/src/pages/Users/ListPage/DynamicTable/TableRows/index.js
similarity index 100%
rename from packages/core/admin/admin/src/pages/Users/ListPage/Table/TableRows/index.js
rename to packages/core/admin/admin/src/pages/Users/ListPage/DynamicTable/TableRows/index.js
diff --git a/packages/core/admin/admin/src/pages/Users/ListPage/Table/index.js b/packages/core/admin/admin/src/pages/Users/ListPage/DynamicTable/index.js
similarity index 96%
rename from packages/core/admin/admin/src/pages/Users/ListPage/Table/index.js
rename to packages/core/admin/admin/src/pages/Users/ListPage/DynamicTable/index.js
index 3d0845d9c9..6055873365 100644
--- a/packages/core/admin/admin/src/pages/Users/ListPage/Table/index.js
+++ b/packages/core/admin/admin/src/pages/Users/ListPage/DynamicTable/index.js
@@ -9,7 +9,7 @@ const Table = ({ canDelete, canUpdate, headers, rows, withBulkActions, withMainA
const [entriesToDelete, setEntriesToDelete] = useState([]);
const [{ query }] = useQueryParams();
const ROW_COUNT = rows.length + 1;
- const COL_COUNT = 7;
+ const COL_COUNT = headers.length + (withBulkActions ? 1 : 0) + (withMainAction ? 1 : 0);
const hasFilters = query.filters !== undefined;
const areAllEntriesSelected = entriesToDelete.length === rows.length && rows.length > 0;
@@ -56,7 +56,6 @@ const Table = ({ canDelete, canUpdate, headers, rows, withBulkActions, withMainA
canDelete={canDelete}
canUpdate={canUpdate}
entriesToDelete={entriesToDelete}
- data={rows}
headers={headers}
onSelectRow={handleSelectRow}
rows={rows}
diff --git a/packages/core/admin/admin/src/pages/Users/ListPage/DynamicTable/tests/index.test.js b/packages/core/admin/admin/src/pages/Users/ListPage/DynamicTable/tests/index.test.js
new file mode 100644
index 0000000000..9fd53fa5cf
--- /dev/null
+++ b/packages/core/admin/admin/src/pages/Users/ListPage/DynamicTable/tests/index.test.js
@@ -0,0 +1,575 @@
+import React from 'react';
+import { render } from '@testing-library/react';
+import { IntlProvider } from 'react-intl';
+import { Router } from 'react-router-dom';
+import { createMemoryHistory } from 'history';
+import { useQueryParams } from '@strapi/helper-plugin';
+import Theme from '../../../../../components/Theme';
+import DynamicTable from '../index';
+
+jest.mock('@strapi/helper-plugin', () => ({
+ ...jest.requireActual('@strapi/helper-plugin'),
+ useQueryParams: jest.fn(() => [{ query: {} }, jest.fn()]),
+}));
+
+// eslint-disable-next-line react/prop-types
+const makeApp = ({
+ canDelete = true,
+ canUpdate = true,
+ headers,
+ rows = [],
+ withBulkActions = true,
+ withMainAction = true,
+}) => (
+
+
+
+
+
+
+
+);
+
+describe('DynamicTable', () => {
+ it('renders and matches the snapshot', () => {
+ const headers = [
+ {
+ name: 'firstname',
+ key: 'firstname',
+ metadatas: { label: 'Name', sortable: true },
+ },
+ {
+ key: 'email',
+ name: 'email',
+ metadatas: { label: 'Email', sortable: true },
+ },
+ ];
+ const app = makeApp({ headers });
+ const { container, getByText } = render(app);
+
+ expect(container.firstChild).toMatchInlineSnapshot(`
+ .c24 {
+ font-weight: 500;
+ font-size: 1rem;
+ line-height: 1.25;
+ color: #666687;
+ }
+
+ .c19 {
+ background: #ffffff;
+ padding: 64px;
+ }
+
+ .c21 {
+ padding-bottom: 24px;
+ }
+
+ .c23 {
+ padding-bottom: 16px;
+ }
+
+ .c20 {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-flex-direction: column;
+ -ms-flex-direction: column;
+ flex-direction: column;
+ -webkit-align-items: center;
+ -webkit-box-align: center;
+ -ms-flex-align: center;
+ align-items: center;
+ text-align: center;
+ }
+
+ .c22 svg {
+ height: 5.5rem;
+ }
+
+ .c16 tr:last-of-type {
+ border-bottom: none;
+ }
+
+ .c17 {
+ border-bottom: 1px solid #eaeaef;
+ }
+
+ .c17 td,
+ .c17 th {
+ padding: 16px;
+ }
+
+ .c17 td:first-of-type,
+ .c17 th:first-of-type {
+ padding: 0 4px;
+ }
+
+ .c18 {
+ vertical-align: middle;
+ text-align: left;
+ color: #666687;
+ outline-offset: -4px;
+ }
+
+ .c18 input {
+ vertical-align: sub;
+ }
+
+ .c12 {
+ font-weight: 400;
+ font-size: 0.875rem;
+ line-height: 1.43;
+ color: #32324d;
+ }
+
+ .c13 {
+ font-weight: 600;
+ line-height: 1.14;
+ }
+
+ .c14 {
+ font-weight: 600;
+ font-size: 0.6875rem;
+ line-height: 1.45;
+ text-transform: uppercase;
+ }
+
+ .c0 {
+ box-shadow: 0px 1px 4px rgba(33,33,52,0.1);
+ }
+
+ .c1 {
+ background: #ffffff;
+ }
+
+ .c3 {
+ padding-right: 24px;
+ padding-left: 24px;
+ }
+
+ .c9 {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-flex-direction: row;
+ -ms-flex-direction: row;
+ flex-direction: row;
+ -webkit-align-items: center;
+ -webkit-box-align: center;
+ -ms-flex-align: center;
+ align-items: center;
+ }
+
+ .c10 {
+ margin: 0;
+ height: 18px;
+ min-width: 18px;
+ border-radius: 4px;
+ border: 1px solid #c0c0cf;
+ -webkit-appearance: none;
+ background-color: #ffffff;
+ }
+
+ .c10:checked {
+ background-color: #4945ff;
+ border: 1px solid #4945ff;
+ }
+
+ .c10:checked:after {
+ content: '';
+ display: block;
+ position: relative;
+ background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAiIGhlaWdodD0iOCIgdmlld0JveD0iMCAwIDEwIDgiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGgKICAgIGQ9Ik04LjU1MzIzIDAuMzk2OTczQzguNjMxMzUgMC4zMTYzNTUgOC43NjA1MSAwLjMxNTgxMSA4LjgzOTMxIDAuMzk1NzY4TDkuODYyNTYgMS40MzQwN0M5LjkzODkzIDEuNTExNTcgOS45MzkzNSAxLjYzNTkgOS44NjM0OSAxLjcxMzlMNC4wNjQwMSA3LjY3NzI0QzMuOTg1OSA3Ljc1NzU1IDMuODU3MDcgNy43NTgwNSAzLjc3ODM0IDcuNjc4MzRMMC4xMzg2NiAzLjk5MzMzQzAuMDYxNzc5OCAzLjkxNTQ5IDAuMDYxNzEwMiAzLjc5MDMyIDAuMTM4NTA0IDMuNzEyNEwxLjE2MjEzIDIuNjczNzJDMS4yNDAzOCAyLjU5NDMyIDEuMzY4NDMgMi41OTQyMiAxLjQ0NjggMi42NzM0OEwzLjkyMTc0IDUuMTc2NDdMOC41NTMyMyAwLjM5Njk3M1oiCiAgICBmaWxsPSJ3aGl0ZSIKICAvPgo8L3N2Zz4=) no-repeat no-repeat center center;
+ width: 10px;
+ height: 10px;
+ left: 50%;
+ top: 50%;
+ -webkit-transform: translateX(-50%) translateY(-50%);
+ -ms-transform: translateX(-50%) translateY(-50%);
+ transform: translateX(-50%) translateY(-50%);
+ }
+
+ .c10:checked:disabled:after {
+ background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAiIGhlaWdodD0iOCIgdmlld0JveD0iMCAwIDEwIDgiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGgKICAgIGQ9Ik04LjU1MzIzIDAuMzk2OTczQzguNjMxMzUgMC4zMTYzNTUgOC43NjA1MSAwLjMxNTgxMSA4LjgzOTMxIDAuMzk1NzY4TDkuODYyNTYgMS40MzQwN0M5LjkzODkzIDEuNTExNTcgOS45MzkzNSAxLjYzNTkgOS44NjM0OSAxLjcxMzlMNC4wNjQwMSA3LjY3NzI0QzMuOTg1OSA3Ljc1NzU1IDMuODU3MDcgNy43NTgwNSAzLjc3ODM0IDcuNjc4MzRMMC4xMzg2NiAzLjk5MzMzQzAuMDYxNzc5OCAzLjkxNTQ5IDAuMDYxNzEwMiAzLjc5MDMyIDAuMTM4NTA0IDMuNzEyNEwxLjE2MjEzIDIuNjczNzJDMS4yNDAzOCAyLjU5NDMyIDEuMzY4NDMgMi41OTQyMiAxLjQ0NjggMi42NzM0OEwzLjkyMTc0IDUuMTc2NDdMOC41NTMyMyAwLjM5Njk3M1oiCiAgICBmaWxsPSIjOEU4RUE5IgogIC8+Cjwvc3ZnPg==) no-repeat no-repeat center center;
+ }
+
+ .c10:disabled {
+ background-color: #dcdce4;
+ border: 1px solid #c0c0cf;
+ }
+
+ .c10:indeterminate {
+ background-color: #4945ff;
+ border: 1px solid #4945ff;
+ }
+
+ .c10:indeterminate:after {
+ content: '';
+ display: block;
+ position: relative;
+ color: white;
+ height: 2px;
+ width: 10px;
+ background-color: #ffffff;
+ left: 50%;
+ top: 50%;
+ -webkit-transform: translateX(-50%) translateY(-50%);
+ -ms-transform: translateX(-50%) translateY(-50%);
+ transform: translateX(-50%) translateY(-50%);
+ }
+
+ .c10:indeterminate:disabled {
+ background-color: #dcdce4;
+ border: 1px solid #c0c0cf;
+ }
+
+ .c10:indeterminate:disabled:after {
+ background-color: #8e8ea9;
+ }
+
+ .c15 {
+ border: 0;
+ -webkit-clip: rect(0 0 0 0);
+ clip: rect(0 0 0 0);
+ height: 1px;
+ margin: -1px;
+ overflow: hidden;
+ padding: 0;
+ position: absolute;
+ width: 1px;
+ }
+
+ .c5 {
+ width: 100%;
+ white-space: nowrap;
+ }
+
+ .c2 {
+ position: relative;
+ border-radius: 4px 4px 0 0;
+ }
+
+ .c2:before {
+ background: linear-gradient(90deg,#000000 0%,rgba(0,0,0,0) 100%);
+ opacity: 0.2;
+ position: absolute;
+ height: 100%;
+ box-shadow: 0px 1px 4px rgba(33,33,52,0.1);
+ width: 8px;
+ left: 0;
+ }
+
+ .c2:after {
+ background: linear-gradient(270deg,#000000 0%,rgba(0,0,0,0) 100%);
+ opacity: 0.2;
+ position: absolute;
+ height: 100%;
+ box-shadow: 0px 1px 4px rgba(33,33,52,0.1);
+ width: 8px;
+ right: 0;
+ top: 0;
+ }
+
+ .c4 {
+ overflow-x: auto;
+ }
+
+ .c6 {
+ border-bottom: 1px solid #eaeaef;
+ }
+
+ .c7 {
+ border-bottom: 1px solid #eaeaef;
+ }
+
+ .c7 td,
+ .c7 th {
+ padding: 16px;
+ }
+
+ .c7 td:first-of-type,
+ .c7 th:first-of-type {
+ padding: 0 4px;
+ }
+
+ .c8 {
+ vertical-align: middle;
+ text-align: left;
+ color: #666687;
+ outline-offset: -4px;
+ }
+
+ .c8 input {
+ vertical-align: sub;
+ }
+
+ .c11 svg {
+ height: 0.25rem;
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+
+
+
+
+
+
+ |
+
+
+
+
+
+
+
+ |
+
+
+ |
+
+
+
+
+
+
+
+
+
+ You don't have any content yet...
+
+
+
+ |
+
+
+
+
+
+
+ `);
+
+ expect(getByText("You don't have any content yet...")).toBeInTheDocument();
+ });
+
+ it('should show the empty state layout with the filter text when there is no content and some filters applied', () => {
+ useQueryParams.mockImplementationOnce(() => [
+ {
+ query: { filters: { $and: [{ firstname: 'soup' }] } },
+ },
+ ]);
+
+ const headers = [
+ {
+ name: 'firstname',
+ key: 'firstname',
+ metadatas: { label: 'Name', sortable: true },
+ },
+ {
+ key: 'email',
+ name: 'email',
+ metadatas: { label: 'Email', sortable: true },
+ },
+ ];
+ const app = makeApp({ headers });
+ const { getByText } = render(app);
+
+ expect(getByText('There are no Users with the applied filters...')).toBeInTheDocument();
+ });
+
+ it('should show the data', () => {
+ const headers = [
+ {
+ name: 'firstname',
+ key: 'firstname',
+ metadatas: { label: 'Name', sortable: true },
+ },
+ {
+ key: 'email',
+ name: 'email',
+ metadatas: { label: 'Email', sortable: true },
+ },
+ ];
+ const rows = [
+ {
+ id: 1,
+ firstname: 'soup',
+ },
+ {
+ id: 2,
+ firstname: 'dummy',
+ email: 'dummy@strapi.io',
+ },
+ ];
+ const app = makeApp({ headers, rows });
+ const { getByText } = render(app);
+
+ expect(getByText('soup')).toBeInTheDocument();
+ expect(getByText('-')).toBeInTheDocument();
+ expect(getByText('dummy')).toBeInTheDocument();
+ expect(getByText('dummy@strapi.io')).toBeInTheDocument();
+ });
+});
diff --git a/packages/core/admin/admin/src/pages/Users/ListPage/index.js b/packages/core/admin/admin/src/pages/Users/ListPage/index.js
index b4f6d59e73..6f9b6d33d9 100644
--- a/packages/core/admin/admin/src/pages/Users/ListPage/index.js
+++ b/packages/core/admin/admin/src/pages/Users/ListPage/index.js
@@ -17,7 +17,7 @@ import { useIntl } from 'react-intl';
import { useQuery } from 'react-query';
import get from 'lodash/get';
import adminPermissions from '../../../permissions';
-import Table from './Table';
+import DynamicTable from './DynamicTable';
import fetchData from './utils/api';
import tableHeaders from './utils/tableHeaders';
@@ -25,6 +25,7 @@ const ListPage = () => {
const {
allowedActions: { canCreate, canDelete, canRead, canUpdate },
} = useRBAC(adminPermissions.settings.users);
+
const toggleNotification = useNotification();
// const [isWarningDeleteAllOpened, setIsWarningDeleteAllOpened] = useState(false);
@@ -232,7 +233,11 @@ const ListPage = () => {
(status !== 'success' && status !== 'error') || (status === 'success' && isFetching);
const createAction = canCreate ? (
-