diff --git a/services/web/app/src/Features/UserMembership/UserMembershipController.js b/services/web/app/src/Features/UserMembership/UserMembershipController.js index 7d1d45f1d6..a75ae2c371 100644 --- a/services/web/app/src/Features/UserMembership/UserMembershipController.js +++ b/services/web/app/src/Features/UserMembership/UserMembershipController.js @@ -17,9 +17,15 @@ const EmailHelper = require('../Helpers/EmailHelper') const { csvAttachment } = require('../../infrastructure/Response') const { UserIsManagerError } = require('./UserMembershipErrors') const CSVParser = require('json2csv').Parser +const Settings = require('@overleaf/settings') + +function isManagedUsersActiveOnGroup(entity) { + return !!(Settings.managedUsers?.enabled && entity.groupPolicy) +} async function manageGroupMembers(req, res, next) { const { entity, entityConfig } = req + const managedUsersActive = isManagedUsersActiveOnGroup(entity) return entity.fetchV1Data(function (error, entity) { if (error != null) { return next(error) @@ -42,6 +48,7 @@ async function manageGroupMembers(req, res, next) { groupId: entityPrimaryKey, users, groupSize: entity.membersLimit, + managedUsersActive, }) } ) diff --git a/services/web/app/src/Features/UserMembership/UserMembershipHandler.js b/services/web/app/src/Features/UserMembership/UserMembershipHandler.js index 14070029e0..a9665632d0 100644 --- a/services/web/app/src/Features/UserMembership/UserMembershipHandler.js +++ b/services/web/app/src/Features/UserMembership/UserMembershipHandler.js @@ -106,7 +106,21 @@ function getPopulatedListOfMembers(entity, attributes, callback) { } } - async.map(userObjects, UserMembershipViewModel.buildAsync, callback) + async.map(userObjects, UserMembershipViewModel.buildAsync, (err, users) => { + if (err) { + return callback(err) + } + for (const user of users) { + if ( + user?._id && + entity?.admin_id && + user._id.toString() === entity.admin_id.toString() + ) { + user.isEntityAdmin = true + } + } + callback(null, users) + }) } function addUserToEntity(entity, attribute, user, callback) { diff --git a/services/web/app/src/Features/UserMembership/UserMembershipViewModel.js b/services/web/app/src/Features/UserMembership/UserMembershipViewModel.js index 3b43578797..550f666312 100644 --- a/services/web/app/src/Features/UserMembership/UserMembershipViewModel.js +++ b/services/web/app/src/Features/UserMembership/UserMembershipViewModel.js @@ -39,6 +39,7 @@ module.exports = UserMembershipViewModel = { last_name: 1, lastLoggedIn: 1, lastActive: 1, + enrollment: 1, } return UserGetter.getUser(userId, projection, function (error, user) { if (error != null || user == null) { @@ -61,6 +62,12 @@ function buildUserViewModel(user, isInvite) { last_active_at: user.lastActive || user.lastLoggedIn || null, last_logged_in_at: user.lastLoggedIn || null, invite: isInvite, + enrollment: user.enrollment + ? { + managedBy: user.enrollment.managedBy, + enrolledAt: user.enrollment.enrolledAt, + } + : undefined, } } diff --git a/services/web/app/views/user_membership/group-members-react.pug b/services/web/app/views/user_membership/group-members-react.pug index 264b8a7222..5760c0b432 100644 --- a/services/web/app/views/user_membership/group-members-react.pug +++ b/services/web/app/views/user_membership/group-members-react.pug @@ -8,6 +8,7 @@ block append meta meta(name="ol-groupId", data-type="string", content=groupId) meta(name="ol-groupName", data-type="string", content=name) meta(name="ol-groupSize", data-type="json", content=groupSize) + meta(name="ol-managedUsersActive", data-type="boolean", content=managedUsersActive) block content main.content.content-alt#subscription-manage-group-root diff --git a/services/web/config/settings.defaults.js b/services/web/config/settings.defaults.js index ed8ab31729..37dfa0bedf 100644 --- a/services/web/config/settings.defaults.js +++ b/services/web/config/settings.defaults.js @@ -826,6 +826,10 @@ module.exports = { // ID of the IEEE brand in the rails app ieeeBrandId: intFromEnv('IEEE_BRAND_ID', 15), + + managedUsers: { + enabled: false, + }, } module.exports.mergeWith = function (overrides) { diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index b1f04b0a01..560fbb5dd7 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -198,6 +198,7 @@ "delete_projects": "", "delete_tag": "", "delete_token": "", + "delete_user": "", "delete_your_account": "", "deleted_at": "", "deleted_by_on": "", @@ -395,6 +396,7 @@ "go_to_code_location_in_pdf": "", "go_to_pdf_location_in_code": "", "go_to_settings": "", + "group_admin": "", "group_plan_tooltip": "", "group_plan_with_name_tooltip": "", "group_subscription": "", @@ -577,6 +579,7 @@ "manage_publisher_managers": "", "manage_sessions": "", "manage_subscription": "", + "managed": "", "managed_users": "", "managed_users_explanation": "", "managers_management": "", @@ -624,6 +627,7 @@ "new_to_latex_look_at": "", "newsletter": "", "next_payment_of_x_collectected_on_y": "", + "no_actions": "", "no_comments": "", "no_existing_password": "", "no_folder": "", @@ -646,6 +650,7 @@ "normal": "", "normally_x_price_per_month": "", "normally_x_price_per_year": "", + "not_managed": "", "notification_project_invite_accepted_message": "", "notification_project_invite_message": "", "number_of_users": "", @@ -689,6 +694,7 @@ "pdf_viewer": "", "pdf_viewer_error": "", "pending_additional_licenses": "", + "pending_invite": "", "percent_discount_for_groups": "", "plan": "", "plan_tooltip": "", @@ -848,6 +854,7 @@ "search_search_for": "", "search_whole_word": "", "search_within_selection": "", + "security": "", "select_a_file": "", "select_a_file_figure_modal": "", "select_a_payment_method": "", diff --git a/services/web/frontend/js/features/group-management/components/group-members-list.tsx b/services/web/frontend/js/features/group-management/components/group-members-list.tsx new file mode 100644 index 0000000000..55cf6b2e1a --- /dev/null +++ b/services/web/frontend/js/features/group-management/components/group-members-list.tsx @@ -0,0 +1,82 @@ +import { Col, Row } from 'react-bootstrap' +import { useTranslation } from 'react-i18next' +import { User } from '../../../../../types/group-management/user' +import Tooltip from '../../../shared/components/tooltip' +import GroupMemberRow from './group-member-row' + +type GroupMembersListProps = { + handleSelectAllClick: (e: any) => void + selectedUsers: User[] + users: User[] + selectUser: (user: User) => void + unselectUser: (user: User) => void +} + +export default function GroupMembersList({ + handleSelectAllClick, + selectedUsers, + users, + selectUser, + unselectUser, +}: GroupMembersListProps) { + const { t } = useTranslation() + return ( +
{t('add_more_members')}