From cf262f09dbfc27d789513260e69a758136c3c6eb Mon Sep 17 00:00:00 2001
From: llj <lingjun.li1@gmail.com>
Date: Tue, 8 Apr 2025 13:47:28 +0800
Subject: [PATCH] =?UTF-8?q?[group]=20added=20full=20operation=20menus=20fo?=
 =?UTF-8?q?r=20all=20the=20department/group=20items=E2=80=A6=20(#7706)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* [group] added full operation menus for all the department/group items in 'Files' page; fixed & improved all the operations

* remove useless state

* fix test lib

---------

Co-authored-by: Michael An <1822852997@qq.com>
---
 .github/workflows/test.yml                    |   1 +
 frontend/src/app.js                           |  15 +-
 .../components/dialog/dismiss-group-dialog.js |  25 +-
 .../components/dialog/group-members-dialog.js |   2 +-
 .../dialog/import-members-dialog.js           |   7 +-
 .../components/dialog/leave-group-dialog.js   |  21 +-
 .../dialog/manage-members-dialog.js           |   2 +-
 .../components/dialog/rename-group-dialog.js  |  54 ++--
 .../dialog/transfer-group-dialog.js           |  35 +-
 frontend/src/components/group-members.js      |   4 +-
 .../components/list-and-add-group-members.js  |   2 +-
 frontend/src/pages/groups/group-item.js       |  84 ++---
 frontend/src/pages/groups/group-op-menu.js    | 273 ++++++++++++++++
 frontend/src/pages/groups/group-view.js       | 300 +++---------------
 frontend/src/pages/libraries/index.js         |  38 ++-
 15 files changed, 443 insertions(+), 420 deletions(-)
 create mode 100644 frontend/src/pages/groups/group-op-menu.js

diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 35b2111be6..a1966b762d 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -25,6 +25,7 @@ jobs:
           sudo apt-get install -y libfuse-dev cmake re2c flex sqlite3
           sudo apt-get install -y libssl-dev libsasl2-dev libldap2-dev libonig-dev
           sudo apt-get install -y libxml2 libxml2-dev libjwt-dev
+          sudo apt-get install -y libhiredis-dev
 
       - name: clone and build
         run: |
diff --git a/frontend/src/app.js b/frontend/src/app.js
index 3ae3ee9f2b..dcd28cb392 100644
--- a/frontend/src/app.js
+++ b/frontend/src/app.js
@@ -157,19 +157,6 @@ class App extends Component {
     }
   };
 
-  onGroupChanged = (groupID) => {
-    setTimeout(function () {
-      let url;
-      if (groupID) {
-        url = siteRoot + 'group/' + groupID + '/';
-      }
-      else {
-        url = siteRoot + 'libraries/';
-      }
-      window.location = url.toString();
-    }, 1);
-  };
-
   tabItemClick = (tabName, groupID) => {
     let pathPrefix = [];
     if (groupID || this.dirViewPanels.indexOf(tabName) > -1) {
@@ -343,7 +330,7 @@ class App extends Component {
               <InvitationsView path={siteRoot + 'invitations/'} />
               <FilesActivities path={siteRoot + 'dashboard'} />
               <MyFileActivities path={siteRoot + 'my-activities'} />
-              <GroupView path={siteRoot + 'group/:groupID'} onGroupChanged={this.onGroupChanged} />
+              <GroupView path={siteRoot + 'group/:groupID'} />
               <LinkedDevices path={siteRoot + 'linked-devices'} />
               <ShareAdminLibraries path={siteRoot + 'share-admin-libs'} />
               <ShareAdminFolders path={siteRoot + 'share-admin-folders'} />
diff --git a/frontend/src/components/dialog/dismiss-group-dialog.js b/frontend/src/components/dialog/dismiss-group-dialog.js
index cb8825172f..fe26b696fa 100644
--- a/frontend/src/components/dialog/dismiss-group-dialog.js
+++ b/frontend/src/components/dialog/dismiss-group-dialog.js
@@ -9,14 +9,11 @@ import SeahubModalHeader from '@/components/common/seahub-modal-header';
 
 class DismissGroupDialog extends React.Component {
 
-  constructor(props) {
-    super(props);
-  }
-
   dismissGroup = () => {
-    let that = this;
-    seafileAPI.deleteGroup(this.props.groupID).then((res) => {
-      that.props.onGroupChanged();
+    const { groupID } = this.props;
+    seafileAPI.deleteGroup(groupID).then((res) => {
+      this.props.onGroupDeleted();
+      toaster.success(gettext('Group deleted'));
     }).catch(error => {
       let errMessage = Utils.getErrorMsg(error);
       toaster.danger(errMessage);
@@ -25,13 +22,13 @@ class DismissGroupDialog extends React.Component {
 
   render() {
     return (
-      <Modal isOpen={this.props.showDismissGroupDialog} toggle={this.props.toggleDismissGroupDialog}>
-        <SeahubModalHeader>{gettext('Delete Group')}</SeahubModalHeader>
+      <Modal isOpen={true} toggle={this.props.toggleDialog}>
+        <SeahubModalHeader toggle={this.props.toggleDialog}>{gettext('Delete Group')}</SeahubModalHeader>
         <ModalBody>
           <span>{gettext('Really want to delete this group?')}</span>
         </ModalBody>
         <ModalFooter>
-          <Button color="secondary" onClick={this.props.toggleDismissGroupDialog}>{gettext('Cancel')}</Button>
+          <Button color="secondary" onClick={this.props.toggleDialog}>{gettext('Cancel')}</Button>
           <Button color="primary" onClick={this.dismissGroup}>{gettext('Delete')}</Button>
         </ModalFooter>
       </Modal>
@@ -40,11 +37,9 @@ class DismissGroupDialog extends React.Component {
 }
 
 const DismissGroupDialogPropTypes = {
-  showDismissGroupDialog: PropTypes.bool.isRequired,
-  toggleDismissGroupDialog: PropTypes.func.isRequired,
-  loadGroup: PropTypes.func.isRequired,
-  groupID: PropTypes.string,
-  onGroupChanged: PropTypes.func.isRequired,
+  groupID: PropTypes.number.isRequired,
+  toggleDialog: PropTypes.func.isRequired,
+  onGroupDeleted: PropTypes.func.isRequired
 };
 
 DismissGroupDialog.propTypes = DismissGroupDialogPropTypes;
diff --git a/frontend/src/components/dialog/group-members-dialog.js b/frontend/src/components/dialog/group-members-dialog.js
index 0d845e8d6d..535177c915 100644
--- a/frontend/src/components/dialog/group-members-dialog.js
+++ b/frontend/src/components/dialog/group-members-dialog.js
@@ -9,7 +9,7 @@ import SeahubModalHeader from '@/components/common/seahub-modal-header';
 import Loading from '../loading';
 
 const propTypes = {
-  groupID: PropTypes.string.isRequired,
+  groupID: PropTypes.number.isRequired,
   toggleDialog: PropTypes.func.isRequired
 };
 
diff --git a/frontend/src/components/dialog/import-members-dialog.js b/frontend/src/components/dialog/import-members-dialog.js
index 7547d3f02e..abeb08cacb 100644
--- a/frontend/src/components/dialog/import-members-dialog.js
+++ b/frontend/src/components/dialog/import-members-dialog.js
@@ -5,7 +5,7 @@ import { gettext, siteRoot, groupImportMembersExtraMsg } from '../../utils/const
 import SeahubModalHeader from '@/components/common/seahub-modal-header';
 
 const propTypes = {
-  toggleImportMembersDialog: PropTypes.func.isRequired,
+  toggleDialog: PropTypes.func.isRequired,
   importMembersInBatch: PropTypes.func.isRequired,
 };
 
@@ -19,7 +19,7 @@ class ImportMembersDialog extends React.Component {
   }
 
   toggle = () => {
-    this.props.toggleImportMembersDialog();
+    this.props.toggleDialog();
   };
 
   openFileInput = () => {
@@ -49,9 +49,8 @@ class ImportMembersDialog extends React.Component {
     return (
       <Modal isOpen={true} toggle={this.toggle}>
         <SeahubModalHeader toggle={this.toggle}>{gettext('Import members from a .xlsx file')}</SeahubModalHeader>
-
         <ModalBody>
-          <p>{groupImportMembersExtraMsg}</p>
+          {groupImportMembersExtraMsg && <p>{groupImportMembersExtraMsg}</p>}
           <p><a className="text-secondary small" href={`${siteRoot}api/v2.1/group-members-import-example/`}>{gettext('Download an example file')}</a></p>
           <button className="btn btn-outline-primary" onClick={this.openFileInput}>{gettext('Upload file')}</button>
           <input className="d-none" type="file" onChange={this.uploadFile} ref={this.fileInputRef} />
diff --git a/frontend/src/components/dialog/leave-group-dialog.js b/frontend/src/components/dialog/leave-group-dialog.js
index 1f30156cec..a80d4e939a 100644
--- a/frontend/src/components/dialog/leave-group-dialog.js
+++ b/frontend/src/components/dialog/leave-group-dialog.js
@@ -9,13 +9,10 @@ import SeahubModalHeader from '@/components/common/seahub-modal-header';
 
 class LeaveGroupDialog extends React.Component {
 
-  constructor(props) {
-    super(props);
-  }
-
   leaveGroup = () => {
-    seafileAPI.quitGroup(this.props.groupID, username).then((res) => {
-      this.props.onGroupChanged();
+    const { groupID } = this.props;
+    seafileAPI.quitGroup(groupID, username).then((res) => {
+      this.props.onLeavingGroup();
     }).catch(error => {
       let errMessage = Utils.getErrorMsg(error);
       toaster.danger(errMessage);
@@ -24,13 +21,13 @@ class LeaveGroupDialog extends React.Component {
 
   render() {
     return (
-      <Modal isOpen={true} toggle={this.props.toggleLeaveGroupDialog}>
-        <SeahubModalHeader toggle={this.props.toggleLeaveGroupDialog}>{gettext('Leave Group')}</SeahubModalHeader>
+      <Modal isOpen={true} toggle={this.props.toggleDialog}>
+        <SeahubModalHeader toggle={this.props.toggleDialog}>{gettext('Leave Group')}</SeahubModalHeader>
         <ModalBody>
           <p>{gettext('Really want to leave this group?')}</p>
         </ModalBody>
         <ModalFooter>
-          <Button color="secondary" onClick={this.props.toggleLeaveGroupDialog}>{gettext('Cancel')}</Button>
+          <Button color="secondary" onClick={this.props.toggleDialog}>{gettext('Cancel')}</Button>
           <Button color="primary" onClick={this.leaveGroup}>{gettext('Leave')}</Button>
         </ModalFooter>
       </Modal>
@@ -39,9 +36,9 @@ class LeaveGroupDialog extends React.Component {
 }
 
 const LeaveGroupDialogPropTypes = {
-  toggleLeaveGroupDialog: PropTypes.func.isRequired,
-  groupID: PropTypes.string,
-  onGroupChanged: PropTypes.func.isRequired,
+  groupID: PropTypes.number.isRequired,
+  onLeavingGroup: PropTypes.func.isRequired,
+  toggleDialog: PropTypes.func.isRequired
 };
 
 LeaveGroupDialog.propTypes = LeaveGroupDialogPropTypes;
diff --git a/frontend/src/components/dialog/manage-members-dialog.js b/frontend/src/components/dialog/manage-members-dialog.js
index cea7a01631..8b3b4c7dbc 100644
--- a/frontend/src/components/dialog/manage-members-dialog.js
+++ b/frontend/src/components/dialog/manage-members-dialog.js
@@ -8,7 +8,7 @@ import SeahubModalHeader from '@/components/common/seahub-modal-header';
 import '../../css/manage-members-dialog.css';
 
 const propTypes = {
-  groupID: PropTypes.string,
+  groupID: PropTypes.number.isRequired,
   isOwner: PropTypes.bool.isRequired,
   toggleManageMembersDialog: PropTypes.func,
   toggleDepartmentDetailDialog: PropTypes.func,
diff --git a/frontend/src/components/dialog/rename-group-dialog.js b/frontend/src/components/dialog/rename-group-dialog.js
index 7c3af16a38..1c564f89d1 100644
--- a/frontend/src/components/dialog/rename-group-dialog.js
+++ b/frontend/src/components/dialog/rename-group-dialog.js
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
 import { gettext } from '../../utils/constants';
 import { seafileAPI } from '../../utils/seafile-api';
 import { Utils } from '../../utils/utils';
-import { Modal, ModalBody, ModalFooter, Input, Button } from 'reactstrap';
+import { Modal, ModalBody, ModalFooter, Input, Label, Button } from 'reactstrap';
 import SeahubModalHeader from '@/components/common/seahub-modal-header';
 import toaster from '../toast';
 
@@ -12,7 +12,7 @@ class RenameGroupDialog extends React.Component {
   constructor(props) {
     super(props);
     this.state = {
-      newGroupName: this.props.currentGroupName,
+      newGroupName: this.props.groupName,
       isSubmitBtnActive: false,
     };
   }
@@ -30,48 +30,42 @@ class RenameGroupDialog extends React.Component {
     });
   };
 
-  renameGroup = () => {
-    let name = this.state.newGroupName.trim();
-    if (name) {
-      let that = this;
-      seafileAPI.renameGroup(this.props.groupID, name).then((res) => {
-        that.props.loadGroup(this.props.groupID);
-        that.props.onGroupChanged(res.data.id);
-      }).catch(error => {
-        let errMessage = Utils.getErrorMsg(error);
-        toaster.danger(errMessage);
-      });
-    }
-    this.setState({
-      newGroupName: '',
+  handleSubmit = () => {
+    const { groupID } = this.props;
+    const { newGroupName } = this.state;
+    seafileAPI.renameGroup(groupID, newGroupName.trim()).then((res) => {
+      const { name } = res.data;
+      this.props.onGroupNameChanged(name);
+    }).catch(error => {
+      let errMessage = Utils.getErrorMsg(error);
+      toaster.danger(errMessage);
     });
-    this.props.toggleRenameGroupDialog();
+    this.props.toggleDialog();
   };
 
   handleKeyDown = (event) => {
     if (event.keyCode === 13) {
-      this.renameGroup();
+      this.handleSubmit();
     }
   };
 
   render() {
     return (
-      <Modal isOpen={this.props.showRenameGroupDialog} toggle={this.props.toggleRenameGroupDialog}>
-        <SeahubModalHeader>{gettext('Rename Group')}</SeahubModalHeader>
+      <Modal isOpen={true} toggle={this.props.toggleDialog}>
+        <SeahubModalHeader toggle={this.props.toggleDialog}>{gettext('Rename Group')}</SeahubModalHeader>
         <ModalBody>
-          <label htmlFor="newGroupName">{gettext('Rename group to')}</label>
+          <Label for="group-name">{gettext('Rename group to')}</Label>
           <Input
             type="text"
-            id="newGroupName"
-            name="new-group-name"
+            id="group-name"
             value={this.state.newGroupName}
             onChange={this.handleGroupNameChange}
             onKeyDown={this.handleKeyDown}
           />
         </ModalBody>
         <ModalFooter>
-          <Button color="secondary" onClick={this.props.toggleRenameGroupDialog}>{gettext('Cancel')}</Button>
-          <Button color="primary" onClick={this.renameGroup} disabled={!this.state.isSubmitBtnActive}>{gettext('Submit')}</Button>
+          <Button color="secondary" onClick={this.props.toggleDialog}>{gettext('Cancel')}</Button>
+          <Button color="primary" onClick={this.handleSubmit} disabled={!this.state.isSubmitBtnActive}>{gettext('Submit')}</Button>
         </ModalFooter>
       </Modal>
     );
@@ -79,12 +73,10 @@ class RenameGroupDialog extends React.Component {
 }
 
 const RenameGroupDialogPropTypes = {
-  showRenameGroupDialog: PropTypes.bool.isRequired,
-  toggleRenameGroupDialog: PropTypes.func.isRequired,
-  loadGroup: PropTypes.func.isRequired,
-  groupID: PropTypes.string,
-  onGroupChanged: PropTypes.func.isRequired,
-  currentGroupName: PropTypes.string.isRequired,
+  toggleDialog: PropTypes.func.isRequired,
+  groupID: PropTypes.number,
+  onGroupNameChanged: PropTypes.func.isRequired,
+  groupName: PropTypes.string.isRequired,
 };
 
 RenameGroupDialog.propTypes = RenameGroupDialogPropTypes;
diff --git a/frontend/src/components/dialog/transfer-group-dialog.js b/frontend/src/components/dialog/transfer-group-dialog.js
index c36540cd3f..7a6bae824b 100644
--- a/frontend/src/components/dialog/transfer-group-dialog.js
+++ b/frontend/src/components/dialog/transfer-group-dialog.js
@@ -11,9 +11,9 @@ import toaster from '../toast';
 import '../../css/transfer-group-dialog.css';
 
 const propTypes = {
-  groupID: PropTypes.string,
-  toggleTransferGroupDialog: PropTypes.func.isRequired,
-  onGroupChanged: PropTypes.func.isRequired
+  groupID: PropTypes.number.isRequired,
+  onGroupTransfered: PropTypes.func.isRequired,
+  toggleDialog: PropTypes.func.isRequired
 };
 
 class TransferGroupDialog extends React.Component {
@@ -21,18 +21,14 @@ class TransferGroupDialog extends React.Component {
   constructor(props) {
     super(props);
     this.state = {
-      selectedOption: null,
-      errMessage: '',
+      selectedOption: null
     };
-    this.options = [];
   }
 
   handleSelectChange = (option) => {
     this.setState({
-      selectedOption: option,
-      errMessage: '',
+      selectedOption: option
     });
-    this.options = [];
   };
 
   transferGroup = () => {
@@ -41,19 +37,21 @@ class TransferGroupDialog extends React.Component {
     if (selectedOption && selectedOption[0]) {
       email = selectedOption[0].email;
     }
-    if (email) {
-      seafileAPI.transferGroup(this.props.groupID, email).then((res) => {
-        this.props.toggleTransferGroupDialog();
-        toaster.success(gettext('Group has been transfered'));
-      }).catch((error) => {
-        let errMessage = Utils.getErrorMsg(error);
-        this.setState({ errMessage: errMessage });
-      });
+    if (!email) {
+      return false;
     }
+    seafileAPI.transferGroup(this.props.groupID, email).then((res) => {
+      toaster.success(gettext('Group has been transfered'));
+      this.props.onGroupTransfered(res.data);
+      this.props.toggleDialog();
+    }).catch((error) => {
+      let errMessage = Utils.getErrorMsg(error);
+      toaster.danger(errMessage);
+    });
   };
 
   toggle = () => {
-    this.props.toggleTransferGroupDialog();
+    this.props.toggleDialog();
   };
 
   render() {
@@ -68,7 +66,6 @@ class TransferGroupDialog extends React.Component {
             placeholder={gettext('Please enter 1 or more character')}
             onSelectChange={this.handleSelectChange}
           />
-          <div className="error">{this.state.errMessage}</div>
         </ModalBody>
         <ModalFooter>
           <Button color="secondary" onClick={this.toggle}>{gettext('Close')}</Button>
diff --git a/frontend/src/components/group-members.js b/frontend/src/components/group-members.js
index 3f15bb6e1f..2bda3f81ac 100644
--- a/frontend/src/components/group-members.js
+++ b/frontend/src/components/group-members.js
@@ -10,7 +10,7 @@ import OpIcon from './op-icon';
 
 const propTypes = {
   groupMembers: PropTypes.array.isRequired,
-  groupID: PropTypes.string,
+  groupID: PropTypes.number.isRequired,
   isOwner: PropTypes.bool.isRequired,
   isItemFreezed: PropTypes.bool.isRequired,
   toggleItemFreezed: PropTypes.func.isRequired,
@@ -61,7 +61,7 @@ const MemberPropTypes = {
   changeMember: PropTypes.func.isRequired,
   deleteMember: PropTypes.func.isRequired,
   toggleItemFreezed: PropTypes.func.isRequired,
-  groupID: PropTypes.string,
+  groupID: PropTypes.number.isRequired,
   isOwner: PropTypes.bool.isRequired,
   isItemFreezed: PropTypes.bool.isRequired
 };
diff --git a/frontend/src/components/list-and-add-group-members.js b/frontend/src/components/list-and-add-group-members.js
index af0f2e1bcf..d12900890b 100644
--- a/frontend/src/components/list-and-add-group-members.js
+++ b/frontend/src/components/list-and-add-group-members.js
@@ -12,7 +12,7 @@ import GroupMembers from './group-members';
 const propTypes = {
   toggleManageMembersDialog: PropTypes.func,
   toggleDepartmentDetailDialog: PropTypes.func,
-  groupID: PropTypes.string,
+  groupID: PropTypes.number.isRequired,
   isOwner: PropTypes.bool.isRequired
 };
 
diff --git a/frontend/src/pages/groups/group-item.js b/frontend/src/pages/groups/group-item.js
index 09e2079968..f35e3571a1 100644
--- a/frontend/src/pages/groups/group-item.js
+++ b/frontend/src/pages/groups/group-item.js
@@ -1,14 +1,12 @@
 import React from 'react';
 import PropTypes from 'prop-types';
-import { gettext, siteRoot, username } from '../../utils/constants';
+import { gettext, siteRoot } from '../../utils/constants';
 import { seafileAPI } from '../../utils/seafile-api';
 import { Utils } from '../../utils/utils';
 import toaster from '../../components/toast';
 import SharedRepoListView from '../../components/shared-repo-list-view/shared-repo-list-view';
-import SingleDropdownToolbar from '../../components/toolbar/single-dropdown-toolbar';
-import CreateRepoDialog from '../../components/dialog/create-repo-dialog';
-import Repo from '../../models/repo';
 import { LIST_MODE } from '../../components/dir-view-mode/constants';
+import GroupOperationMenu from './group-op-menu';
 
 const propTypes = {
   inAllLibs: PropTypes.bool,
@@ -17,21 +15,18 @@ const propTypes = {
   onMonitorRepo: PropTypes.func,
   renameRelatedGroupsRepos: PropTypes.func,
   deleteRelatedGroupsRepos: PropTypes.func,
-  insertRepoIntoGroup: PropTypes.func,
+  addRepoToGroup: PropTypes.func,
   unshareRepoToGroup: PropTypes.func,
   onTransferRepo: PropTypes.func.isRequired,
+  onGroupNameChanged: PropTypes.func.isRequired,
+  onGroupTransfered: PropTypes.func.isRequired,
+  onGroupDeleted: PropTypes.func.isRequired,
+  onLeavingGroup: PropTypes.func.isRequired
 };
 
 
 class GroupItem extends React.Component {
 
-  constructor(props) {
-    super(props);
-    this.state = {
-      isCreateRepoDialogOpen: false
-    };
-  }
-
   onItemUnshare = (repo) => {
     const { group } = this.props;
     const { id: group_id } = group;
@@ -61,53 +56,45 @@ class GroupItem extends React.Component {
     this.props.onMonitorRepo(repo, monitored);
   };
 
-  toggleCreateRepoDialog = () => {
-    this.setState({
-      isCreateRepoDialogOpen: !this.state.isCreateRepoDialogOpen
-    });
-  };
-
-  onCreateRepo = (repo) => {
+  addNewRepo = (newRepo) => {
     const { group } = this.props;
     const { id: group_id } = group;
-    seafileAPI.createGroupOwnedLibrary(group_id, repo).then(res => {
-      let object = {
-        repo_id: res.data.id,
-        repo_name: res.data.name,
-        owner_name: res.data.group_name,
-        owner_email: res.data.owner,
-        permission: res.data.permission,
-        mtime: res.data.mtime,
-        size: res.data.size,
-        encrypted: res.data.encrypted,
-      };
-      const newRepo = new Repo(object);
-      this.props.insertRepoIntoGroup({ repo: newRepo, group_id });
-    }).catch(error => {
-      let errMessage = Utils.getErrorMsg(error);
-      toaster.danger(errMessage);
-    });
-    this.toggleCreateRepoDialog();
+    this.props.addRepoToGroup({ repo: newRepo, group_id });
+  };
+
+  onGroupNameChanged = (newName) => {
+    const { group } = this.props;
+    this.props.onGroupNameChanged(newName, group.id);
+  };
+
+  onGroupDeleted = () => {
+    const { group } = this.props;
+    this.props.onGroupDeleted(group.id);
+  };
+
+  onLeavingGroup = () => {
+    const { group } = this.props;
+    this.props.onLeavingGroup(group.id);
   };
 
   render() {
     const { inAllLibs = false, group, currentViewMode = LIST_MODE } = this.props;
-    const { parent_group_id, admins } = group;
     const emptyTip = <p className={`libraries-empty-tip-in-${currentViewMode}-mode`}>{gettext('No libraries')}</p>;
 
-    const isDeptAdmin = parent_group_id != 0 && admins.indexOf(username) > -1;
     return (
       <div className="pb-3">
         <div className={`d-flex justify-content-between mt-3 py-1 ${currentViewMode == LIST_MODE ? 'sf-border-bottom' : ''}`}>
           <h4 className="sf-heading m-0 d-flex align-items-center">
             <span className={`${group.parent_group_id == 0 ? 'sf3-font-group' : 'sf3-font-department'} sf3-font nav-icon`} aria-hidden="true"></span>
             <a href={`${siteRoot}group/${group.id}/`} title={group.name} className="ellipsis">{group.name}</a>
-            {isDeptAdmin &&
-              <SingleDropdownToolbar
-                withPlusIcon={true}
-                opList={[{ 'text': gettext('New Library'), 'onClick': this.toggleCreateRepoDialog }]}
-              />
-            }
+            <GroupOperationMenu
+              group={group}
+              addNewRepo={this.addNewRepo}
+              onGroupNameChanged={this.onGroupNameChanged}
+              onGroupTransfered={this.props.onGroupTransfered}
+              onGroupDeleted={this.onGroupDeleted}
+              onLeavingGroup={this.onLeavingGroup}
+            />
           </h4>
         </div>
         {group.repos.length === 0 ?
@@ -127,13 +114,6 @@ class GroupItem extends React.Component {
             currentViewMode={currentViewMode}
           />
         }
-        {this.state.isCreateRepoDialogOpen &&
-        <CreateRepoDialog
-          onCreateToggle={this.toggleCreateRepoDialog}
-          onCreateRepo={this.onCreateRepo}
-          libraryType='department'
-        />
-        }
       </div>
     );
   }
diff --git a/frontend/src/pages/groups/group-op-menu.js b/frontend/src/pages/groups/group-op-menu.js
new file mode 100644
index 0000000000..b387d39b91
--- /dev/null
+++ b/frontend/src/pages/groups/group-op-menu.js
@@ -0,0 +1,273 @@
+import React, { Fragment } from 'react';
+import PropTypes from 'prop-types';
+import { gettext, username, canAddRepo } from '../../utils/constants';
+import { seafileAPI } from '../../utils/seafile-api';
+import { Utils } from '../../utils/utils';
+import toaster from '../../components/toast';
+import { Repo } from '../../models';
+import CreateRepoDialog from '../../components/dialog/create-repo-dialog';
+import GroupMembersDialog from '../../components/dialog/group-members-dialog';
+import DismissGroupDialog from '../../components/dialog/dismiss-group-dialog';
+import RenameGroupDialog from '../../components/dialog/rename-group-dialog';
+import TransferGroupDialog from '../../components/dialog/transfer-group-dialog';
+import ImportMembersDialog from '../../components/dialog/import-members-dialog';
+import ManageMembersDialog from '../../components/dialog/manage-members-dialog';
+import DepartmentDetailDialog from '../../components/dialog/department-detail-dialog';
+import LeaveGroupDialog from '../../components/dialog/leave-group-dialog';
+import SingleDropdownToolbar from '../../components/toolbar/single-dropdown-toolbar';
+
+import '../../css/group-view.css';
+
+const propTypes = {
+  group: PropTypes.object.isRequired,
+  addNewRepo: PropTypes.func.isRequired,
+  onGroupNameChanged: PropTypes.func.isRequired,
+  onGroupTransfered: PropTypes.func.isRequired,
+  onGroupDeleted: PropTypes.func.isRequired,
+  onLeavingGroup: PropTypes.func.isRequired
+};
+
+class GroupOperationMenu extends React.Component {
+
+  constructor(props) {
+    super(props);
+    this.state = {
+      isCreateRepoDialogOpen: false,
+      isShowDepartmentDetailDialog: false,
+      isRenameGroupDialogOpen: false,
+      isDeleteGroupDialogOpen: false,
+      isTransferGroupDialogOpen: false,
+      isImportMembersDialogOpen: false,
+      isManageMembersDialogOpen: false,
+      isLeaveGroupDialogOpen: false,
+      isMembersDialogOpen: false
+    };
+  }
+
+  onCreateRepoToggle = () => {
+    this.setState({ isCreateRepoDialogOpen: !this.state.isCreateRepoDialogOpen });
+  };
+
+  onCreateRepo = (repo, groupType) => {
+    const { group } = this.props;
+    const groupId = group.id;
+    if (groupType && groupType === 'department') {
+      seafileAPI.createGroupOwnedLibrary(groupId, repo).then(res => {
+        let object = {
+          repo_id: res.data.id,
+          repo_name: res.data.name,
+          owner_name: res.data.group_name,
+          owner_email: res.data.owner,
+          permission: res.data.permission,
+          mtime: res.data.mtime,
+          size: res.data.size,
+          encrypted: res.data.encrypted,
+        };
+        const repo = new Repo(object);
+        this.props.addNewRepo(repo);
+      }).catch(error => {
+        let errMessage = Utils.getErrorMsg(error);
+        toaster.danger(errMessage);
+      });
+
+    } else {
+      seafileAPI.createGroupRepo(groupId, repo).then(res => {
+        const repo = new Repo(res.data);
+        this.props.addNewRepo(repo);
+      }).catch(error => {
+        let errMessage = Utils.getErrorMsg(error);
+        toaster.danger(errMessage);
+      });
+    }
+    this.onCreateRepoToggle();
+  };
+
+  toggleDeleteGroupDialog = () => {
+    this.setState({
+      isDeleteGroupDialogOpen: !this.state.isDeleteGroupDialogOpen,
+    });
+  };
+
+  toggleRenameGroupDialog = () => {
+    this.setState({
+      isRenameGroupDialogOpen: !this.state.isRenameGroupDialogOpen,
+    });
+  };
+
+  toggleTransferGroupDialog = () => {
+    this.setState({
+      isTransferGroupDialogOpen: !this.state.isTransferGroupDialogOpen,
+    });
+  };
+
+  toggleImportMembersDialog = () => {
+    this.setState({
+      isImportMembersDialogOpen: !this.state.isImportMembersDialogOpen
+    });
+  };
+
+  importMembersInBatch = (file) => {
+    toaster.notify(gettext('It may take some time, please wait.'), { 'id': 'importing-members' });
+    const { group } = this.props;
+    seafileAPI.importGroupMembersViaFile(group.id, file).then((res) => {
+      res.data.success.forEach(item => {
+        toaster.success(gettext('Successfully imported {user_placeholder}').replace('{user_placeholder}', `${item.contact_email}`), { 'id': 'importing-members' });
+      });
+      res.data.failed.forEach(item => {
+        toaster.danger(`${item.email}: ${item.error_msg}`, { 'id': 'importing-members' });
+      });
+    }).catch((error) => {
+      let errMsg = Utils.getErrorMsg(error);
+      toaster.danger(errMsg);
+    });
+  };
+
+  toggleManageMembersDialog = () => {
+    this.setState({
+      isManageMembersDialogOpen: !this.state.isManageMembersDialogOpen,
+    });
+  };
+
+  toggleLeaveGroupDialog = () => {
+    this.setState({
+      isLeaveGroupDialogOpen: !this.state.isLeaveGroupDialogOpen,
+    });
+  };
+
+  toggleMembersDialog = () => {
+    this.setState({
+      isMembersDialogOpen: !this.state.isMembersDialogOpen
+    });
+  };
+
+  toggleDepartmentDetailDialog = () => {
+    this.setState({
+      isShowDepartmentDetailDialog: !this.state.isShowDepartmentDetailDialog
+    });
+  };
+
+  getOpList = () => {
+    const { group } = this.props;
+    const isDepartment = group.parent_group_id !== 0;
+    const isStaff = group.admins.indexOf(username) > -1;
+    const isOwner = group.owner === username;
+    const opList = [];
+    if ((!isDepartment && canAddRepo) ||
+      (isDepartment && isStaff)) {
+      this.newLibraryEnabled = true;
+      opList.push({ 'text': gettext('New Library'), 'onClick': this.onCreateRepoToggle }, 'Divider');
+    }
+    opList.push({ 'text': gettext('Members'), 'onClick': this.toggleMembersDialog });
+    if (isStaff || isOwner) {
+      opList.push({ 'text': gettext('Import members'), 'onClick': this.toggleImportMembersDialog });
+      opList.push({ 'text': gettext('Manage members'), 'onClick': this.toggleManageMembersDialog });
+      opList.push('Divider');
+      opList.push({ 'text': gettext('Rename'), 'onClick': this.toggleRenameGroupDialog });
+      if (isOwner) {
+        opList.push({ 'text': gettext('Transfer'), 'onClick': this.toggleTransferGroupDialog });
+      }
+      if (isOwner) {
+        opList.push({ 'text': gettext('Delete group'), 'onClick': this.toggleDeleteGroupDialog });
+      }
+    }
+
+    if (!isOwner && !isDepartment) {
+      opList.push({ 'text': gettext('Leave group'), 'onClick': this.toggleLeaveGroupDialog });
+    }
+
+    return opList;
+  };
+
+
+  render() {
+    const {
+      isCreateRepoDialogOpen,
+      isMembersDialogOpen
+    } = this.state;
+    const { group } = this.props;
+    const { id: groupID, parent_group_id, owner } = group;
+    const isDepartment = parent_group_id !== 0;
+    const isOwner = owner === username;
+
+    const opList = this.getOpList();
+    return (
+      <Fragment>
+        {group && (
+          <SingleDropdownToolbar
+            withPlusIcon={this.newLibraryEnabled}
+            opList={opList}
+          />
+        )}
+        {isCreateRepoDialogOpen &&
+          <CreateRepoDialog
+            onCreateToggle={this.onCreateRepoToggle}
+            onCreateRepo={this.onCreateRepo}
+            libraryType={isDepartment ? 'department' : 'group'}
+          />
+        }
+        {isMembersDialogOpen &&
+          <GroupMembersDialog
+            groupID={groupID}
+            toggleDialog={this.toggleMembersDialog}
+          />
+        }
+        {this.state.isRenameGroupDialogOpen &&
+          <RenameGroupDialog
+            groupID={groupID}
+            groupName={group.name}
+            onGroupNameChanged={this.props.onGroupNameChanged}
+            toggleDialog={this.toggleRenameGroupDialog}
+          />
+        }
+        {this.state.isDeleteGroupDialogOpen &&
+          <DismissGroupDialog
+            groupID={groupID}
+            onGroupDeleted={this.props.onGroupDeleted}
+            toggleDialog={this.toggleDeleteGroupDialog}
+          />
+        }
+        {this.state.isTransferGroupDialogOpen &&
+          <TransferGroupDialog
+            groupID={groupID}
+            onGroupTransfered={this.props.onGroupTransfered}
+            toggleDialog={this.toggleTransferGroupDialog}
+          />
+        }
+        {this.state.isImportMembersDialogOpen &&
+          <ImportMembersDialog
+            importMembersInBatch={this.importMembersInBatch}
+            toggleDialog={this.toggleImportMembersDialog}
+          />
+        }
+        {this.state.isManageMembersDialogOpen &&
+          <ManageMembersDialog
+            groupID={groupID}
+            isOwner={isOwner}
+            toggleManageMembersDialog={this.toggleManageMembersDialog}
+            toggleDepartmentDetailDialog={this.toggleDepartmentDetailDialog}
+          />
+        }
+        {this.state.isShowDepartmentDetailDialog &&
+          <DepartmentDetailDialog
+            usedFor='add_group_member'
+            toggleDepartmentDetailDialog={this.toggleDepartmentDetailDialog}
+            toggleManageMembersDialog={this.toggleManageMembersDialog}
+            groupID={groupID}
+            isOwner={isOwner}
+          />
+        }
+        {this.state.isLeaveGroupDialogOpen &&
+          <LeaveGroupDialog
+            groupID={groupID}
+            toggleDialog={this.toggleLeaveGroupDialog}
+            onLeavingGroup={this.props.onLeavingGroup}
+          />
+        }
+      </Fragment>
+    );
+  }
+}
+
+GroupOperationMenu.propTypes = propTypes;
+
+export default GroupOperationMenu;
diff --git a/frontend/src/pages/groups/group-view.js b/frontend/src/pages/groups/group-view.js
index fc523fc855..2e023ec14d 100644
--- a/frontend/src/pages/groups/group-view.js
+++ b/frontend/src/pages/groups/group-view.js
@@ -2,35 +2,25 @@ import React, { Fragment } from 'react';
 import PropTypes from 'prop-types';
 import cookie from 'react-cookies';
 import classnames from 'classnames';
-import { gettext, username, canAddRepo } from '../../utils/constants';
+import { navigate } from '@gatsbyjs/reach-router';
+import { gettext, siteRoot, username } from '../../utils/constants';
 import { seafileAPI } from '../../utils/seafile-api';
 import { Utils } from '../../utils/utils';
 import Loading from '../../components/loading';
 import EmptyTip from '../../components/empty-tip';
-import ModalPortal from '../../components/modal-portal';
 import toaster from '../../components/toast';
 import { Group, Repo } from '../../models';
-import CreateRepoDialog from '../../components/dialog/create-repo-dialog';
-import GroupMembersDialog from '../../components/dialog/group-members-dialog';
-import DismissGroupDialog from '../../components/dialog/dismiss-group-dialog';
-import RenameGroupDialog from '../../components/dialog/rename-group-dialog';
-import TransferGroupDialog from '../../components/dialog/transfer-group-dialog';
-import ImportMembersDialog from '../../components/dialog/import-members-dialog';
-import ManageMembersDialog from '../../components/dialog/manage-members-dialog';
-import DepartmentDetailDialog from '../../components/dialog/department-detail-dialog';
-import LeaveGroupDialog from '../../components/dialog/leave-group-dialog';
 import SharedRepoListView from '../../components/shared-repo-list-view/shared-repo-list-view';
 import SortOptionsDialog from '../../components/dialog/sort-options';
-import SingleDropdownToolbar from '../../components/toolbar/single-dropdown-toolbar';
 import ViewModes from '../../components/view-modes';
 import ReposSortMenu from '../../components/sort-menu';
 import { LIST_MODE } from '../../components/dir-view-mode/constants';
+import GroupOperationMenu from './group-op-menu';
 
 import '../../css/group-view.css';
 
 const propTypes = {
-  onGroupChanged: PropTypes.func.isRequired,
-  groupID: PropTypes.string,
+  groupID: PropTypes.string
 };
 
 class GroupView extends React.Component {
@@ -43,9 +33,6 @@ class GroupView extends React.Component {
       errMessage: '',
       emptyTip: null,
       currentGroup: null,
-      currentRepo: null,
-      isStaff: false,
-      isOwner: false,
       currentViewMode: localStorage.getItem('sf_repo_list_view_mode') || LIST_MODE,
       sortBy: cookie.load('seafile-repo-dir-sort-by') || 'name', // 'name' or 'time' or 'size'
       sortOrder: cookie.load('seafile-repo-dir-sort-order') || 'asc', // 'asc' or 'desc'
@@ -54,19 +41,7 @@ class GroupView extends React.Component {
       currentPage: 1,
       perPage: 300,
       hasNextPage: false,
-      libraryType: 'group',
-      isCreateRepoDialogShow: false,
       isDepartmentGroup: false,
-      isShowDepartmentDetailDialog: false,
-      showGroupDropdown: false,
-      showGroupMembersPopover: false,
-      showRenameGroupDialog: false,
-      showDismissGroupDialog: false,
-      showTransferGroupDialog: false,
-      showImportMembersDialog: false,
-      showManageMembersDialog: false,
-      isLeaveGroupDialogOpen: false,
-      isMembersDialogOpen: false
     };
   }
 
@@ -86,9 +61,7 @@ class GroupView extends React.Component {
       this.setState({
         emptyTip: this.getEmptyTip(currentGroup),
         currentGroup,
-        isStaff: currentGroup.admins.indexOf(username) > -1, // for item operations
         isDepartmentGroup: currentGroup.parent_group_id !== 0,
-        isOwner: currentGroup.owner === username,
         currentPage: 1,
         repoList: [] // empty it for the current group
       }, () => {
@@ -160,45 +133,6 @@ class GroupView extends React.Component {
     return null;
   };
 
-  onCreateRepoToggle = () => {
-    this.setState({ isCreateRepoDialogShow: !this.state.isCreateRepoDialogShow });
-  };
-
-  onCreateRepo = (repo, groupOwnerType) => {
-    let groupId = this.props.groupID;
-    if (groupOwnerType && groupOwnerType === 'department') {
-      seafileAPI.createGroupOwnedLibrary(groupId, repo).then(res => { // need modify endpoint api
-        let object = {
-          repo_id: res.data.id,
-          repo_name: res.data.name,
-          owner_name: res.data.group_name,
-          owner_email: res.data.owner,
-          permission: res.data.permission,
-          mtime: res.data.mtime,
-          size: res.data.size,
-          encrypted: res.data.encrypted,
-        };
-        let repo = new Repo(object);
-        let repoList = this.addRepoItem(repo);
-        this.setState({ repoList: repoList });
-      }).catch(error => {
-        let errMessage = Utils.getErrorMsg(error);
-        toaster.danger(errMessage);
-      });
-
-    } else {
-      seafileAPI.createGroupRepo(groupId, repo).then(res => {
-        let repo = new Repo(res.data);
-        let repoList = this.addRepoItem(repo);
-        this.setState({ repoList: repoList });
-      }).catch(error => {
-        let errMessage = Utils.getErrorMsg(error);
-        toaster.danger(errMessage);
-      });
-    }
-    this.onCreateRepoToggle();
-  };
-
   onItemDelete = (repo) => {
     let repoList = this.state.repoList.filter(item => {
       return item.repo_id !== repo.repo_id;
@@ -215,12 +149,6 @@ class GroupView extends React.Component {
     this.loadGroup(this.props.groupID);
   };
 
-  addRepoItem = (repo) => {
-    let newRepoList = this.state.repoList.map(item => {return item;});
-    newRepoList.unshift(repo);
-    return newRepoList;
-  };
-
   onItemUnshare = (repo) => {
     let group = this.state.currentGroup;
     seafileAPI.unshareRepoToGroup(repo.repo_id, group.id).then(() => {
@@ -260,59 +188,6 @@ class GroupView extends React.Component {
     this.setState({ repoList: repoList });
   };
 
-  toggleDismissGroupDialog = () => {
-    this.setState({
-      showDismissGroupDialog: !this.state.showDismissGroupDialog,
-      showGroupDropdown: false,
-    });
-  };
-
-  toggleRenameGroupDialog = () => {
-    this.setState({
-      showRenameGroupDialog: !this.state.showRenameGroupDialog,
-      showGroupDropdown: false,
-    });
-  };
-
-  toggleTransferGroupDialog = () => {
-    this.setState({
-      showTransferGroupDialog: !this.state.showTransferGroupDialog,
-      showGroupDropdown: false,
-    });
-  };
-
-  toggleImportMembersDialog = () => {
-    this.setState({
-      showImportMembersDialog: !this.state.showImportMembersDialog
-    });
-  };
-
-  importMembersInBatch = (file) => {
-    toaster.notify(gettext('It may take some time, please wait.'));
-    seafileAPI.importGroupMembersViaFile(this.state.currentGroup.id, file).then((res) => {
-      res.data.failed.forEach(item => {
-        toaster.danger(`${item.email}: ${item.error_msg}`);
-      });
-    }).catch((error) => {
-      let errMsg = Utils.getErrorMsg(error);
-      toaster.danger(errMsg);
-    });
-  };
-
-  toggleManageMembersDialog = () => {
-    this.setState({
-      showManageMembersDialog: !this.state.showManageMembersDialog,
-      showGroupDropdown: false,
-    });
-  };
-
-  toggleLeaveGroupDialog = () => {
-    this.setState({
-      isLeaveGroupDialogOpen: !this.state.isLeaveGroupDialogOpen,
-      showGroupDropdown: false,
-    });
-  };
-
   sortItems = (sortBy, sortOrder) => {
     cookie.save('seafile-repo-dir-sort-by', sortBy);
     cookie.save('seafile-repo-dir-sort-order', sortOrder);
@@ -323,18 +198,6 @@ class GroupView extends React.Component {
     });
   };
 
-  translateRole = (role) => {
-    if (role === 'Admin') {
-      return gettext('Admin');
-    }
-    else if (role === 'Member') {
-      return gettext('Member');
-    }
-    else if (role === 'Owner') {
-      return gettext('Owner');
-    }
-  };
-
   toggleSortOptionsDialog = () => {
     this.setState({
       isSortOptionsDialogOpen: !this.state.isSortOptionsDialogOpen
@@ -357,43 +220,6 @@ class GroupView extends React.Component {
     }
   };
 
-  toggleMembersDialog = () => {
-    this.setState({
-      isMembersDialogOpen: !this.state.isMembersDialogOpen
-    });
-  };
-
-  getOpList = () => {
-    const { currentGroup, isDepartmentGroup, isStaff, isOwner } = this.state;
-    const opList = [];
-    if ((!isDepartmentGroup && canAddRepo) ||
-      (isDepartmentGroup && isStaff)) {
-      this.newLibraryEnalbed = true;
-      opList.push({ 'text': gettext('New Library'), 'onClick': this.onCreateRepoToggle }, 'Divider');
-    }
-    opList.push({ 'text': gettext('Members'), 'onClick': this.toggleMembersDialog });
-    if (currentGroup) {
-      if (isStaff || isOwner) {
-        opList.push({ 'text': gettext('Import members'), 'onClick': this.toggleImportMembersDialog });
-        opList.push({ 'text': gettext('Manage members'), 'onClick': this.toggleManageMembersDialog });
-        opList.push('Divider');
-        opList.push({ 'text': gettext('Rename'), 'onClick': this.toggleRenameGroupDialog });
-        if (isOwner) {
-          opList.push({ 'text': gettext('Transfer'), 'onClick': this.toggleTransferGroupDialog });
-        }
-        if (isOwner) {
-          opList.push({ 'text': gettext('Delete group'), 'onClick': this.toggleDismissGroupDialog });
-        }
-      }
-
-      if (!isOwner && !isDepartmentGroup) {
-        opList.push({ 'text': gettext('Leave group'), 'onClick': this.toggleLeaveGroupDialog });
-      }
-    }
-
-    return opList;
-  };
-
   switchViewMode = (newMode) => {
     this.setState({
       currentViewMode: newMode
@@ -409,16 +235,38 @@ class GroupView extends React.Component {
     });
   };
 
-  toggleDepartmentDetailDialog = () => {
+  addNewRepo = (newRepo) => {
+    let { repoList } = this.state;
+    repoList.unshift(newRepo);
+    this.setState({ repoList: repoList });
+  };
+
+  onGroupNameChanged = (newName) => {
+    const { currentGroup } = this.state;
+    currentGroup.name = newName;
     this.setState({
-      isShowDepartmentDetailDialog: !this.state.isShowDepartmentDetailDialog
+      currentGroup: currentGroup
     });
   };
 
+  onGroupTransfered = (group) => {
+    this.setState({
+      currentGroup: group
+    });
+  };
+
+  onGroupDeleted = () => {
+    navigate(siteRoot);
+  };
+
+  onLeavingGroup = () => {
+    navigate(siteRoot);
+  };
+
   render() {
     const {
       isLoading, repoList, errMessage, emptyTip,
-      currentGroup, isDepartmentGroup, isMembersDialogOpen,
+      currentGroup, isDepartmentGroup,
       currentViewMode, sortBy, sortOrder
     } = this.state;
 
@@ -427,7 +275,6 @@ class GroupView extends React.Component {
       useRate = currentGroup.group_quota_usage / currentGroup.group_quota * 100 + '%';
     }
 
-    const opList = this.getOpList();
     return (
       <Fragment>
         <div className="main-panel-center flex-row">
@@ -440,9 +287,13 @@ class GroupView extends React.Component {
                     <span className="sf3-font-department sf3-font nav-icon" title={gettext('This is a special group representing a department.')}></span>
                     }
                     <span>{currentGroup.name}</span>
-                    <SingleDropdownToolbar
-                      withPlusIcon={this.newLibraryEnalbed}
-                      opList={opList}
+                    <GroupOperationMenu
+                      group={currentGroup}
+                      addNewRepo={this.addNewRepo}
+                      onGroupNameChanged={this.onGroupNameChanged}
+                      onGroupTransfered={this.onGroupTransfered}
+                      onGroupDeleted={this.onGroupDeleted}
+                      onLeavingGroup={this.onLeavingGroup}
                     />
                   </div>
                   <div className="path-tool d-flex align-items-center">
@@ -510,85 +361,6 @@ class GroupView extends React.Component {
             </div>
           </div>
         </div>
-        {this.state.isCreateRepoDialogShow && !this.state.isDepartmentGroup && (
-          <ModalPortal>
-            <CreateRepoDialog
-              libraryType={this.state.libraryType}
-              onCreateToggle={this.onCreateRepoToggle}
-              onCreateRepo={this.onCreateRepo}
-            />
-          </ModalPortal>
-        )}
-        {this.state.isCreateRepoDialogShow && this.state.isDepartmentGroup &&
-          <CreateRepoDialog
-            onCreateToggle={this.onCreateRepoToggle}
-            onCreateRepo={this.onCreateRepo}
-            libraryType='department'
-          />
-        }
-        {isMembersDialogOpen &&
-        <GroupMembersDialog
-          groupID={this.props.groupID}
-          toggleDialog={this.toggleMembersDialog}
-        />
-        }
-        {this.state.showRenameGroupDialog &&
-          <RenameGroupDialog
-            showRenameGroupDialog={this.state.showRenameGroupDialog}
-            toggleRenameGroupDialog={this.toggleRenameGroupDialog}
-            loadGroup={this.loadGroup}
-            groupID={this.props.groupID}
-            onGroupChanged={this.props.onGroupChanged}
-            currentGroupName={currentGroup.name}
-          />
-        }
-        {this.state.showDismissGroupDialog &&
-          <DismissGroupDialog
-            showDismissGroupDialog={this.state.showDismissGroupDialog}
-            toggleDismissGroupDialog={this.toggleDismissGroupDialog}
-            loadGroup={this.loadGroup}
-            groupID={this.props.groupID}
-            onGroupChanged={this.props.onGroupChanged}
-          />
-        }
-        {this.state.showTransferGroupDialog &&
-          <TransferGroupDialog
-            toggleTransferGroupDialog={this.toggleTransferGroupDialog}
-            groupID={this.props.groupID}
-            onGroupChanged={this.props.onGroupChanged}
-          />
-        }
-        { this.state.showImportMembersDialog &&
-          <ImportMembersDialog
-            toggleImportMembersDialog={this.toggleImportMembersDialog}
-            importMembersInBatch={this.importMembersInBatch}
-          />
-        }
-        {this.state.showManageMembersDialog &&
-          <ManageMembersDialog
-            toggleManageMembersDialog={this.toggleManageMembersDialog}
-            groupID={this.props.groupID}
-            onGroupChanged={this.props.onGroupChanged}
-            isOwner={this.state.isOwner}
-            toggleDepartmentDetailDialog={this.toggleDepartmentDetailDialog}
-          />
-        }
-        {this.state.isShowDepartmentDetailDialog &&
-          <DepartmentDetailDialog
-            usedFor='add_group_member'
-            toggleDepartmentDetailDialog={this.toggleDepartmentDetailDialog}
-            toggleManageMembersDialog={this.toggleManageMembersDialog}
-            groupID={this.props.groupID}
-            isOwner={this.state.isOwner}
-          />
-        }
-        {this.state.isLeaveGroupDialogOpen &&
-          <LeaveGroupDialog
-            toggleLeaveGroupDialog={this.toggleLeaveGroupDialog}
-            groupID={this.props.groupID}
-            onGroupChanged={this.props.onGroupChanged}
-          />
-        }
       </Fragment>
     );
   }
diff --git a/frontend/src/pages/libraries/index.js b/frontend/src/pages/libraries/index.js
index 5d39a8b752..99b297631a 100644
--- a/frontend/src/pages/libraries/index.js
+++ b/frontend/src/pages/libraries/index.js
@@ -52,7 +52,7 @@ class Libraries extends Component {
 
     const eventBus = EventBus.getInstance();
     this.unsubscribeAddNewGroup = eventBus.subscribe(EVENT_BUS_TYPE.ADD_NEW_GROUP, this.addNewGroup);
-    this.unsubscribeAddSharedRepoIntoGroup = eventBus.subscribe(EVENT_BUS_TYPE.ADD_SHARED_REPO_INTO_GROUP, this.insertRepoIntoGroup);
+    this.unsubscribeAddSharedRepoIntoGroup = eventBus.subscribe(EVENT_BUS_TYPE.ADD_SHARED_REPO_INTO_GROUP, this.addRepoToGroup);
     this.unsubscribeUnsharedRepoToGroup = eventBus.subscribe(EVENT_BUS_TYPE.UN_SHARE_REPO_TO_GROUP, this.unshareRepoToGroup);
   }
 
@@ -282,7 +282,7 @@ class Libraries extends Component {
   };
   */
 
-  insertRepoIntoGroup = ({ repo, group_id }) => {
+  addRepoToGroup = ({ repo, group_id }) => {
     if (!repo) {
       return;
     }
@@ -299,11 +299,37 @@ class Libraries extends Component {
     }
 
     targetGroup.repos.unshift(repo);
-    targetGroup.repos = Utils.sortRepos(targetGroup.repos, this.state.sortBy, this.state.sortOrder);
     this.groupsReposManager.add(repo.repo_id, group_id);
     this.setState({ groupList: newGroupList });
   };
 
+  onGroupNameChanged = (newName, groupID) => {
+    const { groupList } = this.state;
+    let newGroupList = [...groupList];
+    let targetGroup = newGroupList.find((group) => group.id === groupID);
+    targetGroup.name = newName;
+    this.setState({ groupList: newGroupList });
+  };
+
+  onGroupTransfered = (group) => {
+    const { groupList } = this.state;
+    let newGroupList = [...groupList];
+    let targetGroup = newGroupList.find((item) => item.id === group.id);
+    targetGroup.owner = group.owner;
+    targetGroup.admins = group.admins;
+    this.setState({ groupList: newGroupList });
+  };
+
+  onGroupDeleted = (groupID) => {
+    const { groupList } = this.state;
+    this.setState({ groupList: groupList.filter(item => item.id != groupID) });
+  };
+
+  onLeavingGroup = (groupID) => {
+    const { groupList } = this.state;
+    this.setState({ groupList: groupList.filter(item => item.id != groupID) });
+  };
+
   unshareRepoToGroup = ({ repo_id, group_id }) => {
     const { groupList } = this.state;
     let newGroupList = [...groupList];
@@ -498,7 +524,11 @@ class Libraries extends Component {
                         onMonitorRepo={this.onMonitorRepo}
                         renameRelatedGroupsRepos={this.renameRelatedGroupsRepos}
                         deleteRelatedGroupsRepos={this.deleteRelatedGroupsRepos}
-                        insertRepoIntoGroup={this.insertRepoIntoGroup}
+                        addRepoToGroup={this.addRepoToGroup}
+                        onGroupNameChanged={this.onGroupNameChanged}
+                        onGroupTransfered={this.onGroupTransfered}
+                        onGroupDeleted={this.onGroupDeleted}
+                        onLeavingGroup={this.onLeavingGroup}
                         unshareRepoToGroup={this.unshareRepoToGroup}
                         onTransferRepo={this.onGroupTransferRepo}
                         currentViewMode={currentViewMode}