add role update (#11217)
## Context This PR introduces the new Create and Edit role components, behind the PERMISSIONS_ENABLED_V2 feature flag.
This commit is contained in:
+56
-34
@@ -1,8 +1,12 @@
|
||||
import { currentWorkspaceMembersState } from '@/auth/states/currentWorkspaceMembersStates';
|
||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||
import { RoleAssignmentTableHeader } from '@/settings/roles/role-assignment/components/RoleAssignmentTableHeader';
|
||||
import { RoleAssignmentWorkspaceMemberPickerDropdown } from '@/settings/roles/role-assignment/components/RoleAssignmentWorkspaceMemberPickerDropdown';
|
||||
import { RoleAssignmentConfirmationModalSelectedWorkspaceMember } from '@/settings/roles/role-assignment/types/RoleAssignmentConfirmationModalSelectedWorkspaceMember';
|
||||
import { useUpdateWorkspaceMemberRole } from '@/settings/roles/hooks/useUpdateWorkspaceMemberRole';
|
||||
import { SettingsRoleAssignmentConfirmationModal } from '@/settings/roles/role-assignment/components/SettingsRoleAssignmentConfirmationModal';
|
||||
import { SettingsRoleAssignmentTableHeader } from '@/settings/roles/role-assignment/components/SettingsRoleAssignmentTableHeader';
|
||||
import { SettingsRoleAssignmentWorkspaceMemberPickerDropdown } from '@/settings/roles/role-assignment/components/SettingsRoleAssignmentWorkspaceMemberPickerDropdown';
|
||||
import { SettingsRoleAssignmentConfirmationModalSelectedWorkspaceMember } from '@/settings/roles/role-assignment/types/SettingsRoleAssignmentConfirmationModalSelectedWorkspaceMember';
|
||||
import { settingsAllRolesSelector } from '@/settings/roles/states/settingsAllRolesSelector';
|
||||
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { TextInput } from '@/ui/input/components/TextInput';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
@@ -26,14 +30,8 @@ import {
|
||||
SearchRecord,
|
||||
WorkspaceMember,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import {
|
||||
GetRolesDocument,
|
||||
useGetRolesQuery,
|
||||
useUpdateWorkspaceMemberRoleMutation,
|
||||
} from '~/generated/graphql';
|
||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||
import { RoleAssignmentConfirmationModal } from './RoleAssignmentConfirmationModal';
|
||||
import { RoleAssignmentTableRow } from './RoleAssignmentTableRow';
|
||||
import { SettingsRoleAssignmentTableRow } from './SettingsRoleAssignmentTableRow';
|
||||
|
||||
const StyledAssignToMemberContainer = styled.div`
|
||||
display: flex;
|
||||
@@ -67,43 +65,51 @@ const StyledNoMembers = styled(TableCell)`
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
`;
|
||||
|
||||
type RoleAssignmentProps = {
|
||||
role: Pick<Role, 'id' | 'label' | 'canUpdateAllSettings'> & {
|
||||
workspaceMembers: Array<WorkspaceMember>;
|
||||
};
|
||||
type SettingsRoleAssignmentProps = {
|
||||
roleId: string;
|
||||
isCreateMode?: boolean;
|
||||
};
|
||||
|
||||
export const RoleAssignment = ({ role }: RoleAssignmentProps) => {
|
||||
export const SettingsRoleAssignment = ({
|
||||
roleId,
|
||||
isCreateMode,
|
||||
}: SettingsRoleAssignmentProps) => {
|
||||
const settingsDraftRole = useRecoilValue(
|
||||
settingsDraftRoleFamilyState(roleId),
|
||||
);
|
||||
|
||||
const navigateSettings = useNavigateSettings();
|
||||
const [updateWorkspaceMemberRole] = useUpdateWorkspaceMemberRoleMutation({
|
||||
refetchQueries: [GetRolesDocument],
|
||||
});
|
||||
const {
|
||||
addWorkspaceMemberToRoleAndUpdateState,
|
||||
updateWorkspaceMemberRoleDraftState,
|
||||
} = useUpdateWorkspaceMemberRole(roleId);
|
||||
|
||||
const [confirmationModalIsOpen, setConfirmationModalIsOpen] =
|
||||
useState<boolean>(false);
|
||||
const [selectedWorkspaceMember, setSelectedWorkspaceMember] =
|
||||
useState<RoleAssignmentConfirmationModalSelectedWorkspaceMember | null>(
|
||||
useState<SettingsRoleAssignmentConfirmationModalSelectedWorkspaceMember | null>(
|
||||
null,
|
||||
);
|
||||
const { data: rolesData } = useGetRolesQuery();
|
||||
const { closeDropdown } = useDropdown('role-member-select');
|
||||
const [searchFilter, setSearchFilter] = useState('');
|
||||
const currentWorkspaceMembers = useRecoilValue(currentWorkspaceMembersState);
|
||||
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
||||
|
||||
const settingsAllRoles = useRecoilValue(settingsAllRolesSelector);
|
||||
|
||||
const workspaceMemberRoleMap = new Map<
|
||||
string,
|
||||
{ id: string; label: string }
|
||||
>();
|
||||
rolesData?.getRoles?.forEach((role) => {
|
||||
role.workspaceMembers.forEach((member) => {
|
||||
settingsAllRoles.forEach((role: Role) => {
|
||||
role.workspaceMembers.forEach((member: WorkspaceMember) => {
|
||||
workspaceMemberRoleMap.set(member.id, { id: role.id, label: role.label });
|
||||
});
|
||||
});
|
||||
|
||||
const filteredWorkspaceMembers = !searchFilter
|
||||
? role.workspaceMembers
|
||||
: role.workspaceMembers.filter((member) => {
|
||||
? settingsDraftRole.workspaceMembers
|
||||
: settingsDraftRole.workspaceMembers.filter((member) => {
|
||||
const searchTerm = searchFilter.toLowerCase();
|
||||
const firstName = member.name.firstName?.toLowerCase() || '';
|
||||
const lastName = member.name.lastName?.toLowerCase() || '';
|
||||
@@ -116,7 +122,7 @@ export const RoleAssignment = ({ role }: RoleAssignmentProps) => {
|
||||
);
|
||||
});
|
||||
|
||||
const assignedWorkspaceMemberIds = role.workspaceMembers.map(
|
||||
const assignedWorkspaceMemberIds = settingsDraftRole.workspaceMembers.map(
|
||||
(workspaceMember) => workspaceMember.id,
|
||||
);
|
||||
|
||||
@@ -153,12 +159,28 @@ export const RoleAssignment = ({ role }: RoleAssignmentProps) => {
|
||||
const handleConfirm = async () => {
|
||||
if (!selectedWorkspaceMember || !confirmationModalIsOpen) return;
|
||||
|
||||
await updateWorkspaceMemberRole({
|
||||
variables: {
|
||||
if (!isCreateMode) {
|
||||
await addWorkspaceMemberToRoleAndUpdateState({
|
||||
workspaceMemberId: selectedWorkspaceMember.id,
|
||||
roleId: role.id,
|
||||
},
|
||||
});
|
||||
});
|
||||
} else {
|
||||
const workspaceMember = currentWorkspaceMembers.find(
|
||||
(member) => member.id === selectedWorkspaceMember.id,
|
||||
);
|
||||
|
||||
if (!workspaceMember) {
|
||||
throw new Error('Workspace member not found');
|
||||
}
|
||||
|
||||
updateWorkspaceMemberRoleDraftState({
|
||||
workspaceMember: {
|
||||
id: workspaceMember.id,
|
||||
name: workspaceMember.name,
|
||||
colorScheme: '',
|
||||
userEmail: '',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
handleModalClose();
|
||||
};
|
||||
@@ -190,11 +212,11 @@ export const RoleAssignment = ({ role }: RoleAssignmentProps) => {
|
||||
/>
|
||||
</StyledSearchContainer>
|
||||
<StyledTable>
|
||||
<RoleAssignmentTableHeader />
|
||||
<SettingsRoleAssignmentTableHeader />
|
||||
<StyledTableRows>
|
||||
{filteredWorkspaceMembers.length > 0 ? (
|
||||
filteredWorkspaceMembers.map((workspaceMember) => (
|
||||
<RoleAssignmentTableRow
|
||||
<SettingsRoleAssignmentTableRow
|
||||
key={workspaceMember.id}
|
||||
workspaceMember={workspaceMember}
|
||||
/>
|
||||
@@ -233,7 +255,7 @@ export const RoleAssignment = ({ role }: RoleAssignmentProps) => {
|
||||
</>
|
||||
}
|
||||
dropdownComponents={
|
||||
<RoleAssignmentWorkspaceMemberPickerDropdown
|
||||
<SettingsRoleAssignmentWorkspaceMemberPickerDropdown
|
||||
excludedWorkspaceMemberIds={[
|
||||
...assignedWorkspaceMemberIds,
|
||||
currentWorkspaceMember?.id,
|
||||
@@ -246,7 +268,7 @@ export const RoleAssignment = ({ role }: RoleAssignmentProps) => {
|
||||
</Section>
|
||||
|
||||
{confirmationModalIsOpen && selectedWorkspaceMember && (
|
||||
<RoleAssignmentConfirmationModal
|
||||
<SettingsRoleAssignmentConfirmationModal
|
||||
selectedWorkspaceMember={selectedWorkspaceMember}
|
||||
isOpen={true}
|
||||
onClose={handleModalClose}
|
||||
+7
-7
@@ -1,23 +1,23 @@
|
||||
import { RoleAssignmentConfirmationModalSubtitle } from '@/settings/roles/role-assignment/components/RoleAssignmentConfirmationModalSubtitle';
|
||||
import { RoleAssignmentConfirmationModalSelectedWorkspaceMember } from '@/settings/roles/role-assignment/types/RoleAssignmentConfirmationModalSelectedWorkspaceMember';
|
||||
import { SettingsRoleAssignmentConfirmationModalSubtitle } from '@/settings/roles/role-assignment/components/SettingsRoleAssignmentConfirmationModalSubtitle';
|
||||
import { SettingsRoleAssignmentConfirmationModalSelectedWorkspaceMember } from '@/settings/roles/role-assignment/types/SettingsRoleAssignmentConfirmationModalSelectedWorkspaceMember';
|
||||
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
|
||||
import { t } from '@lingui/core/macro';
|
||||
|
||||
type RoleAssignmentConfirmationModalProps = {
|
||||
selectedWorkspaceMember: RoleAssignmentConfirmationModalSelectedWorkspaceMember;
|
||||
type SettingsRoleAssignmentConfirmationModalProps = {
|
||||
selectedWorkspaceMember: SettingsRoleAssignmentConfirmationModalSelectedWorkspaceMember;
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onConfirm: () => void;
|
||||
onRoleClick: (roleId: string) => void;
|
||||
};
|
||||
|
||||
export const RoleAssignmentConfirmationModal = ({
|
||||
export const SettingsRoleAssignmentConfirmationModal = ({
|
||||
selectedWorkspaceMember,
|
||||
isOpen,
|
||||
onClose,
|
||||
onConfirm,
|
||||
onRoleClick,
|
||||
}: RoleAssignmentConfirmationModalProps) => {
|
||||
}: SettingsRoleAssignmentConfirmationModalProps) => {
|
||||
const workspaceMemberName = selectedWorkspaceMember.name;
|
||||
|
||||
const title = t`Assign ${workspaceMemberName}?`;
|
||||
@@ -28,7 +28,7 @@ export const RoleAssignmentConfirmationModal = ({
|
||||
setIsOpen={onClose}
|
||||
title={title}
|
||||
subtitle={
|
||||
<RoleAssignmentConfirmationModalSubtitle
|
||||
<SettingsRoleAssignmentConfirmationModalSubtitle
|
||||
selectedWorkspaceMember={selectedWorkspaceMember}
|
||||
onRoleClick={onRoleClick}
|
||||
/>
|
||||
+5
-5
@@ -1,5 +1,5 @@
|
||||
import { SettingsCard } from '@/settings/components/SettingsCard';
|
||||
import { RoleAssignmentConfirmationModalSelectedWorkspaceMember } from '@/settings/roles/role-assignment/types/RoleAssignmentConfirmationModalSelectedWorkspaceMember';
|
||||
import { SettingsRoleAssignmentConfirmationModalSelectedWorkspaceMember } from '@/settings/roles/role-assignment/types/SettingsRoleAssignmentConfirmationModalSelectedWorkspaceMember';
|
||||
import styled from '@emotion/styled';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { Avatar } from 'twenty-ui';
|
||||
@@ -8,15 +8,15 @@ const StyledSettingsCardContainer = styled.div`
|
||||
margin-top: ${({ theme }) => theme.spacing(6)};
|
||||
`;
|
||||
|
||||
type RoleAssignmentConfirmationModalSubtitleProps = {
|
||||
selectedWorkspaceMember: RoleAssignmentConfirmationModalSelectedWorkspaceMember;
|
||||
type SettingsRoleAssignmentConfirmationModalSubtitleProps = {
|
||||
selectedWorkspaceMember: SettingsRoleAssignmentConfirmationModalSelectedWorkspaceMember;
|
||||
onRoleClick: (roleId: string) => void;
|
||||
};
|
||||
|
||||
export const RoleAssignmentConfirmationModalSubtitle = ({
|
||||
export const SettingsRoleAssignmentConfirmationModalSubtitle = ({
|
||||
selectedWorkspaceMember,
|
||||
onRoleClick,
|
||||
}: RoleAssignmentConfirmationModalSubtitleProps) => {
|
||||
}: SettingsRoleAssignmentConfirmationModalSubtitleProps) => {
|
||||
const workspaceMemberName = selectedWorkspaceMember.name;
|
||||
|
||||
return (
|
||||
+1
-1
@@ -2,7 +2,7 @@ import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import { t } from '@lingui/core/macro';
|
||||
|
||||
export const RoleAssignmentTableHeader = () => (
|
||||
export const SettingsRoleAssignmentTableHeader = () => (
|
||||
<TableRow gridAutoColumns="2fr 4fr">
|
||||
<TableHeader>{t`Name`}</TableHeader>
|
||||
<TableHeader>{t`Email`}</TableHeader>
|
||||
+3
-3
@@ -28,13 +28,13 @@ const StyledTableCell = styled(TableCell)`
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
type RoleAssignmentTableRowProps = {
|
||||
type SettingsRoleAssignmentTableRowProps = {
|
||||
workspaceMember: WorkspaceMember;
|
||||
};
|
||||
|
||||
export const RoleAssignmentTableRow = ({
|
||||
export const SettingsRoleAssignmentTableRow = ({
|
||||
workspaceMember,
|
||||
}: RoleAssignmentTableRowProps) => {
|
||||
}: SettingsRoleAssignmentTableRowProps) => {
|
||||
return (
|
||||
<TableRow gridAutoColumns="2fr 4fr">
|
||||
<StyledTableCell>
|
||||
+5
-5
@@ -1,6 +1,6 @@
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useObjectRecordSearchRecords } from '@/object-record/hooks/useObjectRecordSearchRecords';
|
||||
import { RoleAssignmentWorkspaceMemberPickerDropdownContent } from '@/settings/roles/role-assignment/components/RoleAssignmentWorkspaceMemberPickerDropdownContent';
|
||||
import { SettingsRoleAssignmentWorkspaceMemberPickerDropdownContent } from '@/settings/roles/role-assignment/components/SettingsRoleAssignmentWorkspaceMemberPickerDropdownContent';
|
||||
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
|
||||
@@ -9,15 +9,15 @@ import { useLingui } from '@lingui/react/macro';
|
||||
import { ChangeEvent, useState } from 'react';
|
||||
import { SearchRecord } from '~/generated-metadata/graphql';
|
||||
|
||||
type RoleAssignmentWorkspaceMemberPickerDropdownProps = {
|
||||
type SettingsRoleAssignmentWorkspaceMemberPickerDropdownProps = {
|
||||
excludedWorkspaceMemberIds: string[];
|
||||
onSelect: (workspaceMemberSearchRecord: SearchRecord) => void;
|
||||
};
|
||||
|
||||
export const RoleAssignmentWorkspaceMemberPickerDropdown = ({
|
||||
export const SettingsRoleAssignmentWorkspaceMemberPickerDropdown = ({
|
||||
excludedWorkspaceMemberIds,
|
||||
onSelect,
|
||||
}: RoleAssignmentWorkspaceMemberPickerDropdownProps) => {
|
||||
}: SettingsRoleAssignmentWorkspaceMemberPickerDropdownProps) => {
|
||||
const [searchFilter, setSearchFilter] = useState('');
|
||||
|
||||
const { loading, searchRecords: workspaceMembers } =
|
||||
@@ -46,7 +46,7 @@ export const RoleAssignmentWorkspaceMemberPickerDropdown = ({
|
||||
/>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer hasMaxHeight>
|
||||
<RoleAssignmentWorkspaceMemberPickerDropdownContent
|
||||
<SettingsRoleAssignmentWorkspaceMemberPickerDropdownContent
|
||||
loading={loading}
|
||||
searchFilter={searchFilter}
|
||||
filteredWorkspaceMembers={filteredWorkspaceMembers}
|
||||
+3
-3
@@ -2,19 +2,19 @@ import { t } from '@lingui/core/macro';
|
||||
import { MenuItem, MenuItemAvatar } from 'twenty-ui';
|
||||
import { SearchRecord } from '~/generated-metadata/graphql';
|
||||
|
||||
type RoleAssignmentWorkspaceMemberPickerDropdownContentProps = {
|
||||
type SettingsRoleAssignmentWorkspaceMemberPickerDropdownContentProps = {
|
||||
loading: boolean;
|
||||
searchFilter: string;
|
||||
filteredWorkspaceMembers: SearchRecord[];
|
||||
onSelect: (workspaceMemberSearchRecord: SearchRecord) => void;
|
||||
};
|
||||
|
||||
export const RoleAssignmentWorkspaceMemberPickerDropdownContent = ({
|
||||
export const SettingsRoleAssignmentWorkspaceMemberPickerDropdownContent = ({
|
||||
loading,
|
||||
searchFilter,
|
||||
filteredWorkspaceMembers,
|
||||
onSelect,
|
||||
}: RoleAssignmentWorkspaceMemberPickerDropdownContentProps) => {
|
||||
}: SettingsRoleAssignmentWorkspaceMemberPickerDropdownContentProps) => {
|
||||
if (loading) {
|
||||
return null;
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
import { SettingsRoleAssignment } from '@/settings/roles/role-assignment/components/SettingsRoleAssignment';
|
||||
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { ComponentDecorator, RouterDecorator } from 'twenty-ui';
|
||||
import { PENDING_ROLE_ID } from '~/pages/settings/roles/SettingsRoleCreate';
|
||||
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
||||
import { getRolesMock } from '~/testing/mock-data/roles';
|
||||
|
||||
const SettingsRoleAssignmentWrapper = (
|
||||
args: React.ComponentProps<typeof SettingsRoleAssignment>,
|
||||
) => {
|
||||
const setDraftRole = useSetRecoilState(
|
||||
settingsDraftRoleFamilyState(args.roleId),
|
||||
);
|
||||
|
||||
const role = getRolesMock().find((role) => role.id === args.roleId);
|
||||
|
||||
if (isDefined(role)) {
|
||||
setDraftRole(role);
|
||||
}
|
||||
|
||||
return <SettingsRoleAssignment roleId={args.roleId} />;
|
||||
};
|
||||
|
||||
const meta: Meta<typeof SettingsRoleAssignmentWrapper> = {
|
||||
title: 'Modules/Settings/Roles/RoleAssignment/SettingsRoleAssignment',
|
||||
component: SettingsRoleAssignmentWrapper,
|
||||
decorators: [RouterDecorator, ComponentDecorator, I18nFrontDecorator],
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof SettingsRoleAssignmentWrapper>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
roleId: '1',
|
||||
},
|
||||
};
|
||||
|
||||
export const PendingRole: Story = {
|
||||
args: {
|
||||
roleId: PENDING_ROLE_ID,
|
||||
},
|
||||
};
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
export type RoleAssignmentConfirmationModalSelectedWorkspaceMember = {
|
||||
id: string;
|
||||
name: string;
|
||||
role?: { id: string; label: string };
|
||||
avatarUrl?: string | null;
|
||||
};
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
export type SettingsRoleAssignmentConfirmationModalSelectedWorkspaceMember = {
|
||||
id: string;
|
||||
name: string;
|
||||
role?: { id: string; label: string };
|
||||
avatarUrl?: string | null;
|
||||
colorScheme?: string;
|
||||
userEmail?: string;
|
||||
};
|
||||
Reference in New Issue
Block a user