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:
@ -0,0 +1,198 @@
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import { useUpdateWorkspaceMemberRole } from '@/settings/roles/hooks/useUpdateWorkspaceMemberRole';
|
||||
import { SettingsRoleAssignment } from '@/settings/roles/role-assignment/components/SettingsRoleAssignment';
|
||||
import { SettingsRolePermissions } from '@/settings/roles/role-permissions/components/SettingsRolePermissions';
|
||||
import { SettingsRoleSettings } from '@/settings/roles/role-settings/components/SettingsRoleSettings';
|
||||
import { SettingsRoleLabelContainer } from '@/settings/roles/role/components/SettingsRoleLabelContainer';
|
||||
import { SETTINGS_ROLE_DETAIL_TABS } from '@/settings/roles/role/constants/SettingsRoleDetailTabs';
|
||||
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
|
||||
import { settingsPersistedRoleFamilyState } from '@/settings/roles/states/settingsPersistedRoleFamilyState';
|
||||
import { settingsRolesIsLoadingState } from '@/settings/roles/states/settingsRolesIsLoadingState';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
||||
import { TabList } from '@/ui/layout/tab/components/TabList';
|
||||
import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Button, IconLockOpen, IconSettings, IconUserPlus } from 'twenty-ui';
|
||||
import { v4 } from 'uuid';
|
||||
import {
|
||||
FeatureFlagKey,
|
||||
useCreateOneRoleMutation,
|
||||
useUpdateOneRoleMutation,
|
||||
} from '~/generated/graphql';
|
||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
||||
|
||||
type SettingsRoleProps = {
|
||||
roleId: string;
|
||||
isCreateMode: boolean;
|
||||
};
|
||||
|
||||
export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => {
|
||||
const activeTabId = useRecoilComponentValueV2(
|
||||
activeTabIdComponentState,
|
||||
SETTINGS_ROLE_DETAIL_TABS.COMPONENT_INSTANCE_ID,
|
||||
);
|
||||
|
||||
const isPermissionsV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsPermissionsV2Enabled,
|
||||
);
|
||||
|
||||
const navigateSettings = useNavigateSettings();
|
||||
|
||||
const [createRole] = useCreateOneRoleMutation();
|
||||
const [updateRole] = useUpdateOneRoleMutation();
|
||||
|
||||
const settingsDraftRole = useRecoilValue(
|
||||
settingsDraftRoleFamilyState(roleId),
|
||||
);
|
||||
|
||||
const settingsPersistedRole = useRecoilValue(
|
||||
settingsPersistedRoleFamilyState(roleId),
|
||||
);
|
||||
|
||||
const { addWorkspaceMembersToRole } = useUpdateWorkspaceMemberRole(roleId);
|
||||
|
||||
const settingsRolesIsLoading = useRecoilValue(settingsRolesIsLoadingState);
|
||||
|
||||
if (!isDefined(settingsRolesIsLoading)) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const isRoleEditable = isPermissionsV2Enabled && settingsDraftRole.isEditable;
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
id: SETTINGS_ROLE_DETAIL_TABS.TABS_IDS.ASSIGNMENT,
|
||||
title: t`Assignment`,
|
||||
Icon: IconUserPlus,
|
||||
},
|
||||
{
|
||||
id: SETTINGS_ROLE_DETAIL_TABS.TABS_IDS.PERMISSIONS,
|
||||
title: t`Permissions`,
|
||||
Icon: IconLockOpen,
|
||||
},
|
||||
{
|
||||
id: SETTINGS_ROLE_DETAIL_TABS.TABS_IDS.SETTINGS,
|
||||
title: t`Settings`,
|
||||
Icon: IconSettings,
|
||||
},
|
||||
];
|
||||
|
||||
const isDirty = !isDeeplyEqual(settingsDraftRole, settingsPersistedRole);
|
||||
|
||||
const handleSave = () => {
|
||||
if (isCreateMode) {
|
||||
const roleId = v4();
|
||||
|
||||
createRole({
|
||||
variables: {
|
||||
createRoleInput: {
|
||||
id: roleId,
|
||||
label: settingsDraftRole.label,
|
||||
description: settingsDraftRole.description,
|
||||
icon: settingsDraftRole.icon,
|
||||
canUpdateAllSettings: settingsDraftRole.canUpdateAllSettings,
|
||||
canReadAllObjectRecords: settingsDraftRole.canReadAllObjectRecords,
|
||||
canUpdateAllObjectRecords:
|
||||
settingsDraftRole.canUpdateAllObjectRecords,
|
||||
canSoftDeleteAllObjectRecords:
|
||||
settingsDraftRole.canSoftDeleteAllObjectRecords,
|
||||
canDestroyAllObjectRecords:
|
||||
settingsDraftRole.canDestroyAllObjectRecords,
|
||||
},
|
||||
},
|
||||
onCompleted: async (data) => {
|
||||
await addWorkspaceMembersToRole({
|
||||
roleId: data.createOneRole.id,
|
||||
workspaceMemberIds: settingsDraftRole.workspaceMembers.map(
|
||||
(member) => member.id,
|
||||
),
|
||||
});
|
||||
|
||||
navigateSettings(SettingsPath.RoleDetail, {
|
||||
roleId: data.createOneRole.id,
|
||||
});
|
||||
},
|
||||
});
|
||||
} else {
|
||||
updateRole({
|
||||
variables: {
|
||||
updateRoleInput: {
|
||||
id: roleId,
|
||||
update: {
|
||||
label: settingsDraftRole.label,
|
||||
description: settingsDraftRole.description,
|
||||
icon: settingsDraftRole.icon,
|
||||
canUpdateAllSettings: settingsDraftRole.canUpdateAllSettings,
|
||||
canReadAllObjectRecords:
|
||||
settingsDraftRole.canReadAllObjectRecords,
|
||||
canUpdateAllObjectRecords:
|
||||
settingsDraftRole.canUpdateAllObjectRecords,
|
||||
canSoftDeleteAllObjectRecords:
|
||||
settingsDraftRole.canSoftDeleteAllObjectRecords,
|
||||
canDestroyAllObjectRecords:
|
||||
settingsDraftRole.canDestroyAllObjectRecords,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<SubMenuTopBarContainer
|
||||
title={<SettingsRoleLabelContainer roleId={roleId} />}
|
||||
links={[
|
||||
{
|
||||
children: 'Workspace',
|
||||
href: getSettingsPath(SettingsPath.Workspace),
|
||||
},
|
||||
{
|
||||
children: 'Roles',
|
||||
href: getSettingsPath(SettingsPath.Roles),
|
||||
},
|
||||
{
|
||||
children: settingsDraftRole.label,
|
||||
},
|
||||
]}
|
||||
actionButton={
|
||||
isDirty && (
|
||||
<Button
|
||||
title={isCreateMode ? t`Create` : t`Save`}
|
||||
variant="primary"
|
||||
size="small"
|
||||
accent="blue"
|
||||
onClick={handleSave}
|
||||
disabled={!isRoleEditable}
|
||||
/>
|
||||
)
|
||||
}
|
||||
>
|
||||
<SettingsPageContainer>
|
||||
<TabList
|
||||
tabs={tabs}
|
||||
className="tab-list"
|
||||
componentInstanceId={SETTINGS_ROLE_DETAIL_TABS.COMPONENT_INSTANCE_ID}
|
||||
/>
|
||||
{activeTabId === SETTINGS_ROLE_DETAIL_TABS.TABS_IDS.ASSIGNMENT && (
|
||||
<SettingsRoleAssignment roleId={roleId} isCreateMode={isCreateMode} />
|
||||
)}
|
||||
{activeTabId === SETTINGS_ROLE_DETAIL_TABS.TABS_IDS.PERMISSIONS && (
|
||||
<SettingsRolePermissions
|
||||
roleId={roleId}
|
||||
isEditable={isRoleEditable}
|
||||
/>
|
||||
)}
|
||||
{activeTabId === SETTINGS_ROLE_DETAIL_TABS.TABS_IDS.SETTINGS && (
|
||||
<SettingsRoleSettings roleId={roleId} isEditable={isRoleEditable} />
|
||||
)}
|
||||
</SettingsPageContainer>
|
||||
</SubMenuTopBarContainer>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,45 @@
|
||||
import { SETTINGS_ROLE_DETAIL_TABS } from '@/settings/roles/role/constants/SettingsRoleDetailTabs';
|
||||
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
|
||||
import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
|
||||
export const SettingsRoleCreateEffect = ({ roleId }: { roleId: string }) => {
|
||||
const setSettingsDraftRole = useSetRecoilState(
|
||||
settingsDraftRoleFamilyState(roleId),
|
||||
);
|
||||
const setActiveTabId = useSetRecoilComponentStateV2(
|
||||
activeTabIdComponentState,
|
||||
SETTINGS_ROLE_DETAIL_TABS.COMPONENT_INSTANCE_ID,
|
||||
);
|
||||
|
||||
const [isInitialized, setIsInitialized] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (isInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
setActiveTabId(SETTINGS_ROLE_DETAIL_TABS.TABS_IDS.PERMISSIONS);
|
||||
|
||||
const newRole = {
|
||||
id: roleId,
|
||||
label: '',
|
||||
description: '',
|
||||
icon: 'IconUser',
|
||||
canUpdateAllSettings: false,
|
||||
canReadAllObjectRecords: false,
|
||||
canUpdateAllObjectRecords: false,
|
||||
canSoftDeleteAllObjectRecords: false,
|
||||
canDestroyAllObjectRecords: false,
|
||||
isEditable: true,
|
||||
workspaceMembers: [],
|
||||
};
|
||||
|
||||
setSettingsDraftRole(newRole);
|
||||
setIsInitialized(true);
|
||||
}, [isInitialized, roleId, setActiveTabId, setSettingsDraftRole]);
|
||||
|
||||
return null;
|
||||
};
|
||||
@ -0,0 +1,40 @@
|
||||
import { SETTINGS_ROLE_DETAIL_TABS } from '@/settings/roles/role/constants/SettingsRoleDetailTabs';
|
||||
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
|
||||
import { settingsPersistedRoleFamilyState } from '@/settings/roles/states/settingsPersistedRoleFamilyState';
|
||||
import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
type SettingsRoleEditEffectProps = {
|
||||
roleId: string;
|
||||
};
|
||||
|
||||
export const SettingsRoleEditEffect = ({
|
||||
roleId,
|
||||
}: SettingsRoleEditEffectProps) => {
|
||||
const [isInitialized, setIsInitialized] = useState(false);
|
||||
|
||||
const role = useRecoilValue(settingsPersistedRoleFamilyState(roleId));
|
||||
const setDraftRole = useSetRecoilState(settingsDraftRoleFamilyState(roleId));
|
||||
const setActiveTabId = useSetRecoilComponentStateV2(
|
||||
activeTabIdComponentState,
|
||||
SETTINGS_ROLE_DETAIL_TABS.COMPONENT_INSTANCE_ID,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (isInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
setActiveTabId(SETTINGS_ROLE_DETAIL_TABS.TABS_IDS.ASSIGNMENT);
|
||||
|
||||
if (isDefined(role)) {
|
||||
setDraftRole(role);
|
||||
setIsInitialized(true);
|
||||
}
|
||||
}, [isInitialized, role, setActiveTabId, setDraftRole]);
|
||||
|
||||
return <></>;
|
||||
};
|
||||
@ -0,0 +1,50 @@
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
|
||||
import { TitleInput } from '@/ui/input/components/TitleInput';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
type SettingsRoleLabelContainerProps = {
|
||||
roleId: string;
|
||||
};
|
||||
|
||||
const ROLE_LABEL_EDIT_HOTKEY_SCOPE = 'role-label-edit';
|
||||
|
||||
const StyledHeaderTitle = styled.div`
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
font-weight: ${({ theme }) => theme.font.weight.semiBold};
|
||||
font-size: ${({ theme }) => theme.font.size.lg};
|
||||
width: fit-content;
|
||||
max-width: 420px;
|
||||
& > input:disabled {
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
}
|
||||
`;
|
||||
|
||||
export const SettingsRoleLabelContainer = ({
|
||||
roleId,
|
||||
}: SettingsRoleLabelContainerProps) => {
|
||||
const [settingsDraftRole, setSettingsDraftRole] = useRecoilState(
|
||||
settingsDraftRoleFamilyState(roleId),
|
||||
);
|
||||
|
||||
const handleChange = (newValue: string) => {
|
||||
setSettingsDraftRole({
|
||||
...settingsDraftRole,
|
||||
label: newValue,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledHeaderTitle>
|
||||
<TitleInput
|
||||
disabled={!settingsDraftRole.isEditable}
|
||||
sizeVariant="md"
|
||||
value={settingsDraftRole.label}
|
||||
onChange={handleChange}
|
||||
placeholder="Role name"
|
||||
hotkeyScope={ROLE_LABEL_EDIT_HOTKEY_SCOPE}
|
||||
/>
|
||||
</StyledHeaderTitle>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,8 @@
|
||||
export const SETTINGS_ROLE_DETAIL_TABS = {
|
||||
COMPONENT_INSTANCE_ID: 'settings-role-detail-tabs',
|
||||
TABS_IDS: {
|
||||
ASSIGNMENT: 'assignment',
|
||||
PERMISSIONS: 'permissions',
|
||||
SETTINGS: 'settings',
|
||||
},
|
||||
} as const;
|
||||
Reference in New Issue
Block a user