diff --git a/app/src/common/constants/permissions.js b/app/src/common/constants/permissions.js
index c9e5a4ffc4..9da13dbcbc 100644
--- a/app/src/common/constants/permissions.js
+++ b/app/src/common/constants/permissions.js
@@ -106,8 +106,8 @@ export const PERMISSIONS_MAP = {
},
[MEMBER]: {
[VIEWER]: {
- [ACTIONS.SEE_SETTINGS]: true,
- [ACTIONS.SEE_MEMBERS]: true,
+ [ACTIONS.SEE_SETTINGS]: false,
+ [ACTIONS.SEE_MEMBERS]: false,
},
[EDITOR]: {
[ACTIONS.SEE_SETTINGS]: true,
diff --git a/app/src/common/urls.js b/app/src/common/urls.js
index 34f574c55d..93d166f36c 100644
--- a/app/src/common/urls.js
+++ b/app/src/common/urls.js
@@ -129,6 +129,8 @@ export const URLS = {
`${urlCommonBase}organizations${getQueryParams(preferencesObj)}`,
organizationProjects: (organizationId, preferencesObj = {}) =>
`${urlCommonBase}organizations/${organizationId}/projects${getQueryParams(preferencesObj)}`,
+ organizationUsers: (organizationId, preferencesObj = {}) =>
+ `${urlCommonBase}organizations/${organizationId}/users${getQueryParams(preferencesObj)}`,
projectByName: (projectKey) => `${urlBase}project/${projectKey}`,
project: (ids = []) => `${urlBase}project?ids=${ids.join(',')}`,
diff --git a/app/src/componentLibrary/sidebar/sidebar.scss b/app/src/componentLibrary/sidebar/sidebar.scss
index 4c385e7306..a88ab211a6 100644
--- a/app/src/componentLibrary/sidebar/sidebar.scss
+++ b/app/src/componentLibrary/sidebar/sidebar.scss
@@ -42,15 +42,20 @@
box-sizing: border-box;
height: 100%;
gap: 32px;
- background-image:
- linear-gradient(to right, $COLOR--darkmode-gray-500 0 48px,$COLOR--darkmode-gray-450 48px 100%);
+ background-image: linear-gradient(
+ to right,
+ $COLOR--darkmode-gray-500 0 48px,
+ $COLOR--darkmode-gray-450 48px 100%
+ );
z-index: 3;
width: 328px;
align-items: start;
}
@keyframes delay-overflow {
- to { overflow: visible; }
+ to {
+ overflow: visible;
+ }
}
.items-block {
diff --git a/app/src/componentLibrary/sidebar/sidebarButton/sidebarButton.scss b/app/src/componentLibrary/sidebar/sidebarButton/sidebarButton.scss
index 50d9186537..1794be7d08 100644
--- a/app/src/componentLibrary/sidebar/sidebarButton/sidebarButton.scss
+++ b/app/src/componentLibrary/sidebar/sidebarButton/sidebarButton.scss
@@ -14,7 +14,7 @@
* limitations under the License.
*/
- .btn-icon {
+.btn-icon {
width: 48px;
height: 40px;
diff --git a/app/src/components/integrations/modals/addLdapIntegrationModal/twoSteps/twoStepsContent/twoStepsContent.scss b/app/src/components/integrations/modals/addLdapIntegrationModal/twoSteps/twoStepsContent/twoStepsContent.scss
index 4b747cdf69..54a39612f5 100644
--- a/app/src/components/integrations/modals/addLdapIntegrationModal/twoSteps/twoStepsContent/twoStepsContent.scss
+++ b/app/src/components/integrations/modals/addLdapIntegrationModal/twoSteps/twoStepsContent/twoStepsContent.scss
@@ -28,7 +28,7 @@
padding: 24px 16px;
background-color: $COLOR--bg-000;
border-radius: 8px;
- box-shadow: 0 1px 3px 0 rgba(55, 67, 98, 0.10);
+ box-shadow: 0 1px 3px 0 rgba(55, 67, 98, 0.1);
}
.step {
@@ -54,6 +54,7 @@
}
}
-.scrollbars { // TODO will be replaced with new Scroll component in the future
+.scrollbars {
+ // TODO will be replaced with new Scroll component in the future
width: 376px !important;
}
diff --git a/app/src/components/integrations/modals/addLdapIntegrationModal/twoSteps/twoStepsFooter/twoStepsFooter.scss b/app/src/components/integrations/modals/addLdapIntegrationModal/twoSteps/twoStepsFooter/twoStepsFooter.scss
index 6adf7937dd..0637173b8d 100644
--- a/app/src/components/integrations/modals/addLdapIntegrationModal/twoSteps/twoStepsFooter/twoStepsFooter.scss
+++ b/app/src/components/integrations/modals/addLdapIntegrationModal/twoSteps/twoStepsFooter/twoStepsFooter.scss
@@ -21,7 +21,8 @@
padding-top: 16px;
}
-.cancel-button, .discard-button {
+.cancel-button,
+.discard-button {
margin-left: auto;
}
diff --git a/app/src/controllers/instance/organizations/index.js b/app/src/controllers/instance/organizations/index.js
index c28ec3d2ef..b89052a05d 100644
--- a/app/src/controllers/instance/organizations/index.js
+++ b/app/src/controllers/instance/organizations/index.js
@@ -18,6 +18,7 @@ export { FETCH_ORGANIZATIONS } from './constants';
export { fetchOrganizationsAction } from './actionCreators';
export { organizationsReducer } from './reducer';
export {
+ organizationsSelector,
organizationsListSelector,
organizationsListLoadingSelector,
organizationsListPaginationSelector,
diff --git a/app/src/controllers/instance/organizations/sagas.js b/app/src/controllers/instance/organizations/sagas.js
index c179fb1676..88dad58d28 100644
--- a/app/src/controllers/instance/organizations/sagas.js
+++ b/app/src/controllers/instance/organizations/sagas.js
@@ -18,8 +18,6 @@ import { takeEvery, all, put } from 'redux-saga/effects';
import { URLS } from 'common/urls';
import { showDefaultErrorNotification } from 'controllers/notification';
import { fetchDataAction } from 'controllers/fetch';
-import { organizationSagas } from 'controllers/organization';
-import { projectsSagas } from 'controllers/organization/projects';
import { FETCH_ORGANIZATIONS, NAMESPACE } from './constants';
function* fetchOrganizations() {
@@ -35,5 +33,5 @@ function* watchFetchOrganizations() {
}
export function* organizationsSagas() {
- yield all([watchFetchOrganizations(), organizationSagas(), projectsSagas()]);
+ yield all([watchFetchOrganizations()]);
}
diff --git a/app/src/controllers/organization/projects/index.js b/app/src/controllers/organization/projects/index.js
index 4d223dc072..f16697a714 100644
--- a/app/src/controllers/organization/projects/index.js
+++ b/app/src/controllers/organization/projects/index.js
@@ -16,12 +16,7 @@
export { fetchOrganizationProjectsAction, navigateToProjectAction } from './actionCreators';
export { projectsReducer } from './reducer';
-export {
- projectsPaginationSelector,
- projectsSelector,
- loadingSelector,
- querySelector,
-} from './selectors';
+export { projectsPaginationSelector, projectsSelector, loadingSelector } from './selectors';
export { projectsSagas } from './sagas';
export {
DEFAULT_LIMITATION,
diff --git a/app/src/controllers/organization/projects/sagas.js b/app/src/controllers/organization/projects/sagas.js
index 33e45c09ed..c0a6af46ea 100644
--- a/app/src/controllers/organization/projects/sagas.js
+++ b/app/src/controllers/organization/projects/sagas.js
@@ -21,10 +21,9 @@ import { fetch } from 'common/utils';
import { hideModalAction } from 'controllers/modal';
import { NOTIFICATION_TYPES, showNotification } from 'controllers/notification';
import { fetchOrganizationBySlugAction } from '..';
-import { activeOrganizationSelector } from '../selectors';
+import { activeOrganizationSelector, querySelector } from '../selectors';
import { fetchOrganizationProjectsAction } from './actionCreators';
-import { querySelector } from './selectors';
-import { CREATE_PROJECT, ERROR_CODES, FETCH_ORGANIZATION_PROJECTS, NAMESPACE } from './constants';
+import { CREATE_PROJECT, FETCH_ORGANIZATION_PROJECTS, ERROR_CODES, NAMESPACE } from './constants';
function* fetchOrganizationProjects({ payload: organizationId }) {
const query = yield select(querySelector);
diff --git a/app/src/controllers/organization/projects/selectors.js b/app/src/controllers/organization/projects/selectors.js
index 3f450eab23..243cf099ec 100644
--- a/app/src/controllers/organization/projects/selectors.js
+++ b/app/src/controllers/organization/projects/selectors.js
@@ -14,11 +14,6 @@
* limitations under the License.
*/
-import { createSelector } from 'reselect';
-import { createQueryParametersSelector } from 'controllers/pages';
-import { SORTING_ASC } from 'controllers/sorting';
-import { getAlternativePaginationAndSortParams, PAGE_KEY, SIZE_KEY } from 'controllers/pagination';
-import { SORTING_KEY, DEFAULT_PAGINATION } from './constants';
import { organizationSelector } from '../selectors';
const domainSelector = (state) => organizationSelector(state).projects || {};
@@ -26,25 +21,3 @@ const domainSelector = (state) => organizationSelector(state).projects || {};
export const projectsPaginationSelector = (state) => domainSelector(state).pagination;
export const projectsSelector = (state) => domainSelector(state).projects;
export const loadingSelector = (state) => domainSelector(state).loading || false;
-
-export const createOrganizationProjectsParametersSelector = ({
- defaultPagination,
- defaultSorting,
- sortingKey,
-} = {}) =>
- createSelector(
- createQueryParametersSelector({
- defaultPagination,
- defaultSorting,
- sortingKey,
- }),
- ({ [SIZE_KEY]: limit, [SORTING_KEY]: sort, [PAGE_KEY]: pageNumber, ...rest }) => {
- return { ...getAlternativePaginationAndSortParams(sort, limit, pageNumber), ...rest };
- },
- );
-
-export const querySelector = createOrganizationProjectsParametersSelector({
- defaultPagination: DEFAULT_PAGINATION,
- defaultDirection: SORTING_ASC,
- sortingKey: SORTING_KEY,
-});
diff --git a/app/src/controllers/organization/reducer.js b/app/src/controllers/organization/reducer.js
index c8514b38a3..1bfe3f90f6 100644
--- a/app/src/controllers/organization/reducer.js
+++ b/app/src/controllers/organization/reducer.js
@@ -19,6 +19,7 @@ import { fetchReducer } from 'controllers/fetch';
import { loadingReducer } from 'controllers/loading';
import { queueReducers } from 'common/utils';
import { projectsReducer } from './projects/reducer';
+import { usersReducer } from './users/reducer';
import { FETCH_ORGANIZATION_BY_SLUG, SET_ACTIVE_ORGANIZATION } from './constants';
const setActiveOrganizationReducer = (state = [], { type = '', payload = {} }) => {
@@ -41,4 +42,5 @@ export const organizationReducer = combineReducers({
),
organizationLoading: loadingReducer(FETCH_ORGANIZATION_BY_SLUG),
projects: projectsReducer,
+ users: usersReducer,
});
diff --git a/app/src/controllers/organization/sagas.js b/app/src/controllers/organization/sagas.js
index 68c0e6dbdf..3fedbdfbf2 100644
--- a/app/src/controllers/organization/sagas.js
+++ b/app/src/controllers/organization/sagas.js
@@ -20,9 +20,10 @@ import { redirect } from 'redux-first-router';
import { ORGANIZATIONS_PAGE } from 'controllers/pages';
import { URLS } from 'common/urls';
import { showDefaultErrorNotification } from 'controllers/notification';
-import { fetchOrganizationProjectsAction } from 'controllers/organization/projects';
+import { fetchOrganizationProjectsAction, projectsSagas } from './projects';
import { FETCH_ORGANIZATION_BY_SLUG, PREPARE_ACTIVE_ORGANIZATION_PROJECTS } from './constants';
import { activeOrganizationSelector } from './selectors';
+import { usersSagas } from './users';
function* fetchOrganizationBySlug({ payload: slug }) {
try {
@@ -58,5 +59,10 @@ function* watchFetchOrganizationBySlug() {
}
export function* organizationSagas() {
- yield all([watchFetchOrganizationProjects(), watchFetchOrganizationBySlug()]);
+ yield all([
+ watchFetchOrganizationProjects(),
+ watchFetchOrganizationBySlug(),
+ projectsSagas(),
+ usersSagas(),
+ ]);
}
diff --git a/app/src/controllers/organization/selectors.js b/app/src/controllers/organization/selectors.js
index 10269b7e36..af40e6cef9 100644
--- a/app/src/controllers/organization/selectors.js
+++ b/app/src/controllers/organization/selectors.js
@@ -13,8 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
+import { createSelector } from 'reselect';
+import { createQueryParametersSelector } from 'controllers/pages';
+import { getAlternativePaginationAndSortParams, PAGE_KEY, SIZE_KEY } from 'controllers/pagination';
+import { SORTING_ASC } from 'controllers/sorting';
import { organizationsSelector } from 'controllers/instance/organizations/selectors';
+import { SORTING_KEY } from './projects';
+import { DEFAULT_PAGINATION } from './projects/constants';
export const organizationSelector = (state) => organizationsSelector(state).organization || {};
@@ -26,3 +31,21 @@ export const activeOrganizationLoadingSelector = (state) =>
export const activeOrganizationNameSelector = (state) => activeOrganizationSelector(state)?.name;
export const activeOrganizationIdSelector = (state) => activeOrganizationSelector(state)?.id;
+
+export const createParametersSelector = ({ defaultPagination, defaultSorting, sortingKey } = {}) =>
+ createSelector(
+ createQueryParametersSelector({
+ defaultPagination,
+ defaultSorting,
+ sortingKey,
+ }),
+ ({ [SIZE_KEY]: limit, [SORTING_KEY]: sort, [PAGE_KEY]: pageNumber, ...rest }) => {
+ return { ...getAlternativePaginationAndSortParams(sort, limit, pageNumber), ...rest };
+ },
+ );
+
+export const querySelector = createParametersSelector({
+ defaultPagination: DEFAULT_PAGINATION,
+ defaultDirection: SORTING_ASC,
+ sortingKey: SORTING_KEY,
+});
diff --git a/app/src/controllers/organization/users/actionCreators.js b/app/src/controllers/organization/users/actionCreators.js
new file mode 100644
index 0000000000..7cd1914e82
--- /dev/null
+++ b/app/src/controllers/organization/users/actionCreators.js
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2024 EPAM Systems
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { FETCH_ORGANIZATION_USERS, PREPARE_ACTIVE_ORGANIZATION_USERS } from './constants';
+
+export const prepareActiveOrganizationUsersAction = (payload) => ({
+ type: PREPARE_ACTIVE_ORGANIZATION_USERS,
+ payload,
+});
+
+export const fetchOrganizationUsersAction = (params) => {
+ return {
+ type: FETCH_ORGANIZATION_USERS,
+ payload: params,
+ };
+};
diff --git a/app/src/controllers/organization/users/constants.js b/app/src/controllers/organization/users/constants.js
new file mode 100644
index 0000000000..0a688fd9dd
--- /dev/null
+++ b/app/src/controllers/organization/users/constants.js
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2024 EPAM Systems
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export const FETCH_ORGANIZATION_USERS = 'fetchOrganizationUsers';
+export const PREPARE_ACTIVE_ORGANIZATION_USERS = 'prepareActiveOrganizationUsers';
+export const NAMESPACE = 'organizationUsers';
diff --git a/app/src/controllers/organization/users/index.js b/app/src/controllers/organization/users/index.js
new file mode 100644
index 0000000000..18b3dd219b
--- /dev/null
+++ b/app/src/controllers/organization/users/index.js
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2024 EPAM Systems
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export { NAMESPACE, FETCH_ORGANIZATION_USERS } from './constants';
+export {
+ prepareActiveOrganizationUsersAction,
+ fetchOrganizationUsersAction,
+} from './actionCreators';
+export { usersPaginationSelector, usersSelector, loadingSelector } from './selectors';
+export { usersSagas } from './sagas';
diff --git a/app/src/controllers/organization/users/reducer.js b/app/src/controllers/organization/users/reducer.js
new file mode 100644
index 0000000000..ce7daa57bf
--- /dev/null
+++ b/app/src/controllers/organization/users/reducer.js
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2024 EPAM Systems
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { combineReducers } from 'redux';
+import { fetchReducer } from 'controllers/fetch';
+import { alternativePaginationReducer } from 'controllers/pagination';
+import { loadingReducer } from 'controllers/loading';
+import { createPageScopedReducer } from 'common/utils/createPageScopedReducer';
+import { ORGANIZATION_USERS_PAGE } from 'controllers/pages/constants';
+import { NAMESPACE } from './constants';
+import { initialPaginationState } from '../projects/constants';
+
+export const usersFetchReducer = fetchReducer(NAMESPACE, {
+ contentPath: 'items',
+ initialState: [],
+});
+
+export const reducer = combineReducers({
+ pagination: alternativePaginationReducer(NAMESPACE, initialPaginationState),
+ loading: loadingReducer(NAMESPACE),
+ users: usersFetchReducer,
+});
+
+export const usersReducer = createPageScopedReducer(reducer, ORGANIZATION_USERS_PAGE);
diff --git a/app/src/controllers/organization/users/sagas.js b/app/src/controllers/organization/users/sagas.js
new file mode 100644
index 0000000000..f3d8d4d534
--- /dev/null
+++ b/app/src/controllers/organization/users/sagas.js
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2024 EPAM Systems
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { createFetchPredicate, fetchDataAction } from 'controllers/fetch';
+import { URLS } from 'common/urls';
+import { all, put, select, take, takeEvery } from 'redux-saga/effects';
+import { activeOrganizationSelector, querySelector } from '../selectors';
+import { FETCH_ORGANIZATION_BY_SLUG } from '../constants';
+import { fetchOrganizationUsersAction } from './actionCreators';
+import {
+ FETCH_ORGANIZATION_USERS,
+ NAMESPACE,
+ PREPARE_ACTIVE_ORGANIZATION_USERS,
+} from './constants';
+
+function* fetchOrganizationUsers({ payload: organizationId }) {
+ const query = yield select(querySelector);
+
+ yield put(fetchDataAction(NAMESPACE)(URLS.organizationUsers(organizationId, { ...query })));
+}
+
+function* watchFetchUsers() {
+ yield takeEvery(FETCH_ORGANIZATION_USERS, fetchOrganizationUsers);
+}
+
+function* prepareActiveOrganizationUsers({ payload: { organizationSlug } }) {
+ let activeOrganization = yield select(activeOrganizationSelector);
+ try {
+ if (!activeOrganization || organizationSlug !== activeOrganization?.slug) {
+ yield take(createFetchPredicate(FETCH_ORGANIZATION_BY_SLUG));
+ activeOrganization = yield select(activeOrganizationSelector);
+ }
+ yield put(fetchOrganizationUsersAction(activeOrganization.id));
+ } catch (error) {
+ throw new Error(error);
+ }
+}
+
+function* watchFetchOrganizationUsers() {
+ yield takeEvery(PREPARE_ACTIVE_ORGANIZATION_USERS, prepareActiveOrganizationUsers);
+}
+
+export function* usersSagas() {
+ yield all([watchFetchUsers(), watchFetchOrganizationUsers()]);
+}
diff --git a/app/src/controllers/organization/users/selectors.js b/app/src/controllers/organization/users/selectors.js
new file mode 100644
index 0000000000..f7df8911fe
--- /dev/null
+++ b/app/src/controllers/organization/users/selectors.js
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2024 EPAM Systems
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { organizationSelector } from '../selectors';
+
+const domainSelector = (state) => organizationSelector(state).users || {};
+
+export const usersPaginationSelector = (state) => domainSelector(state).pagination;
+export const usersSelector = (state) => domainSelector(state).users;
+export const loadingSelector = (state) => domainSelector(state).loading || false;
diff --git a/app/src/controllers/pages/constants.js b/app/src/controllers/pages/constants.js
index 845842861d..11b8daf1ce 100644
--- a/app/src/controllers/pages/constants.js
+++ b/app/src/controllers/pages/constants.js
@@ -32,7 +32,7 @@ export const PLUGIN_UI_EXTENSION_ADMIN_PAGE = 'PLUGIN_UI_EXTENSION_ADMIN_PAGE';
export const API_PAGE = 'API_PAGE';
export const ORGANIZATIONS_PAGE = 'ORGANIZATIONS_PAGE';
export const ORGANIZATION_PROJECTS_PAGE = 'ORGANIZATION_PROJECTS_PAGE';
-export const ORGANIZATION_MEMBERS_PAGE = 'ORGANIZATION_MEMBERS_PAGE';
+export const ORGANIZATION_USERS_PAGE = 'ORGANIZATION_USERS_PAGE';
export const ORGANIZATION_SETTINGS_PAGE = 'ORGANIZATION_SETTINGS_PAGE';
export const PROJECT_PAGE = 'PROJECT_PAGE';
export const PROJECT_DASHBOARD_PAGE = 'PROJECT_DASHBOARD_PAGE';
@@ -72,6 +72,7 @@ export const pageNames = {
[NOT_FOUND]: NOT_FOUND,
ORGANIZATIONS_PAGE,
ORGANIZATION_PROJECTS_PAGE,
+ ORGANIZATION_USERS_PAGE,
ALL_USERS_PAGE,
SERVER_SETTINGS_PAGE,
SERVER_SETTINGS_TAB_PAGE,
diff --git a/app/src/controllers/pages/index.js b/app/src/controllers/pages/index.js
index a9540b4d42..10c6924627 100644
--- a/app/src/controllers/pages/index.js
+++ b/app/src/controllers/pages/index.js
@@ -92,7 +92,7 @@ export {
PROJECT_PLUGIN_PAGE,
ORGANIZATIONS_PAGE,
ORGANIZATION_PROJECTS_PAGE,
- ORGANIZATION_MEMBERS_PAGE,
+ ORGANIZATION_USERS_PAGE,
ORGANIZATION_SETTINGS_PAGE,
} from './constants';
export { NOT_FOUND } from 'redux-first-router';
diff --git a/app/src/layouts/organizationLayout/organizationSidebar/organizationSidebar.jsx b/app/src/layouts/organizationLayout/organizationSidebar/organizationSidebar.jsx
index 00c82d7075..81152fc421 100644
--- a/app/src/layouts/organizationLayout/organizationSidebar/organizationSidebar.jsx
+++ b/app/src/layouts/organizationLayout/organizationSidebar/organizationSidebar.jsx
@@ -23,7 +23,7 @@ import { useIntl } from 'react-intl';
import { canSeeMembers } from 'common/utils/permissions';
import {
ORGANIZATION_PROJECTS_PAGE,
- ORGANIZATION_MEMBERS_PAGE,
+ ORGANIZATION_USERS_PAGE,
ORGANIZATION_SETTINGS_PAGE,
USER_PROFILE_PAGE_ORGANIZATION_LEVEL,
ORGANIZATIONS_PAGE,
@@ -74,7 +74,7 @@ export const OrganizationSidebar = ({ onClickNavBtn }) => {
onClick: (isSidebarCollapsed) =>
onClickButton({ itemName: messages.users.defaultMessage, isSidebarCollapsed }),
link: {
- type: ORGANIZATION_MEMBERS_PAGE,
+ type: ORGANIZATION_USERS_PAGE,
payload: { organizationSlug },
},
icon: MembersIcon,
diff --git a/app/src/pages/inside/common/statusDropdown/statusDropdown.scss b/app/src/pages/inside/common/statusDropdown/statusDropdown.scss
index 6e7c8f08b9..a0c121a536 100644
--- a/app/src/pages/inside/common/statusDropdown/statusDropdown.scss
+++ b/app/src/pages/inside/common/statusDropdown/statusDropdown.scss
@@ -68,4 +68,4 @@
.defined-status {
padding: 0 16px;
-}
\ No newline at end of file
+}
diff --git a/app/src/pages/inside/uniqueErrorsPage/uniqueErrorsGrid/clusterItemsGridRow/clusterItemsGridRow.scss b/app/src/pages/inside/uniqueErrorsPage/uniqueErrorsGrid/clusterItemsGridRow/clusterItemsGridRow.scss
index 2393135995..27640a8260 100644
--- a/app/src/pages/inside/uniqueErrorsPage/uniqueErrorsGrid/clusterItemsGridRow/clusterItemsGridRow.scss
+++ b/app/src/pages/inside/uniqueErrorsPage/uniqueErrorsGrid/clusterItemsGridRow/clusterItemsGridRow.scss
@@ -36,7 +36,7 @@
}
&.extension-col {
- width: 165px;
+ width: 165px;
}
}
diff --git a/app/src/pages/inside/uniqueErrorsPage/uniqueErrorsGrid/uniqueErrorsGrid.scss b/app/src/pages/inside/uniqueErrorsPage/uniqueErrorsGrid/uniqueErrorsGrid.scss
index 3a41f6aa38..98fd576166 100644
--- a/app/src/pages/inside/uniqueErrorsPage/uniqueErrorsGrid/uniqueErrorsGrid.scss
+++ b/app/src/pages/inside/uniqueErrorsPage/uniqueErrorsGrid/uniqueErrorsGrid.scss
@@ -22,7 +22,7 @@
padding-left: 37px;
}
-.matched-header{
+.matched-header {
width: 130px;
}
diff --git a/app/src/pages/instance/organizationsPage/organizationsPage.scss b/app/src/pages/instance/organizationsPage/organizationsPage.scss
index 2c8dbc2c44..774adb7b35 100644
--- a/app/src/pages/instance/organizationsPage/organizationsPage.scss
+++ b/app/src/pages/instance/organizationsPage/organizationsPage.scss
@@ -14,7 +14,7 @@
* limitations under the License.
*/
- .organizations-page {
+.organizations-page {
display: flex;
flex-direction: column;
min-height: 100%;
diff --git a/app/src/pages/instance/projectEventsPage/eventsGrid/eventsGrid.scss b/app/src/pages/instance/projectEventsPage/eventsGrid/eventsGrid.scss
index 7af8f8ac6d..074145bbf6 100644
--- a/app/src/pages/instance/projectEventsPage/eventsGrid/eventsGrid.scss
+++ b/app/src/pages/instance/projectEventsPage/eventsGrid/eventsGrid.scss
@@ -74,7 +74,7 @@
.events-grid-row {
@media (max-width: $SCREEN_SM_MAX) {
display: table-row;
- > :nth-last-child(-n+2) {
+ > :nth-last-child(-n + 2) {
max-width: 0;
display: none;
}
diff --git a/app/src/pages/organization/common/membersPage/emptyMembersPageState/emptyMembersPageState.jsx b/app/src/pages/organization/common/membersPage/emptyMembersPageState/emptyMembersPageState.jsx
new file mode 100644
index 0000000000..23825e4cd7
--- /dev/null
+++ b/app/src/pages/organization/common/membersPage/emptyMembersPageState/emptyMembersPageState.jsx
@@ -0,0 +1,40 @@
+import { useIntl } from 'react-intl';
+import { BubblesLoader } from '@reportportal/ui-kit';
+import PropTypes from 'prop-types';
+import classNames from 'classnames/bind';
+import { EmptyPageState } from 'pages/common/emptyPageState';
+import { messages } from '../membersPageHeader/messages';
+import EmptyIcon from './img/empty-members-icon-inline.svg';
+import styles from './emptyMembersPageState.scss';
+
+const cx = classNames.bind(styles);
+
+export const EmptyMembersPageState = ({ isLoading, hasPermission, showInviteUserModal }) => {
+ const { formatMessage } = useIntl();
+ return isLoading ? (
+
+
+
+ ) : (
+
+ );
+};
+
+EmptyMembersPageState.propTypes = {
+ isLoading: PropTypes.bool,
+ hasPermission: PropTypes.bool,
+ showInviteUserModal: PropTypes.func,
+};
+
+EmptyMembersPageState.defaultProps = {
+ isLoading: false,
+ hasPermission: false,
+ showInviteUserModal: () => {},
+};
diff --git a/app/src/pages/organization/common/membersPage/emptyMembersPageState/emptyMembersPageState.scss b/app/src/pages/organization/common/membersPage/emptyMembersPageState/emptyMembersPageState.scss
new file mode 100644
index 0000000000..c33c8fb561
--- /dev/null
+++ b/app/src/pages/organization/common/membersPage/emptyMembersPageState/emptyMembersPageState.scss
@@ -0,0 +1,7 @@
+.loader {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 100%;
+ flex: 1;
+}
diff --git a/app/src/pages/organization/projectTeamPage/img/empty-members-icon-inline.svg b/app/src/pages/organization/common/membersPage/emptyMembersPageState/img/empty-members-icon-inline.svg
similarity index 100%
rename from app/src/pages/organization/projectTeamPage/img/empty-members-icon-inline.svg
rename to app/src/pages/organization/common/membersPage/emptyMembersPageState/img/empty-members-icon-inline.svg
diff --git a/app/src/pages/organization/common/membersPage/emptyMembersPageState/index.js b/app/src/pages/organization/common/membersPage/emptyMembersPageState/index.js
new file mode 100644
index 0000000000..21692e2098
--- /dev/null
+++ b/app/src/pages/organization/common/membersPage/emptyMembersPageState/index.js
@@ -0,0 +1,17 @@
+/*!
+ * Copyright 2024 EPAM Systems
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export { EmptyMembersPageState } from './emptyMembersPageState';
diff --git a/app/src/pages/organization/common/membersPage/membersListTable/index.js b/app/src/pages/organization/common/membersPage/membersListTable/index.js
new file mode 100644
index 0000000000..416b8d5229
--- /dev/null
+++ b/app/src/pages/organization/common/membersPage/membersListTable/index.js
@@ -0,0 +1,17 @@
+/*!
+ * Copyright 2024 EPAM Systems
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export { MembersListTable } from './membersListTable';
diff --git a/app/src/pages/organization/common/membersPage/membersListTable/membersListTable.jsx b/app/src/pages/organization/common/membersPage/membersListTable/membersListTable.jsx
new file mode 100644
index 0000000000..7370f3447d
--- /dev/null
+++ b/app/src/pages/organization/common/membersPage/membersListTable/membersListTable.jsx
@@ -0,0 +1,81 @@
+/*!
+ * Copyright 2024 EPAM Systems
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import classNames from 'classnames/bind';
+import PropTypes from 'prop-types';
+import { Table } from '@reportportal/ui-kit';
+import { DEFAULT_PAGE_SIZE_OPTIONS } from 'controllers/members/constants';
+import { PaginationWrapper } from 'components/main/paginationWrapper';
+import styles from './membersListTable.scss';
+
+const cx = classNames.bind(styles);
+
+export const MembersListTable = ({
+ data,
+ primaryColumn,
+ fixedColumns,
+ onTableSorting,
+ showPagination,
+ rowActionMenu,
+ sortingDirection,
+ pageSize,
+ activePage,
+ itemCount,
+ pageCount,
+ onChangePage,
+ onChangePageSize,
+}) => {
+ return (
+
+
+
+ );
+};
+
+MembersListTable.propTypes = {
+ data: PropTypes.array.isRequired,
+ primaryColumn: PropTypes.object.isRequired,
+ fixedColumns: PropTypes.array.isRequired,
+ onTableSorting: PropTypes.func.isRequired,
+ showPagination: PropTypes.bool.isRequired,
+ rowActionMenu: PropTypes.node,
+ sortingDirection: PropTypes.string.isRequired,
+ pageSize: PropTypes.number.isRequired,
+ activePage: PropTypes.number.isRequired,
+ itemCount: PropTypes.number.isRequired,
+ pageCount: PropTypes.number.isRequired,
+ onChangePage: PropTypes.func.isRequired,
+ onChangePageSize: PropTypes.func.isRequired,
+};
diff --git a/app/src/pages/organization/common/membersPage/membersListTable/membersListTable.scss b/app/src/pages/organization/common/membersPage/membersListTable/membersListTable.scss
new file mode 100644
index 0000000000..676f59a1ec
--- /dev/null
+++ b/app/src/pages/organization/common/membersPage/membersListTable/membersListTable.scss
@@ -0,0 +1,22 @@
+/*!
+ * Copyright 2024 EPAM Systems
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+.members-list-table {
+ margin-top: 24px;
+ padding: 0 32px 32px 32px;
+ max-width: 1264px;
+ box-sizing: border-box;
+}
diff --git a/app/src/pages/organization/common/membersPage/membersPageHeader/index.js b/app/src/pages/organization/common/membersPage/membersPageHeader/index.js
new file mode 100644
index 0000000000..ae997aaaa5
--- /dev/null
+++ b/app/src/pages/organization/common/membersPage/membersPageHeader/index.js
@@ -0,0 +1,17 @@
+/*!
+ * Copyright 2024 EPAM Systems
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export { MembersPageHeader } from './membersPageHeader';
diff --git a/app/src/pages/organization/common/membersPage/membersPageHeader/membersPageHeader.jsx b/app/src/pages/organization/common/membersPage/membersPageHeader/membersPageHeader.jsx
new file mode 100644
index 0000000000..61b598ec47
--- /dev/null
+++ b/app/src/pages/organization/common/membersPage/membersPageHeader/membersPageHeader.jsx
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2024 EPAM Systems
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import PropTypes from 'prop-types';
+import classNames from 'classnames/bind';
+import styles from './membersPageHeader.scss';
+
+const cx = classNames.bind(styles);
+
+export const MembersPageHeader = ({ title, children }) => {
+ return (
+
+
+ {title}
+ {children}
+
+
+ );
+};
+
+MembersPageHeader.propTypes = {
+ title: PropTypes.string.isRequired,
+ children: PropTypes.node,
+};
+
+MembersPageHeader.defaultProps = {
+ children: null,
+};
diff --git a/app/src/pages/organization/common/membersPage/membersPageHeader/membersPageHeader.scss b/app/src/pages/organization/common/membersPage/membersPageHeader/membersPageHeader.scss
new file mode 100644
index 0000000000..0ab16a39b4
--- /dev/null
+++ b/app/src/pages/organization/common/membersPage/membersPageHeader/membersPageHeader.scss
@@ -0,0 +1,42 @@
+/*!
+ * Copyright 2024 EPAM Systems
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+.members-page-header-container {
+ padding: 48px 32px 16px 32px;
+ border-bottom: 1px solid $COLOR--e-100;
+ background: $COLOR--bg-000;
+ box-sizing: border-box;
+ position: sticky;
+ top: 0;
+ z-index: 2;
+}
+
+.header {
+ display: flex;
+ min-height: 31px;
+ justify-content: space-between;
+}
+
+.title {
+ font-family: $FONT-REGULAR;
+ font-size: 20px;
+ line-height: 31px;
+ color: $COLOR--almost-black;
+ text-transform: capitalize;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
diff --git a/app/src/pages/organization/projectTeamPage/messages.js b/app/src/pages/organization/common/membersPage/membersPageHeader/messages.js
similarity index 88%
rename from app/src/pages/organization/projectTeamPage/messages.js
rename to app/src/pages/organization/common/membersPage/membersPageHeader/messages.js
index c6fb7c4840..386bf2fa28 100644
--- a/app/src/pages/organization/projectTeamPage/messages.js
+++ b/app/src/pages/organization/common/membersPage/membersPageHeader/messages.js
@@ -17,7 +17,11 @@
import { defineMessages } from 'react-intl';
export const messages = defineMessages({
- title: {
+ organizationUsersTitle: {
+ id: 'OrganizationUsers.organizationUsersTitle',
+ defaultMessage: 'Organization users',
+ },
+ projectTeamTitle: {
id: 'ProjectTeamPage.title',
defaultMessage: 'Project team',
},
diff --git a/app/src/pages/organization/projectTeamPage/projectTeamListTable/messages.js b/app/src/pages/organization/common/membersPage/messages.js
similarity index 86%
rename from app/src/pages/organization/projectTeamPage/projectTeamListTable/messages.js
rename to app/src/pages/organization/common/membersPage/messages.js
index 6dee0dea73..608d0e4677 100644
--- a/app/src/pages/organization/projectTeamPage/projectTeamListTable/messages.js
+++ b/app/src/pages/organization/common/membersPage/messages.js
@@ -33,4 +33,12 @@ export const messages = defineMessages({
id: 'MembersListTable.permissions',
defaultMessage: 'Permissions',
},
+ role: {
+ id: 'MembersListTable.role',
+ defaultMessage: 'Role',
+ },
+ projects: {
+ id: 'MembersListTable.projects',
+ defaultMessage: 'Projects',
+ },
});
diff --git a/app/src/pages/organization/organizationProjectsPage/projectsListTable/projectsListTable.scss b/app/src/pages/organization/organizationProjectsPage/projectsListTable/projectsListTable.scss
index 05a57970b4..7bd4d241f4 100644
--- a/app/src/pages/organization/organizationProjectsPage/projectsListTable/projectsListTable.scss
+++ b/app/src/pages/organization/organizationProjectsPage/projectsListTable/projectsListTable.scss
@@ -66,6 +66,6 @@
}
}
-.loader{
+.loader {
margin: auto;
}
diff --git a/app/src/pages/organization/organizationProjectsPage/projectsPageHeader/projectsPageHeader.scss b/app/src/pages/organization/organizationProjectsPage/projectsPageHeader/projectsPageHeader.scss
index a905189575..617f2e7646 100644
--- a/app/src/pages/organization/organizationProjectsPage/projectsPageHeader/projectsPageHeader.scss
+++ b/app/src/pages/organization/organizationProjectsPage/projectsPageHeader/projectsPageHeader.scss
@@ -14,7 +14,7 @@
* limitations under the License.
*/
- .projects-page-header-container {
+.projects-page-header-container {
padding: 16px 32px;
border-bottom: 1px solid $COLOR--e-100;
background: $COLOR--bg-000;
@@ -100,7 +100,8 @@
}
}
- .details-item-icon, svg {
+ .details-item-icon,
+ svg {
width: 16px;
height: 16px;
}
@@ -108,4 +109,4 @@
.search-input {
overflow: hidden;
-}
\ No newline at end of file
+}
diff --git a/app/src/pages/organization/organizationUsersPage/index.js b/app/src/pages/organization/organizationUsersPage/index.js
new file mode 100644
index 0000000000..102ff7d3b9
--- /dev/null
+++ b/app/src/pages/organization/organizationUsersPage/index.js
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2024 EPAM Systems
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export { OrganizationUsersPage } from './organizationUsersPage';
diff --git a/app/src/pages/organization/organizationUsersPage/organizationUsersListTable/index.js b/app/src/pages/organization/organizationUsersPage/organizationUsersListTable/index.js
new file mode 100644
index 0000000000..46d3945561
--- /dev/null
+++ b/app/src/pages/organization/organizationUsersPage/organizationUsersListTable/index.js
@@ -0,0 +1,17 @@
+/*!
+ * Copyright 2024 EPAM Systems
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export { OrganizationTeamListTable } from './organizationUsersListTable';
diff --git a/app/src/pages/organization/organizationUsersPage/organizationUsersListTable/organizationUsersListTable.jsx b/app/src/pages/organization/organizationUsersPage/organizationUsersListTable/organizationUsersListTable.jsx
new file mode 100644
index 0000000000..2b2525ec32
--- /dev/null
+++ b/app/src/pages/organization/organizationUsersPage/organizationUsersListTable/organizationUsersListTable.jsx
@@ -0,0 +1,205 @@
+/*!
+ * Copyright 2024 EPAM Systems
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import classNames from 'classnames/bind';
+import PropTypes from 'prop-types';
+import { FormattedMessage, useIntl } from 'react-intl';
+import { useMemo } from 'react';
+import { useDispatch, useSelector } from 'react-redux';
+import { AbsRelTime } from 'components/main/absRelTime';
+import { MeatballMenuIcon, Popover } from '@reportportal/ui-kit';
+import { urlOrganizationAndProjectSelector } from 'controllers/pages';
+import { SORTING_ASC, withSortingURL } from 'controllers/sorting';
+import { DEFAULT_SORT_COLUMN } from 'controllers/members/constants';
+import {
+ DEFAULT_PAGE_SIZE,
+ DEFAULT_PAGINATION,
+ PAGE_KEY,
+ withPagination,
+} from 'controllers/pagination';
+import {
+ prepareActiveOrganizationUsersAction,
+ usersPaginationSelector,
+} from 'controllers/organization/users';
+import { SORTING_KEY } from 'controllers/organization/projects';
+import { ADMINISTRATOR } from 'common/constants/accountRoles';
+import { MembersListTable } from '../../common/membersPage/membersListTable';
+import { messages } from '../../common/membersPage/messages';
+import styles from './organizationUsersListTable.scss';
+
+const cx = classNames.bind(styles);
+
+const OrgTeamListTableWrapped = ({
+ users,
+ onChangeSorting,
+ sortingDirection,
+ pageSize,
+ activePage,
+ itemCount,
+ pageCount,
+ onChangePage,
+ onChangePageSize,
+}) => {
+ const { formatMessage } = useIntl();
+ const dispatch = useDispatch();
+ const { organizationSlug, projectSlug } = useSelector(urlOrganizationAndProjectSelector);
+ const showPagination = users.length > 0;
+ const data = useMemo(
+ () =>
+ users.map(
+ ({
+ id,
+ email,
+ full_name: fullName,
+ relationships,
+ instance_role: instanceRole,
+ last_login_at: lastLogin,
+ org_role: orgRole,
+ }) => {
+ const projectsCount = relationships.projects.meta.count;
+ return {
+ id,
+ fullName: {
+ content: fullName,
+ component: (
+
+
+ }
+ >
+
+
+
+
+ );
+
+ const onTableSorting = ({ key }) => {
+ onChangeSorting(key);
+ dispatch(prepareActiveOrganizationUsersAction());
+ };
+
+ return (
+
+ );
+};
+
+OrgTeamListTableWrapped.propTypes = {
+ users: PropTypes.array,
+ sortingDirection: PropTypes.string,
+ onChangeSorting: PropTypes.func,
+ pageSize: PropTypes.number,
+ activePage: PropTypes.number,
+ itemCount: PropTypes.number.isRequired,
+ pageCount: PropTypes.number.isRequired,
+ onChangePage: PropTypes.func.isRequired,
+ onChangePageSize: PropTypes.func.isRequired,
+};
+
+OrgTeamListTableWrapped.defaultProps = {
+ users: [],
+ pageSize: DEFAULT_PAGE_SIZE,
+ activePage: DEFAULT_PAGINATION[PAGE_KEY],
+};
+
+export const OrganizationTeamListTable = withSortingURL({
+ defaultFields: [DEFAULT_SORT_COLUMN],
+ defaultDirection: SORTING_ASC,
+ sortingKey: SORTING_KEY,
+})(
+ withPagination({
+ paginationSelector: usersPaginationSelector,
+ })(OrgTeamListTableWrapped),
+);
diff --git a/app/src/pages/organization/organizationUsersPage/organizationUsersListTable/organizationUsersListTable.scss b/app/src/pages/organization/organizationUsersPage/organizationUsersListTable/organizationUsersListTable.scss
new file mode 100644
index 0000000000..b3d523bd01
--- /dev/null
+++ b/app/src/pages/organization/organizationUsersPage/organizationUsersListTable/organizationUsersListTable.scss
@@ -0,0 +1,61 @@
+/*!
+ * Copyright 2024 EPAM Systems
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+.row-action-menu {
+ display: flex;
+ flex-direction: column;
+
+ p {
+ padding: 7px 16px;
+ align-items: center;
+ font-size: 13px;
+ line-height: 20px;
+ width: 120px;
+ color: $COLOR--almost-black;
+ }
+}
+
+.menu-icon {
+ svg {
+ margin-bottom: 3px;
+ }
+}
+
+.admin-badge {
+ display: inline-flex;
+ justify-content: center;
+ align-items: center;
+ padding: 2px 8px;
+ border-radius: 6px;
+ background-color: $COLOR--tag-value-text;
+ color: $COLOR--white-two;
+ font-family: $FONT-ROBOTO-BOLD;
+ font-size: 11px;
+ line-height: 16px;
+ margin-right: 16px;
+}
+
+.member-name-column {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+}
+
+.date {
+ font-family: inherit;
+ color: inherit;
+ min-height: auto;
+}
diff --git a/app/src/pages/organization/organizationUsersPage/organizationUsersPage.jsx b/app/src/pages/organization/organizationUsersPage/organizationUsersPage.jsx
new file mode 100644
index 0000000000..5e8f000809
--- /dev/null
+++ b/app/src/pages/organization/organizationUsersPage/organizationUsersPage.jsx
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2024 EPAM Systems
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { useSelector } from 'react-redux';
+import { loadingSelector, usersSelector } from 'controllers/organization/users';
+import { OrganizationTeamListTable } from 'pages/organization/organizationUsersPage/organizationUsersListTable/organizationUsersListTable';
+import { ScrollWrapper } from 'components/main/scrollWrapper';
+import classNames from 'classnames/bind';
+import styles from './organizationUsersPage.scss';
+import { EmptyMembersPageState as EmptyUsersPageState } from '../common/membersPage/emptyMembersPageState';
+import { OrganizationUsersPageHeader } from './organizationUsersPageHeader';
+
+const cx = classNames.bind(styles);
+
+export const OrganizationUsersPage = () => {
+ const users = useSelector(usersSelector);
+ const isUsersLoading = useSelector(loadingSelector);
+ const isEmptyUsers = users.length === 0;
+
+ return (
+
+
+
+ {isEmptyUsers ? (
+
+ ) : (
+
+ )}
+
+
+ );
+};
diff --git a/app/src/pages/organization/organizationUsersPage/organizationUsersPage.scss b/app/src/pages/organization/organizationUsersPage/organizationUsersPage.scss
new file mode 100644
index 0000000000..eea90a59cf
--- /dev/null
+++ b/app/src/pages/organization/organizationUsersPage/organizationUsersPage.scss
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2024 EPAM Systems
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+.organization-users-page {
+ display: flex;
+ flex-direction: column;
+ min-height: calc(100% - 48px);
+}
diff --git a/app/src/pages/organization/organizationUsersPage/organizationUsersPageHeader/index.js b/app/src/pages/organization/organizationUsersPage/organizationUsersPageHeader/index.js
new file mode 100644
index 0000000000..c4ced6e20f
--- /dev/null
+++ b/app/src/pages/organization/organizationUsersPage/organizationUsersPageHeader/index.js
@@ -0,0 +1,17 @@
+/*!
+ * Copyright 2024 EPAM Systems
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export { OrganizationUsersPageHeader } from './organizationUsersPageHeader';
diff --git a/app/src/pages/organization/organizationUsersPage/organizationUsersPageHeader/organizationUsersPageHeader.jsx b/app/src/pages/organization/organizationUsersPage/organizationUsersPageHeader/organizationUsersPageHeader.jsx
new file mode 100644
index 0000000000..de1084197f
--- /dev/null
+++ b/app/src/pages/organization/organizationUsersPage/organizationUsersPageHeader/organizationUsersPageHeader.jsx
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2024 EPAM Systems
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import PropTypes from 'prop-types';
+import classNames from 'classnames/bind';
+import { Button, SearchIcon } from '@reportportal/ui-kit';
+import { useIntl } from 'react-intl';
+import { messages } from '../../common/membersPage/membersPageHeader/messages';
+import styles from './organizationUsersPageHeader.scss';
+import { MembersPageHeader } from '../../common/membersPage/membersPageHeader';
+
+const cx = classNames.bind(styles);
+
+export const OrganizationUsersPageHeader = ({ isNotEmpty, onInvite }) => {
+ const { formatMessage } = useIntl();
+
+ return (
+
+
+ {isNotEmpty && (
+ <>
+
+
+
+
+
+
+ >
+ )}
+
+
+ );
+};
+
+OrganizationUsersPageHeader.propTypes = {
+ isNotEmpty: PropTypes.bool,
+ onInvite: PropTypes.func,
+};
+
+OrganizationUsersPageHeader.defaultProps = {
+ isNotEmpty: false,
+ onInvite: () => {},
+};
diff --git a/app/src/pages/organization/organizationUsersPage/organizationUsersPageHeader/organizationUsersPageHeader.scss b/app/src/pages/organization/organizationUsersPage/organizationUsersPageHeader/organizationUsersPageHeader.scss
new file mode 100644
index 0000000000..162e23ef69
--- /dev/null
+++ b/app/src/pages/organization/organizationUsersPage/organizationUsersPageHeader/organizationUsersPageHeader.scss
@@ -0,0 +1,45 @@
+/*!
+ * Copyright 2024 EPAM Systems
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+.actions {
+ display: flex;
+ align-items: center;
+ gap: 32px;
+
+ .icons {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ i {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ &.search-icon {
+ width: 40px;
+ height: 36px;
+
+ svg,
+ svg * {
+ width: 18px;
+ height: 18px;
+ fill: $COLOR--e-300;
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/pages/organization/projectTeamPage/projectTeamListTable/projectTeamListTable.jsx b/app/src/pages/organization/projectTeamPage/projectTeamListTable/projectTeamListTable.jsx
index 8a44b6db40..0456eb8c6f 100644
--- a/app/src/pages/organization/projectTeamPage/projectTeamListTable/projectTeamListTable.jsx
+++ b/app/src/pages/organization/projectTeamPage/projectTeamListTable/projectTeamListTable.jsx
@@ -21,11 +21,11 @@ import { useMemo } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { activeProjectKeySelector } from 'controllers/user';
import { AbsRelTime } from 'components/main/absRelTime';
-import { MeatballMenuIcon, Popover, Table } from '@reportportal/ui-kit';
+import { MeatballMenuIcon, Popover } from '@reportportal/ui-kit';
import { UserAvatar } from 'pages/inside/common/userAvatar';
import { urlOrganizationAndProjectSelector, userRolesSelector } from 'controllers/pages';
import { SORTING_ASC, withSortingURL } from 'controllers/sorting';
-import { DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_SORT_COLUMN } from 'controllers/members/constants';
+import { DEFAULT_SORT_COLUMN } from 'controllers/members/constants';
import { fetchMembersAction, membersPaginationSelector } from 'controllers/members';
import { canSeeEmailMembers, getRoleTitle } from 'common/utils/permissions';
import { canSeeRowActionMenu } from 'common/utils/permissions/permissions';
@@ -35,9 +35,9 @@ import {
PAGE_KEY,
withPagination,
} from 'controllers/pagination';
-import { PaginationWrapper } from 'components/main/paginationWrapper';
-import { messages } from './messages';
+import { messages } from '../../common/membersPage/messages';
import styles from './projectTeamListTable.scss';
+import { MembersListTable } from '../../common/membersPage/membersListTable';
const cx = classNames.bind(styles);
@@ -158,28 +158,21 @@ const ProjectTeamListTableWrapped = ({
};
return (
- 0}
+ rowActionMenu={canSeeRowActionMenu(userRoles) ? rowActionMenu : null}
+ sortingDirection={sortingDirection}
pageSize={pageSize}
activePage={activePage}
- totalItems={itemCount}
- totalPages={pageCount}
- pageSizeOptions={DEFAULT_PAGE_SIZE_OPTIONS}
- changePage={onChangePage}
- changePageSize={onChangePageSize}
- >
-