mirror of
https://github.com/mattermost/mattermost.git
synced 2026-09-01 15:00:08 +08:00
[MM-69226] Fix Manage Teams role dropdown overflowing outside the modal (#37400)
* Fix Manage Teams dropdown overflowing outside modal for last rows The role dropdown in the Admin Console Manage Teams modal always opened downward, so for users with many teams the menu for the last rows was pushed below the modal and out of view. Compute openUp for rows near the bottom of the list, mirroring the TeamMembersDropdown behavior. Co-authored-by: mattermost-code <matty-code@mattermost.com> * Render Manage Teams role dropdown via floating menu to fix overflow Migrate ManageTeamsDropdown from the deprecated Menu/MenuWrapper widget to the modern components/menu (MUI Popover). The old widget positioned the menu relative to the modal content, so for users with many teams the role dropdown on the last rows was pushed below the modal and off-screen. The new menu renders in a portal anchored to the trigger and opens upward for rows near the bottom of the list, mirroring TeamMembersDropdown. Co-authored-by: mattermost-code <matty-code@mattermost.com> * Add openUp helper coverage and action tests for ManageTeamsDropdown Extract the open-up positioning rule into a testable shouldOpenUp helper and cover its boundaries, plus the demote and error paths, so the fix for the dropdown overflow is exercised directly. Co-authored-by: mattermost-code <matty-code@mattermost.com> * Satisfy lint for table-driven test comment Co-authored-by: mattermost-code <matty-code@mattermost.com> * Gate onMemberChange assertions behind waitFor in dropdown tests Move onMemberChange expectations inside waitFor so they wait for the async updateTeamMemberSchemeRoles promise to resolve before asserting. Co-authored-by: mattermost-code <matty-code@mattermost.com> * Fix Manage Teams e2e to target MUI menu button and items The Manage Teams role dropdown was migrated from the deprecated components/widgets/menu (div.MenuWrapper) to the modern components/menu (MUI Popover). The Playwright spec still clicked div.MenuWrapper and looked for menu items inside the team row, but the new menu renders in a portal. Target the accessible role button and role menuitem elements instead. --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: mattermost-code <matty-code@mattermost.com>
This commit is contained in:
@@ -63,6 +63,7 @@ test('MM-T5520-2 should change user roles', async ({pw}) => {
|
||||
|
||||
test('MM-T5520-3 should be able to manage teams', async ({pw}) => {
|
||||
const {systemConsolePage} = await setupAndGetRandomUser(pw);
|
||||
const {page} = systemConsolePage;
|
||||
|
||||
const userRow = systemConsolePage.users.usersTable.getRowByIndex(0);
|
||||
|
||||
@@ -70,28 +71,27 @@ test('MM-T5520-3 should be able to manage teams', async ({pw}) => {
|
||||
const actionMenu = await userRow.openActionMenu();
|
||||
await actionMenu.clickManageTeams();
|
||||
|
||||
// # Click Make Team Admin
|
||||
const team = systemConsolePage.page.locator('div.manage-teams__team');
|
||||
const teamDropdown = team.locator('div.MenuWrapper');
|
||||
await teamDropdown.click();
|
||||
const makeTeamAdmin = teamDropdown.getByText('Make Team Admin');
|
||||
await makeTeamAdmin.click();
|
||||
// The role dropdown renders its menu in a portal (MUI Popover), so the menu
|
||||
// items live at the page level rather than inside the team row.
|
||||
const team = page.locator('div.manage-teams__team');
|
||||
|
||||
// # Open the role dropdown and click Make Team Admin
|
||||
await team.getByRole('button', {name: 'Team Member'}).click();
|
||||
await page.getByRole('menuitem', {name: 'Make Team Admin'}).click();
|
||||
|
||||
// * Verify role is updated
|
||||
await expect(team.getByText('Team Admin')).toBeVisible();
|
||||
|
||||
// # Change back to Team Member
|
||||
await teamDropdown.click();
|
||||
const makeTeamMember = teamDropdown.getByText('Make Team Member');
|
||||
await makeTeamMember.click();
|
||||
await team.getByRole('button', {name: 'Team Admin'}).click();
|
||||
await page.getByRole('menuitem', {name: 'Make Team Member'}).click();
|
||||
|
||||
// * Verify role is updated
|
||||
await expect(team.getByText('Team Member')).toBeVisible();
|
||||
|
||||
// # Click Remove From Team
|
||||
await teamDropdown.click();
|
||||
const removeFromTeam = teamDropdown.getByText('Remove From Team');
|
||||
await removeFromTeam.click();
|
||||
// # Click Remove from Team
|
||||
await team.getByRole('button', {name: 'Team Member'}).click();
|
||||
await page.getByRole('menuitem', {name: 'Remove from Team'}).click();
|
||||
|
||||
// * The team should be detached
|
||||
await team.waitFor({state: 'detached'});
|
||||
|
||||
-73
@@ -1,73 +0,0 @@
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`ManageTeamsDropdown should match snapshot for guest 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="MenuWrapper "
|
||||
>
|
||||
<a>
|
||||
<span>
|
||||
Guest
|
||||
|
||||
</span>
|
||||
<span
|
||||
class="caret"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`ManageTeamsDropdown should match snapshot for system admin 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="MenuWrapper "
|
||||
>
|
||||
<a>
|
||||
<span>
|
||||
System Admin
|
||||
|
||||
</span>
|
||||
<span
|
||||
class="caret"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`ManageTeamsDropdown should match snapshot for team admin 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="MenuWrapper "
|
||||
>
|
||||
<a>
|
||||
<span>
|
||||
Team Admin
|
||||
|
||||
</span>
|
||||
<span
|
||||
class="caret"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`ManageTeamsDropdown should match snapshot for team member 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="MenuWrapper "
|
||||
>
|
||||
<a>
|
||||
<span>
|
||||
Team Member
|
||||
|
||||
</span>
|
||||
<span
|
||||
class="caret"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
+146
-61
@@ -3,14 +3,33 @@
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import ManageTeamsDropdown from 'components/admin_console/manage_teams_modal/manage_teams_dropdown';
|
||||
import ManageTeamsDropdown, {shouldOpenUp} from 'components/admin_console/manage_teams_modal/manage_teams_dropdown';
|
||||
|
||||
import {renderWithContext, screen} from 'tests/react_testing_utils';
|
||||
import {renderWithContext, screen, userEvent, waitFor} from 'tests/react_testing_utils';
|
||||
import {TestHelper} from 'utils/test_helper';
|
||||
|
||||
describe('shouldOpenUp', () => {
|
||||
test.each([
|
||||
|
||||
// Short lists never open up, so a short modal keeps its natural downward menus.
|
||||
[0, 1, false],
|
||||
[0, 3, false],
|
||||
[2, 3, false],
|
||||
|
||||
// Long lists open up only for the last few rows so the menu stays inside the modal.
|
||||
[0, 20, false],
|
||||
[16, 20, false],
|
||||
[17, 20, true],
|
||||
[18, 20, true],
|
||||
[19, 20, true],
|
||||
])('index %i of %i teams -> openUp %s', (index, totalTeams, expected) => {
|
||||
expect(shouldOpenUp(index, totalTeams)).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ManageTeamsDropdown', () => {
|
||||
const baseProps = {
|
||||
team: TestHelper.getTeamMock(),
|
||||
team: TestHelper.getTeamMock({id: 'teamid', group_constrained: false}),
|
||||
user: TestHelper.getUserMock({
|
||||
id: 'currentUserId',
|
||||
last_picture_update: 1234,
|
||||
@@ -24,86 +43,152 @@ describe('ManageTeamsDropdown', () => {
|
||||
scheme_guest: false,
|
||||
scheme_admin: false,
|
||||
}),
|
||||
index: 0,
|
||||
totalTeams: 1,
|
||||
onError: jest.fn(),
|
||||
onMemberChange: jest.fn(),
|
||||
updateTeamMemberSchemeRoles: jest.fn(),
|
||||
updateTeamMemberSchemeRoles: jest.fn().mockResolvedValue({}),
|
||||
handleRemoveUserFromTeam: jest.fn(),
|
||||
};
|
||||
|
||||
test('should match snapshot for team member', () => {
|
||||
const {container} = renderWithContext(
|
||||
<ManageTeamsDropdown {...baseProps}/>,
|
||||
);
|
||||
|
||||
expect(screen.getByText('Team Member')).toBeInTheDocument();
|
||||
expect(container).toMatchSnapshot();
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
test('should match snapshot for system admin', () => {
|
||||
const user = {
|
||||
...baseProps.user,
|
||||
roles: 'system_admin',
|
||||
};
|
||||
test('shows the "Team Member" role for a plain member and the member actions when opened', async () => {
|
||||
renderWithContext(<ManageTeamsDropdown {...baseProps}/>);
|
||||
|
||||
const props = {
|
||||
...baseProps,
|
||||
user,
|
||||
};
|
||||
await userEvent.click(screen.getByRole('button', {name: /Team Member/i}));
|
||||
|
||||
const {container} = renderWithContext(
|
||||
<ManageTeamsDropdown {...props}/>,
|
||||
);
|
||||
|
||||
expect(screen.getByText('System Admin')).toBeInTheDocument();
|
||||
expect(container).toMatchSnapshot();
|
||||
expect(screen.getByRole('menuitem', {name: 'Make Team Admin'})).toBeInTheDocument();
|
||||
expect(screen.getByRole('menuitem', {name: 'Remove from Team'})).toBeInTheDocument();
|
||||
expect(screen.queryByRole('menuitem', {name: 'Make Team Member'})).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should match snapshot for team admin', () => {
|
||||
const user = {
|
||||
...baseProps.user,
|
||||
roles: 'system_user',
|
||||
};
|
||||
|
||||
const teamMember = {
|
||||
...baseProps.teamMember,
|
||||
scheme_admin: true,
|
||||
};
|
||||
|
||||
test('shows the "Team Admin" role and the demote action for a team admin', async () => {
|
||||
const props = {
|
||||
...baseProps,
|
||||
user,
|
||||
teamMember,
|
||||
teamMember: {...baseProps.teamMember, scheme_admin: true},
|
||||
};
|
||||
|
||||
const {container} = renderWithContext(
|
||||
<ManageTeamsDropdown {...props}/>,
|
||||
);
|
||||
renderWithContext(<ManageTeamsDropdown {...props}/>);
|
||||
|
||||
expect(screen.getByText('Team Admin')).toBeInTheDocument();
|
||||
expect(container).toMatchSnapshot();
|
||||
await userEvent.click(screen.getByRole('button', {name: /Team Admin/i}));
|
||||
|
||||
expect(screen.getByRole('menuitem', {name: 'Make Team Member'})).toBeInTheDocument();
|
||||
expect(screen.getByRole('menuitem', {name: 'Remove from Team'})).toBeInTheDocument();
|
||||
expect(screen.queryByRole('menuitem', {name: 'Make Team Admin'})).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should match snapshot for guest', () => {
|
||||
const user = {
|
||||
...baseProps.user,
|
||||
roles: 'system_guest',
|
||||
};
|
||||
|
||||
const teamMember = {
|
||||
...baseProps.teamMember,
|
||||
};
|
||||
|
||||
test('shows the "Guest" role and no promotion action for a guest', async () => {
|
||||
const props = {
|
||||
...baseProps,
|
||||
user,
|
||||
teamMember,
|
||||
user: {...baseProps.user, roles: 'system_guest'},
|
||||
};
|
||||
|
||||
const {container} = renderWithContext(
|
||||
<ManageTeamsDropdown {...props}/>,
|
||||
);
|
||||
renderWithContext(<ManageTeamsDropdown {...props}/>);
|
||||
|
||||
expect(screen.getByText('Guest')).toBeInTheDocument();
|
||||
expect(container).toMatchSnapshot();
|
||||
await userEvent.click(screen.getByRole('button', {name: /Guest/i}));
|
||||
|
||||
expect(screen.getByRole('menuitem', {name: 'Remove from Team'})).toBeInTheDocument();
|
||||
expect(screen.queryByRole('menuitem', {name: 'Make Team Admin'})).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('menuitem', {name: 'Make Team Member'})).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('hides "Remove from Team" for a group constrained team', async () => {
|
||||
const props = {
|
||||
...baseProps,
|
||||
team: TestHelper.getTeamMock({id: 'teamid', group_constrained: true}),
|
||||
};
|
||||
|
||||
renderWithContext(<ManageTeamsDropdown {...props}/>);
|
||||
|
||||
await userEvent.click(screen.getByRole('button', {name: /Team Member/i}));
|
||||
|
||||
expect(screen.getByRole('menuitem', {name: 'Make Team Admin'})).toBeInTheDocument();
|
||||
expect(screen.queryByRole('menuitem', {name: 'Remove from Team'})).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('promotes the member to team admin and reports the change', async () => {
|
||||
renderWithContext(<ManageTeamsDropdown {...baseProps}/>);
|
||||
|
||||
await userEvent.click(screen.getByRole('button', {name: /Team Member/i}));
|
||||
await userEvent.click(screen.getByRole('menuitem', {name: 'Make Team Admin'}));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(baseProps.updateTeamMemberSchemeRoles).toHaveBeenCalledWith('teamid', 'currentUserId', true, true);
|
||||
expect(baseProps.onMemberChange).toHaveBeenCalledWith('teamid');
|
||||
});
|
||||
});
|
||||
|
||||
test('demotes a team admin to member and reports the change', async () => {
|
||||
const props = {
|
||||
...baseProps,
|
||||
teamMember: {...baseProps.teamMember, scheme_admin: true},
|
||||
};
|
||||
|
||||
renderWithContext(<ManageTeamsDropdown {...props}/>);
|
||||
|
||||
await userEvent.click(screen.getByRole('button', {name: /Team Admin/i}));
|
||||
await userEvent.click(screen.getByRole('menuitem', {name: 'Make Team Member'}));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(baseProps.updateTeamMemberSchemeRoles).toHaveBeenCalledWith('teamid', 'currentUserId', true, false);
|
||||
expect(baseProps.onMemberChange).toHaveBeenCalledWith('teamid');
|
||||
});
|
||||
});
|
||||
|
||||
test('removes the user from the team', async () => {
|
||||
renderWithContext(<ManageTeamsDropdown {...baseProps}/>);
|
||||
|
||||
await userEvent.click(screen.getByRole('button', {name: /Team Member/i}));
|
||||
await userEvent.click(screen.getByRole('menuitem', {name: 'Remove from Team'}));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(baseProps.handleRemoveUserFromTeam).toHaveBeenCalledWith('teamid');
|
||||
});
|
||||
});
|
||||
|
||||
test('surfaces the promotion error message when the request fails', async () => {
|
||||
const onError = jest.fn();
|
||||
const props = {
|
||||
...baseProps,
|
||||
onError,
|
||||
onMemberChange: jest.fn(),
|
||||
updateTeamMemberSchemeRoles: jest.fn().mockResolvedValue({error: {message: 'boom'}}),
|
||||
};
|
||||
|
||||
renderWithContext(<ManageTeamsDropdown {...props}/>);
|
||||
|
||||
await userEvent.click(screen.getByRole('button', {name: /Team Member/i}));
|
||||
await userEvent.click(screen.getByRole('menuitem', {name: 'Make Team Admin'}));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onError).toHaveBeenCalled();
|
||||
});
|
||||
expect(onError.mock.calls[0][0].props.id).toBe('admin.manage_teams.makeAdminError');
|
||||
expect(props.onMemberChange).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('surfaces the demotion error message when the request fails', async () => {
|
||||
const onError = jest.fn();
|
||||
const props = {
|
||||
...baseProps,
|
||||
teamMember: {...baseProps.teamMember, scheme_admin: true},
|
||||
onError,
|
||||
onMemberChange: jest.fn(),
|
||||
updateTeamMemberSchemeRoles: jest.fn().mockResolvedValue({error: {message: 'boom'}}),
|
||||
};
|
||||
|
||||
renderWithContext(<ManageTeamsDropdown {...props}/>);
|
||||
|
||||
await userEvent.click(screen.getByRole('button', {name: /Team Admin/i}));
|
||||
await userEvent.click(screen.getByRole('menuitem', {name: 'Make Team Member'}));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onError).toHaveBeenCalled();
|
||||
});
|
||||
expect(onError.mock.calls[0][0].props.id).toBe('admin.manage_teams.makeMemberError');
|
||||
expect(props.onMemberChange).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
+74
-23
@@ -10,13 +10,23 @@ import type {UserProfile} from '@mattermost/types/users';
|
||||
import type {ActionResult} from 'mattermost-redux/types/actions';
|
||||
import {isAdmin, isSystemAdmin, isGuest} from 'mattermost-redux/utils/user_utils';
|
||||
|
||||
import Menu from 'components/widgets/menu/menu';
|
||||
import MenuWrapper from 'components/widgets/menu/menu_wrapper';
|
||||
import * as Menu from 'components/menu';
|
||||
import DropdownIcon from 'components/widgets/icons/fa_dropdown_icon';
|
||||
|
||||
const ROWS_FROM_BOTTOM_TO_OPEN_UP = 3;
|
||||
|
||||
// The role dropdown opens upward for rows near the bottom of the list so its menu
|
||||
// stays inside the modal instead of overflowing below it (MM-69226).
|
||||
export function shouldOpenUp(index: number, totalTeams: number): boolean {
|
||||
return totalTeams > ROWS_FROM_BOTTOM_TO_OPEN_UP && totalTeams - index <= ROWS_FROM_BOTTOM_TO_OPEN_UP;
|
||||
}
|
||||
|
||||
type Props = {
|
||||
team: Team;
|
||||
user: UserProfile;
|
||||
teamMember: TeamMembership;
|
||||
index: number;
|
||||
totalTeams: number;
|
||||
onError: (error: JSX.Element) => void;
|
||||
onMemberChange: (teamId: string) => void;
|
||||
updateTeamMemberSchemeRoles: (teamId: string, userId: string, isSchemeUser: boolean, isSchemeAdmin: boolean) => Promise<ActionResult>;
|
||||
@@ -59,7 +69,10 @@ const ManageTeamsDropdown = (props: Props) => {
|
||||
const isSysAdmin = isSystemAdmin(props.user.roles);
|
||||
const isGuestUser = isGuest(props.user.roles);
|
||||
|
||||
const {team} = props;
|
||||
const {team, index, totalTeams} = props;
|
||||
|
||||
const openUp = shouldOpenUp(index, totalTeams);
|
||||
|
||||
let title;
|
||||
if (isSysAdmin) {
|
||||
title = formatMessage({id: 'admin.user_item.sysAdmin', defaultMessage: 'System Admin'});
|
||||
@@ -71,33 +84,71 @@ const ManageTeamsDropdown = (props: Props) => {
|
||||
title = formatMessage({id: 'admin.user_item.teamMember', defaultMessage: 'Team Member'});
|
||||
}
|
||||
|
||||
const showMakeTeamAdmin = !isTeamAdmin && !isGuestUser;
|
||||
const showRemoveFromTeam = !team.group_constrained;
|
||||
|
||||
return (
|
||||
<MenuWrapper>
|
||||
<a>
|
||||
<span>{title} </span>
|
||||
<span className='caret'/>
|
||||
</a>
|
||||
<Menu
|
||||
openLeft={true}
|
||||
ariaLabel={formatMessage({id: 'team_members_dropdown.menuAriaLabel', defaultMessage: 'Change the role of a team member'})}
|
||||
>
|
||||
<Menu.ItemAction
|
||||
show={!isTeamAdmin && !isGuestUser}
|
||||
<Menu.Container
|
||||
menuButton={{
|
||||
id: `manageTeamsDropdown_${team.id}`,
|
||||
class: 'dropdown-toggle theme color--link style--none',
|
||||
children: (
|
||||
<>
|
||||
<span>{title} </span>
|
||||
<DropdownIcon/>
|
||||
</>
|
||||
),
|
||||
}}
|
||||
menu={{
|
||||
id: `manageTeamsDropdown_${team.id}_menu`,
|
||||
'aria-label': formatMessage({id: 'team_members_dropdown.menuAriaLabel', defaultMessage: 'Change the role of a team member'}),
|
||||
}}
|
||||
anchorOrigin={{
|
||||
vertical: openUp ? 'top' : 'bottom',
|
||||
horizontal: 'right',
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: openUp ? 'bottom' : 'top',
|
||||
horizontal: 'right',
|
||||
}}
|
||||
>
|
||||
{showMakeTeamAdmin ? (
|
||||
<Menu.Item
|
||||
id='makeTeamAdmin'
|
||||
onClick={makeTeamAdmin}
|
||||
text={formatMessage({id: 'admin.user_item.makeTeamAdmin', defaultMessage: 'Make Team Admin'})}
|
||||
labels={
|
||||
<FormattedMessage
|
||||
id='admin.user_item.makeTeamAdmin'
|
||||
defaultMessage='Make Team Admin'
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Menu.ItemAction
|
||||
show={isTeamAdmin}
|
||||
) : null}
|
||||
{isTeamAdmin ? (
|
||||
<Menu.Item
|
||||
id='makeTeamMember'
|
||||
onClick={makeMember}
|
||||
text={formatMessage({id: 'admin.user_item.makeMember', defaultMessage: 'Make Team Member'})}
|
||||
labels={
|
||||
<FormattedMessage
|
||||
id='admin.user_item.makeMember'
|
||||
defaultMessage='Make Team Member'
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Menu.ItemAction
|
||||
show={!team.group_constrained}
|
||||
) : null}
|
||||
{showRemoveFromTeam ? (
|
||||
<Menu.Item
|
||||
id='removeFromTeam'
|
||||
onClick={removeFromTeam}
|
||||
text={formatMessage({id: 'team_members_dropdown.leave_team', defaultMessage: 'Remove from Team'})}
|
||||
labels={
|
||||
<FormattedMessage
|
||||
id='team_members_dropdown.leave_team'
|
||||
defaultMessage='Remove from Team'
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Menu>
|
||||
</MenuWrapper>
|
||||
) : null}
|
||||
</Menu.Container>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
+3
-1
@@ -107,7 +107,7 @@ const ManageTeamsModal = ({locale, onExited, user, actions}: Props) => {
|
||||
|
||||
let teamList;
|
||||
if (teams && teamMembers) {
|
||||
teamList = teams.map((team) => {
|
||||
teamList = teams.map((team, index) => {
|
||||
const teamMember = teamMembers.find((member: TeamMembership) => member.team_id === team.id);
|
||||
if (!teamMember) {
|
||||
return null;
|
||||
@@ -123,6 +123,8 @@ const ManageTeamsModal = ({locale, onExited, user, actions}: Props) => {
|
||||
user={user}
|
||||
team={team}
|
||||
teamMember={teamMember}
|
||||
index={index}
|
||||
totalTeams={teams.length}
|
||||
onError={handleError}
|
||||
onMemberChange={handleMemberChange}
|
||||
updateTeamMemberSchemeRoles={actions.updateTeamMemberSchemeRoles}
|
||||
|
||||
Reference in New Issue
Block a user