Add object level permission permissions to role page (ReadOnly) (#11568)
## Context This PR adds the display of object-level permissions. A following PR will add the ability to update those permissions. The PR contains the SettingsRoleObjectLevel page but it's not fully implemented yet (save won't trigger the corresponding mutation) <img width="616" alt="Screenshot 2025-04-14 at 18 02 40" src="https://github.com/user-attachments/assets/f8c58193-31f3-468a-a96d-f06a9f2e1423" />
This commit is contained in:
@ -1891,6 +1891,7 @@ export type Role = {
|
|||||||
id: Scalars['String']['output'];
|
id: Scalars['String']['output'];
|
||||||
isEditable: Scalars['Boolean']['output'];
|
isEditable: Scalars['Boolean']['output'];
|
||||||
label: Scalars['String']['output'];
|
label: Scalars['String']['output'];
|
||||||
|
objectPermissions?: Maybe<Array<ObjectPermission>>;
|
||||||
settingPermissions?: Maybe<Array<SettingPermission>>;
|
settingPermissions?: Maybe<Array<SettingPermission>>;
|
||||||
workspaceMembers: Array<WorkspaceMember>;
|
workspaceMembers: Array<WorkspaceMember>;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1693,6 +1693,7 @@ export type Role = {
|
|||||||
id: Scalars['String'];
|
id: Scalars['String'];
|
||||||
isEditable: Scalars['Boolean'];
|
isEditable: Scalars['Boolean'];
|
||||||
label: Scalars['String'];
|
label: Scalars['String'];
|
||||||
|
objectPermissions?: Maybe<Array<ObjectPermission>>;
|
||||||
settingPermissions?: Maybe<Array<SettingPermission>>;
|
settingPermissions?: Maybe<Array<SettingPermission>>;
|
||||||
workspaceMembers: Array<WorkspaceMember>;
|
workspaceMembers: Array<WorkspaceMember>;
|
||||||
};
|
};
|
||||||
@ -2668,6 +2669,8 @@ export type UpdateLabPublicFeatureFlagMutationVariables = Exact<{
|
|||||||
|
|
||||||
export type UpdateLabPublicFeatureFlagMutation = { __typename?: 'Mutation', updateLabPublicFeatureFlag: { __typename?: 'FeatureFlagDTO', key: FeatureFlagKey, value: boolean } };
|
export type UpdateLabPublicFeatureFlagMutation = { __typename?: 'Mutation', updateLabPublicFeatureFlag: { __typename?: 'FeatureFlagDTO', key: FeatureFlagKey, value: boolean } };
|
||||||
|
|
||||||
|
export type ObjectPermissionFragmentFragment = { __typename?: 'ObjectPermission', id: string, objectMetadataId: string, roleId: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null };
|
||||||
|
|
||||||
export type RoleFragmentFragment = { __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean };
|
export type RoleFragmentFragment = { __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean };
|
||||||
|
|
||||||
export type SettingPermissionFragmentFragment = { __typename?: 'SettingPermission', id: string, setting: SettingPermissionType, roleId: string };
|
export type SettingPermissionFragmentFragment = { __typename?: 'SettingPermission', id: string, setting: SettingPermissionType, roleId: string };
|
||||||
@ -2704,7 +2707,7 @@ export type UpsertSettingPermissionsMutation = { __typename?: 'Mutation', upsert
|
|||||||
export type GetRolesQueryVariables = Exact<{ [key: string]: never; }>;
|
export type GetRolesQueryVariables = Exact<{ [key: string]: never; }>;
|
||||||
|
|
||||||
|
|
||||||
export type GetRolesQuery = { __typename?: 'Query', getRoles: Array<{ __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean, workspaceMembers: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }>, settingPermissions?: Array<{ __typename?: 'SettingPermission', id: string, setting: SettingPermissionType, roleId: string }> | null }> };
|
export type GetRolesQuery = { __typename?: 'Query', getRoles: Array<{ __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean, workspaceMembers: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }>, settingPermissions?: Array<{ __typename?: 'SettingPermission', id: string, setting: SettingPermissionType, roleId: string }> | null, objectPermissions?: Array<{ __typename?: 'ObjectPermission', id: string, objectMetadataId: string, roleId: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null }> | null }> };
|
||||||
|
|
||||||
export type CreateApprovedAccessDomainMutationVariables = Exact<{
|
export type CreateApprovedAccessDomainMutationVariables = Exact<{
|
||||||
input: CreateApprovedAccessDomainInput;
|
input: CreateApprovedAccessDomainInput;
|
||||||
@ -3019,6 +3022,17 @@ export const AvailableSsoIdentityProvidersFragmentFragmentDoc = gql`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
export const ObjectPermissionFragmentFragmentDoc = gql`
|
||||||
|
fragment ObjectPermissionFragment on ObjectPermission {
|
||||||
|
id
|
||||||
|
objectMetadataId
|
||||||
|
roleId
|
||||||
|
canReadObjectRecords
|
||||||
|
canUpdateObjectRecords
|
||||||
|
canSoftDeleteObjectRecords
|
||||||
|
canDestroyObjectRecords
|
||||||
|
}
|
||||||
|
`;
|
||||||
export const SettingPermissionFragmentFragmentDoc = gql`
|
export const SettingPermissionFragmentFragmentDoc = gql`
|
||||||
fragment SettingPermissionFragment on SettingPermission {
|
fragment SettingPermissionFragment on SettingPermission {
|
||||||
id
|
id
|
||||||
@ -4984,11 +4998,15 @@ export const GetRolesDocument = gql`
|
|||||||
settingPermissions {
|
settingPermissions {
|
||||||
...SettingPermissionFragment
|
...SettingPermissionFragment
|
||||||
}
|
}
|
||||||
|
objectPermissions {
|
||||||
|
...ObjectPermissionFragment
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
${RoleFragmentFragmentDoc}
|
${RoleFragmentFragmentDoc}
|
||||||
${WorkspaceMemberQueryFragmentFragmentDoc}
|
${WorkspaceMemberQueryFragmentFragmentDoc}
|
||||||
${SettingPermissionFragmentFragmentDoc}`;
|
${SettingPermissionFragmentFragmentDoc}
|
||||||
|
${ObjectPermissionFragmentFragmentDoc}`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* __useGetRolesQuery__
|
* __useGetRolesQuery__
|
||||||
|
|||||||
@ -313,6 +313,12 @@ const SettingsRoleEdit = lazy(() =>
|
|||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const SettingsRoleObjectLevel = lazy(() =>
|
||||||
|
import('~/pages/settings/roles/SettingsRoleObjectLevel').then((module) => ({
|
||||||
|
default: module.SettingsRoleObjectLevel,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
|
||||||
type SettingsRoutesProps = {
|
type SettingsRoutesProps = {
|
||||||
isFunctionSettingsEnabled?: boolean;
|
isFunctionSettingsEnabled?: boolean;
|
||||||
isAdminPageEnabled?: boolean;
|
isAdminPageEnabled?: boolean;
|
||||||
@ -402,6 +408,10 @@ export const SettingsRoutes = ({
|
|||||||
path={SettingsPath.RoleCreate}
|
path={SettingsPath.RoleCreate}
|
||||||
element={<SettingsRoleCreate />}
|
element={<SettingsRoleCreate />}
|
||||||
/>
|
/>
|
||||||
|
<Route
|
||||||
|
path={SettingsPath.RoleObjectLevel}
|
||||||
|
element={<SettingsRoleObjectLevel />}
|
||||||
|
/>
|
||||||
</Route>
|
</Route>
|
||||||
<Route
|
<Route
|
||||||
element={
|
element={
|
||||||
|
|||||||
@ -7,15 +7,21 @@ import { Select } from '@/ui/input/components/Select';
|
|||||||
import { t } from '@lingui/core/macro';
|
import { t } from '@lingui/core/macro';
|
||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
import { H2Title, IconUserPin } from 'twenty-ui/display';
|
||||||
|
import { Card, Section } from 'twenty-ui/layout';
|
||||||
import {
|
import {
|
||||||
Role,
|
Role,
|
||||||
UpdateWorkspaceMutation,
|
UpdateWorkspaceMutation,
|
||||||
useUpdateWorkspaceMutation,
|
useUpdateWorkspaceMutation,
|
||||||
} from '~/generated/graphql';
|
} from '~/generated/graphql';
|
||||||
import { Card, Section } from 'twenty-ui/layout';
|
|
||||||
import { H2Title, IconUserPin } from 'twenty-ui/display';
|
|
||||||
|
|
||||||
export const SettingsRoleDefaultRole = ({ roles }: { roles: Role[] }) => {
|
type SettingsRoleDefaultRoleProps = {
|
||||||
|
roles: Role[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SettingsRoleDefaultRole = ({
|
||||||
|
roles,
|
||||||
|
}: SettingsRoleDefaultRoleProps) => {
|
||||||
const [updateWorkspace] = useUpdateWorkspaceMutation();
|
const [updateWorkspace] = useUpdateWorkspaceMutation();
|
||||||
|
|
||||||
const [currentWorkspace, setCurrentWorkspace] = useRecoilState(
|
const [currentWorkspace, setCurrentWorkspace] = useRecoilState(
|
||||||
|
|||||||
@ -4,8 +4,6 @@ import { TableRow } from '@/ui/layout/table/components/TableRow';
|
|||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Role } from '~/generated-metadata/graphql';
|
|
||||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
|
||||||
import {
|
import {
|
||||||
AppTooltip,
|
AppTooltip,
|
||||||
Avatar,
|
Avatar,
|
||||||
@ -14,6 +12,8 @@ import {
|
|||||||
TooltipDelay,
|
TooltipDelay,
|
||||||
useIcons,
|
useIcons,
|
||||||
} from 'twenty-ui/display';
|
} from 'twenty-ui/display';
|
||||||
|
import { Role } from '~/generated-metadata/graphql';
|
||||||
|
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
||||||
|
|
||||||
const StyledAssignedText = styled.div`
|
const StyledAssignedText = styled.div`
|
||||||
color: ${({ theme }) => theme.font.color.secondary};
|
color: ${({ theme }) => theme.font.color.secondary};
|
||||||
@ -52,15 +52,13 @@ const StyledTableRow = styled(TableRow)`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const SettingsRolesTableRow = ({ role }: { role: Role }) => {
|
type SettingsRolesTableRowProps = {
|
||||||
|
role: Role;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SettingsRolesTableRow = ({ role }: SettingsRolesTableRowProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
const navigateSettings = useNavigateSettings();
|
|
||||||
|
|
||||||
const handleRoleClick = (roleId: string) => {
|
|
||||||
navigateSettings(SettingsPath.RoleDetail, { roleId });
|
|
||||||
};
|
|
||||||
|
|
||||||
const { getIcon } = useIcons();
|
const { getIcon } = useIcons();
|
||||||
const Icon = getIcon(role.icon ?? 'IconUser');
|
const Icon = getIcon(role.icon ?? 'IconUser');
|
||||||
|
|
||||||
@ -68,7 +66,7 @@ export const SettingsRolesTableRow = ({ role }: { role: Role }) => {
|
|||||||
<StyledTableRow
|
<StyledTableRow
|
||||||
key={role.id}
|
key={role.id}
|
||||||
gridAutoColumns="332px 3fr 2fr 1fr"
|
gridAutoColumns="332px 3fr 2fr 1fr"
|
||||||
onClick={() => handleRoleClick(role.id)}
|
to={getSettingsPath(SettingsPath.RoleDetail, { roleId: role.id })}
|
||||||
>
|
>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<StyledNameCell>
|
<StyledNameCell>
|
||||||
|
|||||||
@ -0,0 +1,13 @@
|
|||||||
|
import { gql } from '@apollo/client';
|
||||||
|
|
||||||
|
export const OBJECT_PERMISSION_FRAGMENT = gql`
|
||||||
|
fragment ObjectPermissionFragment on ObjectPermission {
|
||||||
|
id
|
||||||
|
objectMetadataId
|
||||||
|
roleId
|
||||||
|
canReadObjectRecords
|
||||||
|
canUpdateObjectRecords
|
||||||
|
canSoftDeleteObjectRecords
|
||||||
|
canDestroyObjectRecords
|
||||||
|
}
|
||||||
|
`;
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { OBJECT_PERMISSION_FRAGMENT } from '@/settings/roles/graphql/fragments/objectPermissionFragment';
|
||||||
import { ROLE_FRAGMENT } from '@/settings/roles/graphql/fragments/roleFragment';
|
import { ROLE_FRAGMENT } from '@/settings/roles/graphql/fragments/roleFragment';
|
||||||
import { SETTING_PERMISSION_FRAGMENT } from '@/settings/roles/graphql/fragments/settingPermissionFragment';
|
import { SETTING_PERMISSION_FRAGMENT } from '@/settings/roles/graphql/fragments/settingPermissionFragment';
|
||||||
import { WORKSPACE_MEMBER_QUERY_FRAGMENT } from '@/workspace-member/graphql/fragments/workspaceMemberQueryFragment';
|
import { WORKSPACE_MEMBER_QUERY_FRAGMENT } from '@/workspace-member/graphql/fragments/workspaceMemberQueryFragment';
|
||||||
@ -7,6 +8,7 @@ export const GET_ROLES = gql`
|
|||||||
${WORKSPACE_MEMBER_QUERY_FRAGMENT}
|
${WORKSPACE_MEMBER_QUERY_FRAGMENT}
|
||||||
${ROLE_FRAGMENT}
|
${ROLE_FRAGMENT}
|
||||||
${SETTING_PERMISSION_FRAGMENT}
|
${SETTING_PERMISSION_FRAGMENT}
|
||||||
|
${OBJECT_PERMISSION_FRAGMENT}
|
||||||
query GetRoles {
|
query GetRoles {
|
||||||
getRoles {
|
getRoles {
|
||||||
...RoleFragment
|
...RoleFragment
|
||||||
@ -16,6 +18,9 @@ export const GET_ROLES = gql`
|
|||||||
settingPermissions {
|
settingPermissions {
|
||||||
...SettingPermissionFragment
|
...SettingPermissionFragment
|
||||||
}
|
}
|
||||||
|
objectPermissions {
|
||||||
|
...ObjectPermissionFragment
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@ -1,34 +1,9 @@
|
|||||||
import { SettingsOptionCardContentToggle } from '@/settings/components/SettingsOptions/SettingsOptionCardContentToggle';
|
import { SettingsRolePermissionsObjectLevelSection } from '@/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelSection';
|
||||||
import { SettingsRolePermissionsObjectsTableHeader } from '@/settings/roles/role-permissions/components/SettingsRolePermissionsObjectsTableHeader';
|
import { SettingsRolePermissionsObjectsSection } from '@/settings/roles/role-permissions/objects-permissions/components/SettingsRolePermissionsObjectsSection';
|
||||||
import { SettingsRolePermissionsObjectsTableRow } from '@/settings/roles/role-permissions/components/SettingsRolePermissionsObjectsTableRow';
|
import { SettingsRolePermissionsSettingsSection } from '@/settings/roles/role-permissions/settings-permissions/components/SettingsRolePermissionsSettingsSection';
|
||||||
import { SettingsRolePermissionsSettingsTableHeader } from '@/settings/roles/role-permissions/components/SettingsRolePermissionsSettingsTableHeader';
|
|
||||||
import { SettingsRolePermissionsSettingsTableRow } from '@/settings/roles/role-permissions/components/SettingsRolePermissionsSettingsTableRow';
|
|
||||||
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
|
|
||||||
import { SettingsRolePermissionsObjectPermission } from '@/settings/roles/types/SettingsRolePermissionsObjectPermission';
|
|
||||||
import { SettingsRolePermissionsSettingPermission } from '@/settings/roles/types/SettingsRolePermissionsSettingPermission';
|
|
||||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { t } from '@lingui/core/macro';
|
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||||
import { useRecoilState } from 'recoil';
|
|
||||||
import {
|
|
||||||
H2Title,
|
|
||||||
IconCode,
|
|
||||||
IconEye,
|
|
||||||
IconHierarchy,
|
|
||||||
IconKey,
|
|
||||||
IconLockOpen,
|
|
||||||
IconPencil,
|
|
||||||
IconServer,
|
|
||||||
IconSettings,
|
|
||||||
IconTrash,
|
|
||||||
IconTrashX,
|
|
||||||
IconUsers,
|
|
||||||
} from 'twenty-ui/display';
|
|
||||||
import { Card, Section } from 'twenty-ui/layout';
|
|
||||||
import {
|
|
||||||
FeatureFlagKey,
|
|
||||||
SettingPermissionType,
|
|
||||||
} from '~/generated-metadata/graphql';
|
|
||||||
|
|
||||||
const StyledRolePermissionsContainer = styled.div`
|
const StyledRolePermissionsContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -36,19 +11,6 @@ const StyledRolePermissionsContainer = styled.div`
|
|||||||
gap: ${({ theme }) => theme.spacing(8)};
|
gap: ${({ theme }) => theme.spacing(8)};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledTable = styled.div`
|
|
||||||
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledTableRows = styled.div`
|
|
||||||
padding-bottom: ${({ theme }) => theme.spacing(2)};
|
|
||||||
padding-top: ${({ theme }) => theme.spacing(2)};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledCard = styled(Card)`
|
|
||||||
margin-bottom: ${({ theme }) => theme.spacing(4)};
|
|
||||||
`;
|
|
||||||
|
|
||||||
type SettingsRolePermissionsProps = {
|
type SettingsRolePermissionsProps = {
|
||||||
roleId: string;
|
roleId: string;
|
||||||
isEditable: boolean;
|
isEditable: boolean;
|
||||||
@ -58,168 +20,26 @@ export const SettingsRolePermissions = ({
|
|||||||
roleId,
|
roleId,
|
||||||
isEditable,
|
isEditable,
|
||||||
}: SettingsRolePermissionsProps) => {
|
}: SettingsRolePermissionsProps) => {
|
||||||
const [settingsDraftRole, setSettingsDraftRole] = useRecoilState(
|
|
||||||
settingsDraftRoleFamilyState(roleId),
|
|
||||||
);
|
|
||||||
|
|
||||||
const objectPermissionsConfig: SettingsRolePermissionsObjectPermission[] = [
|
|
||||||
{
|
|
||||||
key: 'seeRecords',
|
|
||||||
label: t`See Records on All Objects`,
|
|
||||||
Icon: IconEye,
|
|
||||||
value: settingsDraftRole.canReadAllObjectRecords,
|
|
||||||
setValue: (value: boolean) => {
|
|
||||||
setSettingsDraftRole({
|
|
||||||
...settingsDraftRole,
|
|
||||||
canReadAllObjectRecords: value,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'editRecords',
|
|
||||||
label: t`Edit Records on All Objects`,
|
|
||||||
Icon: IconPencil,
|
|
||||||
value: settingsDraftRole.canUpdateAllObjectRecords,
|
|
||||||
setValue: (value: boolean) => {
|
|
||||||
setSettingsDraftRole({
|
|
||||||
...settingsDraftRole,
|
|
||||||
canUpdateAllObjectRecords: value,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'deleteRecords',
|
|
||||||
label: t`Delete Records on All Objects`,
|
|
||||||
Icon: IconTrash,
|
|
||||||
value: settingsDraftRole.canSoftDeleteAllObjectRecords,
|
|
||||||
setValue: (value: boolean) => {
|
|
||||||
setSettingsDraftRole({
|
|
||||||
...settingsDraftRole,
|
|
||||||
canSoftDeleteAllObjectRecords: value,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'destroyRecords',
|
|
||||||
label: t`Destroy Records on All Objects`,
|
|
||||||
Icon: IconTrashX,
|
|
||||||
value: settingsDraftRole.canDestroyAllObjectRecords,
|
|
||||||
setValue: (value: boolean) => {
|
|
||||||
setSettingsDraftRole({
|
|
||||||
...settingsDraftRole,
|
|
||||||
canDestroyAllObjectRecords: value,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const settingsPermissionsConfig: SettingsRolePermissionsSettingPermission[] =
|
|
||||||
[
|
|
||||||
{
|
|
||||||
key: SettingPermissionType.API_KEYS_AND_WEBHOOKS,
|
|
||||||
name: t`API Keys & Webhooks`,
|
|
||||||
description: t`Manage API keys and webhooks`,
|
|
||||||
Icon: IconCode,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: SettingPermissionType.WORKSPACE,
|
|
||||||
name: t`Workspace`,
|
|
||||||
description: t`Set global workspace preferences`,
|
|
||||||
Icon: IconSettings,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: SettingPermissionType.WORKSPACE_MEMBERS,
|
|
||||||
name: t`Users`,
|
|
||||||
description: t`Add or remove users`,
|
|
||||||
Icon: IconUsers,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: SettingPermissionType.ROLES,
|
|
||||||
name: t`Roles`,
|
|
||||||
description: t`Define user roles and access levels`,
|
|
||||||
Icon: IconLockOpen,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: SettingPermissionType.DATA_MODEL,
|
|
||||||
name: t`Data Model`,
|
|
||||||
description: t`Edit CRM data structure and fields`,
|
|
||||||
Icon: IconHierarchy,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: SettingPermissionType.ADMIN_PANEL,
|
|
||||||
name: t`Admin Panel`,
|
|
||||||
description: t`Admin settings and system tools`,
|
|
||||||
Icon: IconServer,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: SettingPermissionType.SECURITY,
|
|
||||||
name: t`Security`,
|
|
||||||
description: t`Manage security policies`,
|
|
||||||
Icon: IconKey,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const isPermissionsV2Enabled = useIsFeatureEnabled(
|
const isPermissionsV2Enabled = useIsFeatureEnabled(
|
||||||
FeatureFlagKey.IsPermissionsV2Enabled,
|
FeatureFlagKey.IsPermissionsV2Enabled,
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledRolePermissionsContainer>
|
<StyledRolePermissionsContainer>
|
||||||
<Section>
|
<SettingsRolePermissionsObjectsSection
|
||||||
<H2Title
|
roleId={roleId}
|
||||||
title={t`Objects`}
|
isEditable={isEditable}
|
||||||
description={t`Ability to interact with each object`}
|
/>
|
||||||
|
{isPermissionsV2Enabled && (
|
||||||
|
<SettingsRolePermissionsObjectLevelSection
|
||||||
|
roleId={roleId}
|
||||||
|
isEditable={isEditable}
|
||||||
/>
|
/>
|
||||||
<StyledTable>
|
)}
|
||||||
<SettingsRolePermissionsObjectsTableHeader
|
<SettingsRolePermissionsSettingsSection
|
||||||
roleId={roleId}
|
roleId={roleId}
|
||||||
objectPermissionsConfig={objectPermissionsConfig}
|
isEditable={isEditable}
|
||||||
isEditable={isEditable}
|
/>
|
||||||
/>
|
|
||||||
<StyledTableRows>
|
|
||||||
{objectPermissionsConfig.map((permission) => (
|
|
||||||
<SettingsRolePermissionsObjectsTableRow
|
|
||||||
key={permission.key}
|
|
||||||
permission={permission}
|
|
||||||
isEditable={isEditable}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</StyledTableRows>
|
|
||||||
</StyledTable>
|
|
||||||
</Section>
|
|
||||||
<Section>
|
|
||||||
<H2Title title={t`Settings`} description={t`Settings permissions`} />
|
|
||||||
{isPermissionsV2Enabled && (
|
|
||||||
<StyledCard rounded>
|
|
||||||
<SettingsOptionCardContentToggle
|
|
||||||
Icon={IconSettings}
|
|
||||||
title={t`Settings All Access`}
|
|
||||||
description={t`Ability to edit all settings`}
|
|
||||||
checked={settingsDraftRole.canUpdateAllSettings}
|
|
||||||
disabled={!isEditable}
|
|
||||||
onChange={() => {
|
|
||||||
setSettingsDraftRole({
|
|
||||||
...settingsDraftRole,
|
|
||||||
canUpdateAllSettings: !settingsDraftRole.canUpdateAllSettings,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</StyledCard>
|
|
||||||
)}
|
|
||||||
<StyledTable>
|
|
||||||
<SettingsRolePermissionsSettingsTableHeader />
|
|
||||||
<StyledTableRows>
|
|
||||||
{settingsPermissionsConfig.map((permission) => (
|
|
||||||
<SettingsRolePermissionsSettingsTableRow
|
|
||||||
key={permission.key}
|
|
||||||
roleId={roleId}
|
|
||||||
permission={permission}
|
|
||||||
isEditable={isEditable}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</StyledTableRows>
|
|
||||||
</StyledTable>
|
|
||||||
</Section>
|
|
||||||
</StyledRolePermissionsContainer>
|
</StyledRolePermissionsContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,58 @@
|
|||||||
|
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
|
||||||
|
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
|
||||||
|
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||||
|
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
|
||||||
|
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||||
|
import { t } from '@lingui/core/macro';
|
||||||
|
import { ChangeEvent, useState } from 'react';
|
||||||
|
import { useIcons } from 'twenty-ui/display';
|
||||||
|
import { MenuItem } from 'twenty-ui/navigation';
|
||||||
|
|
||||||
|
type SettingsRolePermissionsObjectLevelObjectPickerDropdownContentProps = {
|
||||||
|
excludedObjectMetadataIds: string[];
|
||||||
|
onSelect: (objectMetadataId: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SettingsRolePermissionsObjectLevelObjectPickerDropdownContent = ({
|
||||||
|
excludedObjectMetadataIds,
|
||||||
|
onSelect,
|
||||||
|
}: SettingsRolePermissionsObjectLevelObjectPickerDropdownContentProps) => {
|
||||||
|
const [searchFilter, setSearchFilter] = useState('');
|
||||||
|
|
||||||
|
const { objectMetadataItems } = useFilteredObjectMetadataItems();
|
||||||
|
|
||||||
|
const { getIcon } = useIcons();
|
||||||
|
|
||||||
|
const handleSearchFilterChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setSearchFilter(event.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const filteredObjectMetadataItems = objectMetadataItems.filter(
|
||||||
|
(objectMetadataItem) =>
|
||||||
|
objectMetadataItem.labelSingular
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(searchFilter.toLowerCase()) &&
|
||||||
|
!excludedObjectMetadataIds.includes(objectMetadataItem.id),
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuSearchInput
|
||||||
|
value={searchFilter}
|
||||||
|
onChange={handleSearchFilterChange}
|
||||||
|
placeholder={t`Search`}
|
||||||
|
/>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItemsContainer hasMaxHeight>
|
||||||
|
{filteredObjectMetadataItems.map((objectMetadataItem) => (
|
||||||
|
<MenuItem
|
||||||
|
key={objectMetadataItem.id}
|
||||||
|
text={objectMetadataItem.labelSingular}
|
||||||
|
LeftIcon={getIcon(objectMetadataItem.icon)}
|
||||||
|
onClick={() => onSelect(objectMetadataItem.id)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</DropdownMenuItemsContainer>
|
||||||
|
</DropdownMenu>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,96 @@
|
|||||||
|
import { SETTINGS_ROLE_OBJECT_PERMISSION_ICON_CONFIG } from '@/settings/roles/role-permissions/objects-permissions/constants/settingsRoleObjectPermissionIconConfig';
|
||||||
|
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
|
||||||
|
import { useTheme } from '@emotion/react';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
import { ObjectPermission } from '~/generated/graphql';
|
||||||
|
|
||||||
|
const StyledIconWrapper = styled.div<{ isForbidden?: boolean }>`
|
||||||
|
align-items: center;
|
||||||
|
background: ${({ theme, isForbidden }) =>
|
||||||
|
isForbidden ? theme.adaptiveColors.orange1 : theme.adaptiveColors.blue1};
|
||||||
|
border: 1px solid
|
||||||
|
${({ theme, isForbidden }) =>
|
||||||
|
isForbidden ? theme.adaptiveColors.orange3 : theme.adaptiveColors.blue3};
|
||||||
|
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||||
|
display: flex;
|
||||||
|
height: ${({ theme }) => theme.spacing(4)};
|
||||||
|
justify-content: center;
|
||||||
|
width: ${({ theme }) => theme.spacing(4)};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledIcon = styled.div<{ isForbidden?: boolean }>`
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
color: ${({ theme, isForbidden }) =>
|
||||||
|
isForbidden ? theme.color.orange : theme.color.blue};
|
||||||
|
justify-content: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledSettingsRolePermissionsObjectLevelOverrideCell = styled.div`
|
||||||
|
display: flex;
|
||||||
|
gap: ${({ theme }) => theme.spacing(1)};
|
||||||
|
`;
|
||||||
|
|
||||||
|
type SettingsRolePermissionsObjectLevelOverrideCellProps = {
|
||||||
|
objectPermission: ObjectPermission;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SettingsRolePermissionsObjectLevelOverrideCell = ({
|
||||||
|
objectPermission,
|
||||||
|
}: SettingsRolePermissionsObjectLevelOverrideCellProps) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const settingsDraftRole = useRecoilValue(
|
||||||
|
settingsDraftRoleFamilyState(objectPermission.roleId),
|
||||||
|
);
|
||||||
|
|
||||||
|
const permissionMappings = {
|
||||||
|
canReadObjectRecords: 'canReadAllObjectRecords',
|
||||||
|
canUpdateObjectRecords: 'canUpdateAllObjectRecords',
|
||||||
|
canSoftDeleteObjectRecords: 'canSoftDeleteAllObjectRecords',
|
||||||
|
canDestroyObjectRecords: 'canDestroyAllObjectRecords',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
type ObjectPermissionKey = keyof typeof permissionMappings;
|
||||||
|
|
||||||
|
const isOverridden = (permission: ObjectPermissionKey) => {
|
||||||
|
const rolePermission = permissionMappings[permission];
|
||||||
|
return (
|
||||||
|
isDefined(objectPermission[permission]) &&
|
||||||
|
!!settingsDraftRole[rolePermission as keyof typeof settingsDraftRole] !==
|
||||||
|
!!objectPermission[permission]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledSettingsRolePermissionsObjectLevelOverrideCell>
|
||||||
|
{(Object.keys(permissionMappings) as ObjectPermissionKey[]).map(
|
||||||
|
(permission) => {
|
||||||
|
const { Icon, IconForbidden: IconOverride } =
|
||||||
|
SETTINGS_ROLE_OBJECT_PERMISSION_ICON_CONFIG[permission];
|
||||||
|
const permissionValue = objectPermission[permission];
|
||||||
|
|
||||||
|
if (!isOverridden(permission)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledIconWrapper
|
||||||
|
key={permission}
|
||||||
|
isForbidden={permissionValue === false}
|
||||||
|
>
|
||||||
|
<StyledIcon isForbidden={permissionValue === false}>
|
||||||
|
{permissionValue === false && (
|
||||||
|
<IconOverride size={theme.icon.size.sm} />
|
||||||
|
)}
|
||||||
|
{permissionValue === true && <Icon size={theme.icon.size.sm} />}
|
||||||
|
</StyledIcon>
|
||||||
|
</StyledIconWrapper>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
</StyledSettingsRolePermissionsObjectLevelOverrideCell>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,113 @@
|
|||||||
|
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||||
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
|
import { SettingsRolePermissionsObjectLevelTableHeader } from '@/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelTableHeader';
|
||||||
|
import { SettingsRolePermissionsObjectLevelTableRow } from '@/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelTableRow';
|
||||||
|
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
|
||||||
|
import { Table } from '@/ui/layout/table/components/Table';
|
||||||
|
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
import { t } from '@lingui/core/macro';
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
import { H2Title } from 'twenty-ui/display';
|
||||||
|
import { Section } from 'twenty-ui/layout';
|
||||||
|
|
||||||
|
// const StyledCreateObjectOverrideSection = styled(Section)`
|
||||||
|
// border-top: 1px solid ${({ theme }) => theme.border.color.light};
|
||||||
|
// display: flex;
|
||||||
|
// justify-content: flex-end;
|
||||||
|
// padding-top: ${({ theme }) => theme.spacing(2)};
|
||||||
|
// padding-bottom: ${({ theme }) => theme.spacing(2)};
|
||||||
|
// `;
|
||||||
|
|
||||||
|
const StyledTableRows = styled.div`
|
||||||
|
padding-bottom: ${({ theme }) => theme.spacing(2)};
|
||||||
|
padding-top: ${({ theme }) => theme.spacing(2)};
|
||||||
|
`;
|
||||||
|
|
||||||
|
type SettingsRolePermissionsObjectLevelSectionProps = {
|
||||||
|
roleId: string;
|
||||||
|
isEditable: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const StyledNoOverride = styled(TableCell)`
|
||||||
|
color: ${({ theme }) => theme.font.color.tertiary};
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const SettingsRolePermissionsObjectLevelSection = ({
|
||||||
|
roleId,
|
||||||
|
}: SettingsRolePermissionsObjectLevelSectionProps) => {
|
||||||
|
const settingsDraftRole = useRecoilValue(
|
||||||
|
settingsDraftRoleFamilyState(roleId),
|
||||||
|
);
|
||||||
|
|
||||||
|
const objectMetadataItems = useObjectMetadataItems();
|
||||||
|
|
||||||
|
const objectMetadataMap = objectMetadataItems.objectMetadataItems.reduce(
|
||||||
|
(acc, item) => {
|
||||||
|
acc[item.id] = item;
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{} as Record<string, ObjectMetadataItem>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const objectPermissions = settingsDraftRole.objectPermissions;
|
||||||
|
|
||||||
|
// const handleSelectObjectMetadata = (objectMetadataId: string) => {
|
||||||
|
// setSettingsDraftRole((draftRole) => ({
|
||||||
|
// ...draftRole,
|
||||||
|
// objectPermissions: [
|
||||||
|
// ...(draftRole.objectPermissions ?? []),
|
||||||
|
// { objectMetadataId, roleId, id: v4() },
|
||||||
|
// ],
|
||||||
|
// }));
|
||||||
|
// };
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Section>
|
||||||
|
<H2Title
|
||||||
|
title={t`Object-Level Permissions`}
|
||||||
|
description={t`Set additional object-level permissions`}
|
||||||
|
/>
|
||||||
|
<Table>
|
||||||
|
<SettingsRolePermissionsObjectLevelTableHeader />
|
||||||
|
<StyledTableRows>
|
||||||
|
{isDefined(objectPermissions) && objectPermissions?.length > 0 ? (
|
||||||
|
objectPermissions?.map((objectPermission) => (
|
||||||
|
<SettingsRolePermissionsObjectLevelTableRow
|
||||||
|
key={objectPermission.id}
|
||||||
|
objectPermission={objectPermission}
|
||||||
|
objectMetadataItem={
|
||||||
|
objectMetadataMap[objectPermission.objectMetadataId]
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<StyledNoOverride>{t`No overrides found`}</StyledNoOverride>
|
||||||
|
)}
|
||||||
|
</StyledTableRows>
|
||||||
|
</Table>
|
||||||
|
{/* <StyledCreateObjectOverrideSection>
|
||||||
|
<Dropdown
|
||||||
|
dropdownId="role-object-select"
|
||||||
|
dropdownHotkeyScope={{ scope: 'roleObject' }}
|
||||||
|
clickableComponent={
|
||||||
|
<Button
|
||||||
|
Icon={IconPlus}
|
||||||
|
title={t`Add Object`}
|
||||||
|
variant="secondary"
|
||||||
|
size="small"
|
||||||
|
disabled={!isEditable}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
dropdownComponents={
|
||||||
|
<SettingsRolePermissionsObjectLevelObjectPickerDropdownContent
|
||||||
|
excludedObjectMetadataIds={[]}
|
||||||
|
onSelect={handleSelectObjectMetadata}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</StyledCreateObjectOverrideSection> */}
|
||||||
|
</Section>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
||||||
|
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||||
|
import { t } from '@lingui/core/macro';
|
||||||
|
|
||||||
|
export const SettingsRolePermissionsObjectLevelTableHeader = () => (
|
||||||
|
<TableRow>
|
||||||
|
<TableHeader>{t`Object`}</TableHeader>
|
||||||
|
<TableHeader>{t`Permission overrides`}</TableHeader>
|
||||||
|
<TableHeader></TableHeader>
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
@ -0,0 +1,73 @@
|
|||||||
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
|
import { SettingsRolePermissionsObjectLevelOverrideCell } from '@/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelOverrideCell';
|
||||||
|
import { SettingsPath } from '@/types/SettingsPath';
|
||||||
|
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
||||||
|
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||||
|
import { useTheme } from '@emotion/react';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
import { IconChevronRight, useIcons } from 'twenty-ui/display';
|
||||||
|
import { ObjectPermission } from '~/generated/graphql';
|
||||||
|
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
||||||
|
|
||||||
|
const StyledNameTableCell = styled(TableCell)`
|
||||||
|
color: ${({ theme }) => theme.font.color.primary};
|
||||||
|
gap: ${({ theme }) => theme.spacing(1)};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledNameLabel = styled.div`
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
`;
|
||||||
|
|
||||||
|
type SettingsRolePermissionsObjectLevelTableRowProps = {
|
||||||
|
objectPermission: ObjectPermission;
|
||||||
|
objectMetadataItem: ObjectMetadataItem;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SettingsRolePermissionsObjectLevelTableRow = ({
|
||||||
|
objectPermission,
|
||||||
|
objectMetadataItem,
|
||||||
|
}: SettingsRolePermissionsObjectLevelTableRowProps) => {
|
||||||
|
const { getIcon } = useIcons();
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
if (!objectMetadataItem) {
|
||||||
|
throw new Error('Object metadata item not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const Icon = getIcon(objectMetadataItem.icon);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableRow
|
||||||
|
to={getSettingsPath(SettingsPath.RoleObjectLevel, {
|
||||||
|
roleId: objectPermission.roleId,
|
||||||
|
objectMetadataId: objectPermission.objectMetadataId,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<StyledNameTableCell>
|
||||||
|
{!!Icon && (
|
||||||
|
<Icon
|
||||||
|
style={{ minWidth: theme.icon.size.md }}
|
||||||
|
size={theme.icon.size.md}
|
||||||
|
stroke={theme.icon.stroke.sm}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<StyledNameLabel title={objectMetadataItem.labelPlural}>
|
||||||
|
{objectMetadataItem.labelPlural}
|
||||||
|
</StyledNameLabel>
|
||||||
|
</StyledNameTableCell>
|
||||||
|
<TableCell>
|
||||||
|
<SettingsRolePermissionsObjectLevelOverrideCell
|
||||||
|
objectPermission={objectPermission}
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align={'right'}>
|
||||||
|
<IconChevronRight
|
||||||
|
size={theme.icon.size.md}
|
||||||
|
color={theme.font.color.tertiary}
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,94 @@
|
|||||||
|
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
|
||||||
|
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||||
|
import { SettingsDataModelObjectTypeTag } from '@/settings/data-model/objects/components/SettingsDataModelObjectTypeTag';
|
||||||
|
import { getObjectTypeLabel } from '@/settings/data-model/utils/getObjectTypeLabel';
|
||||||
|
import { SettingsRolePermissionsObjectLevelObjectFormObjectLevel } from '@/settings/roles/role-permissions/object-level-permissions/object-form/components/SettingsRolePermissionsObjectLevelObjectFormObjectLevel';
|
||||||
|
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
|
||||||
|
import { SettingsPath } from '@/types/SettingsPath';
|
||||||
|
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
import { t } from '@lingui/core/macro';
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
|
import { H3Title } from 'twenty-ui/display';
|
||||||
|
import { Button } from 'twenty-ui/input';
|
||||||
|
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
||||||
|
|
||||||
|
const StyledObjectTypeTag = styled(SettingsDataModelObjectTypeTag)`
|
||||||
|
box-sizing: border-box;
|
||||||
|
height: ${({ theme }) => theme.spacing(5)};
|
||||||
|
margin-left: ${({ theme }) => theme.spacing(2)};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledTitleContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
`;
|
||||||
|
|
||||||
|
type SettingsRolePermissionsObjectLevelObjectFormProps = {
|
||||||
|
roleId: string;
|
||||||
|
objectMetadataId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SettingsRolePermissionsObjectLevelObjectForm = ({
|
||||||
|
roleId,
|
||||||
|
objectMetadataId,
|
||||||
|
}: SettingsRolePermissionsObjectLevelObjectFormProps) => {
|
||||||
|
const settingsDraftRole = useRecoilValue(
|
||||||
|
settingsDraftRoleFamilyState(roleId),
|
||||||
|
);
|
||||||
|
|
||||||
|
const objectMetadata = useObjectMetadataItemById({
|
||||||
|
objectId: objectMetadataId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const objectMetadataItem = objectMetadata.objectMetadataItem;
|
||||||
|
|
||||||
|
const objectTypeLabel = getObjectTypeLabel(objectMetadataItem);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SubMenuTopBarContainer
|
||||||
|
title={
|
||||||
|
<StyledTitleContainer>
|
||||||
|
<H3Title title={objectMetadataItem.labelPlural} />
|
||||||
|
<StyledObjectTypeTag objectTypeLabel={objectTypeLabel} />
|
||||||
|
</StyledTitleContainer>
|
||||||
|
}
|
||||||
|
links={[
|
||||||
|
{
|
||||||
|
children: 'Workspace',
|
||||||
|
href: getSettingsPath(SettingsPath.Workspace),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
children: 'Roles',
|
||||||
|
href: getSettingsPath(SettingsPath.Roles),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
children: settingsDraftRole.label,
|
||||||
|
href: getSettingsPath(SettingsPath.RoleDetail, {
|
||||||
|
roleId,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
children: `Permissions · ${objectMetadataItem.labelSingular}`,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
actionButton={
|
||||||
|
<Button
|
||||||
|
title={t`Back`}
|
||||||
|
variant="primary"
|
||||||
|
size="small"
|
||||||
|
accent="blue"
|
||||||
|
to={getSettingsPath(SettingsPath.RoleDetail, {
|
||||||
|
roleId,
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SettingsPageContainer>
|
||||||
|
<SettingsRolePermissionsObjectLevelObjectFormObjectLevel
|
||||||
|
objectMetadataItem={objectMetadataItem}
|
||||||
|
roleId={roleId}
|
||||||
|
/>
|
||||||
|
</SettingsPageContainer>
|
||||||
|
</SubMenuTopBarContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,100 @@
|
|||||||
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
|
import { SettingsRolePermissionsObjectLevelObjectFormObjectLevelHeader } from '@/settings/roles/role-permissions/object-level-permissions/object-form/components/SettingsRolePermissionsObjectLevelObjectFormObjectLevelHeader';
|
||||||
|
import { SettingsRolePermissionsObjectsTableRow } from '@/settings/roles/role-permissions/objects-permissions/components/SettingsRolePermissionsObjectsTableRow';
|
||||||
|
import { SettingsRolePermissionsObjectPermission } from '@/settings/roles/role-permissions/objects-permissions/types/SettingsRolePermissionsObjectPermission';
|
||||||
|
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
import { t } from '@lingui/core/macro';
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
|
import { H2Title } from 'twenty-ui/display';
|
||||||
|
import { Section } from 'twenty-ui/layout';
|
||||||
|
|
||||||
|
const StyledTable = styled.div`
|
||||||
|
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledTableRows = styled.div`
|
||||||
|
padding-bottom: ${({ theme }) => theme.spacing(2)};
|
||||||
|
padding-top: ${({ theme }) => theme.spacing(2)};
|
||||||
|
`;
|
||||||
|
|
||||||
|
type SettingsRolePermissionsObjectLevelObjectFormObjectLevelProps = {
|
||||||
|
roleId: string;
|
||||||
|
objectMetadataItem: ObjectMetadataItem;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SettingsRolePermissionsObjectLevelObjectFormObjectLevel = ({
|
||||||
|
roleId,
|
||||||
|
objectMetadataItem,
|
||||||
|
}: SettingsRolePermissionsObjectLevelObjectFormObjectLevelProps) => {
|
||||||
|
const settingsDraftRole = useRecoilValue(
|
||||||
|
settingsDraftRoleFamilyState(roleId),
|
||||||
|
);
|
||||||
|
|
||||||
|
const settingsDraftRoleObjectPermissions =
|
||||||
|
settingsDraftRole.objectPermissions?.find(
|
||||||
|
(permission) => permission.objectMetadataId === objectMetadataItem.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!settingsDraftRoleObjectPermissions) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const objectLabel = objectMetadataItem.labelPlural;
|
||||||
|
|
||||||
|
const objectPermissionsConfig: SettingsRolePermissionsObjectPermission[] = [
|
||||||
|
{
|
||||||
|
key: 'canReadObjectRecords',
|
||||||
|
label: t`See Records on ${objectLabel}`,
|
||||||
|
value: settingsDraftRoleObjectPermissions.canReadObjectRecords,
|
||||||
|
setValue: (_value: boolean) => {
|
||||||
|
// TODO: Implement
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'canUpdateObjectRecords',
|
||||||
|
label: t`Edit Records on ${objectLabel}`,
|
||||||
|
value: settingsDraftRoleObjectPermissions.canUpdateObjectRecords,
|
||||||
|
setValue: (_value: boolean) => {
|
||||||
|
// TODO: Implement
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'canSoftDeleteObjectRecords',
|
||||||
|
label: t`Delete Records on ${objectLabel}`,
|
||||||
|
value: settingsDraftRoleObjectPermissions.canSoftDeleteObjectRecords,
|
||||||
|
setValue: (_value: boolean) => {
|
||||||
|
// TODO: Implement
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'canDestroyObjectRecords',
|
||||||
|
label: t`Destroy Records on ${objectLabel}`,
|
||||||
|
value: settingsDraftRoleObjectPermissions.canDestroyObjectRecords,
|
||||||
|
setValue: (_value: boolean) => {
|
||||||
|
// TODO: Implement
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Section>
|
||||||
|
<H2Title
|
||||||
|
title={t`Object-Level Permissions`}
|
||||||
|
description={t`Ability to interact with this specific object`}
|
||||||
|
/>
|
||||||
|
<StyledTable>
|
||||||
|
<SettingsRolePermissionsObjectLevelObjectFormObjectLevelHeader />
|
||||||
|
<StyledTableRows>
|
||||||
|
{objectPermissionsConfig.map((permission) => (
|
||||||
|
<SettingsRolePermissionsObjectsTableRow
|
||||||
|
key={permission.key}
|
||||||
|
permission={permission}
|
||||||
|
isEditable={settingsDraftRole.isEditable}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</StyledTableRows>
|
||||||
|
</StyledTable>
|
||||||
|
</Section>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
||||||
|
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||||
|
import { t } from '@lingui/core/macro';
|
||||||
|
|
||||||
|
export const SettingsRolePermissionsObjectLevelObjectFormObjectLevelHeader =
|
||||||
|
() => (
|
||||||
|
<TableRow gridAutoColumns="1fr 24px">
|
||||||
|
<TableHeader>{t`Name`}</TableHeader>
|
||||||
|
<TableHeader aria-label={t`Actions`}></TableHeader>
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
@ -0,0 +1,135 @@
|
|||||||
|
import { SettingsRolePermissionsObjectsTableHeader } from '@/settings/roles/role-permissions/objects-permissions/components/SettingsRolePermissionsObjectsTableHeader';
|
||||||
|
import { SettingsRolePermissionsObjectsTableRow } from '@/settings/roles/role-permissions/objects-permissions/components/SettingsRolePermissionsObjectsTableRow';
|
||||||
|
import { SettingsRolePermissionsObjectPermission } from '@/settings/roles/role-permissions/objects-permissions/types/SettingsRolePermissionsObjectPermission';
|
||||||
|
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
import { t } from '@lingui/core/macro';
|
||||||
|
import { useRecoilState } from 'recoil';
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
import { H2Title } from 'twenty-ui/display';
|
||||||
|
import { Section } from 'twenty-ui/layout';
|
||||||
|
|
||||||
|
const StyledTable = styled.div`
|
||||||
|
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledTableRows = styled.div`
|
||||||
|
padding-bottom: ${({ theme }) => theme.spacing(2)};
|
||||||
|
padding-top: ${({ theme }) => theme.spacing(2)};
|
||||||
|
`;
|
||||||
|
|
||||||
|
type SettingsRolePermissionsObjectsSectionProps = {
|
||||||
|
roleId: string;
|
||||||
|
isEditable: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SettingsRolePermissionsObjectsSection = ({
|
||||||
|
roleId,
|
||||||
|
isEditable,
|
||||||
|
}: SettingsRolePermissionsObjectsSectionProps) => {
|
||||||
|
const [settingsDraftRole, setSettingsDraftRole] = useRecoilState(
|
||||||
|
settingsDraftRoleFamilyState(roleId),
|
||||||
|
);
|
||||||
|
|
||||||
|
const objectPermissions = settingsDraftRole.objectPermissions;
|
||||||
|
|
||||||
|
const objectPermissionsConfig: SettingsRolePermissionsObjectPermission[] = [
|
||||||
|
{
|
||||||
|
key: 'canReadObjectRecords',
|
||||||
|
label: t`See Records on All Objects`,
|
||||||
|
overriddenBy:
|
||||||
|
objectPermissions?.filter(
|
||||||
|
(permission) =>
|
||||||
|
isDefined(permission.canReadObjectRecords) &&
|
||||||
|
permission.canReadObjectRecords !==
|
||||||
|
settingsDraftRole.canReadAllObjectRecords,
|
||||||
|
)?.length ?? 0,
|
||||||
|
value: settingsDraftRole.canReadAllObjectRecords,
|
||||||
|
setValue: (value: boolean) => {
|
||||||
|
setSettingsDraftRole({
|
||||||
|
...settingsDraftRole,
|
||||||
|
canReadAllObjectRecords: value,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'canUpdateObjectRecords',
|
||||||
|
label: t`Edit Records on All Objects`,
|
||||||
|
overriddenBy:
|
||||||
|
objectPermissions?.filter(
|
||||||
|
(permission) =>
|
||||||
|
isDefined(permission.canUpdateObjectRecords) &&
|
||||||
|
permission.canUpdateObjectRecords !==
|
||||||
|
settingsDraftRole.canUpdateAllObjectRecords,
|
||||||
|
)?.length ?? 0,
|
||||||
|
value: settingsDraftRole.canUpdateAllObjectRecords,
|
||||||
|
setValue: (value: boolean) => {
|
||||||
|
setSettingsDraftRole({
|
||||||
|
...settingsDraftRole,
|
||||||
|
canUpdateAllObjectRecords: value,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'canSoftDeleteObjectRecords',
|
||||||
|
label: t`Delete Records on All Objects`,
|
||||||
|
overriddenBy:
|
||||||
|
objectPermissions?.filter(
|
||||||
|
(permission) =>
|
||||||
|
isDefined(permission.canSoftDeleteObjectRecords) &&
|
||||||
|
permission.canSoftDeleteObjectRecords !==
|
||||||
|
settingsDraftRole.canSoftDeleteAllObjectRecords,
|
||||||
|
)?.length ?? 0,
|
||||||
|
value: settingsDraftRole.canSoftDeleteAllObjectRecords,
|
||||||
|
setValue: (value: boolean) => {
|
||||||
|
setSettingsDraftRole({
|
||||||
|
...settingsDraftRole,
|
||||||
|
canSoftDeleteAllObjectRecords: value,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'canDestroyObjectRecords',
|
||||||
|
label: t`Destroy Records on All Objects`,
|
||||||
|
overriddenBy:
|
||||||
|
objectPermissions?.filter(
|
||||||
|
(permission) =>
|
||||||
|
isDefined(permission.canDestroyObjectRecords) &&
|
||||||
|
permission.canDestroyObjectRecords !==
|
||||||
|
settingsDraftRole.canDestroyAllObjectRecords,
|
||||||
|
)?.length ?? 0,
|
||||||
|
value: settingsDraftRole.canDestroyAllObjectRecords,
|
||||||
|
setValue: (value: boolean) => {
|
||||||
|
setSettingsDraftRole({
|
||||||
|
...settingsDraftRole,
|
||||||
|
canDestroyAllObjectRecords: value,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Section>
|
||||||
|
<H2Title
|
||||||
|
title={t`Objects`}
|
||||||
|
description={t`Ability to interact with each object`}
|
||||||
|
/>
|
||||||
|
<StyledTable>
|
||||||
|
<SettingsRolePermissionsObjectsTableHeader
|
||||||
|
roleId={roleId}
|
||||||
|
objectPermissionsConfig={objectPermissionsConfig}
|
||||||
|
isEditable={isEditable}
|
||||||
|
/>
|
||||||
|
<StyledTableRows>
|
||||||
|
{objectPermissionsConfig.map((permission) => (
|
||||||
|
<SettingsRolePermissionsObjectsTableRow
|
||||||
|
key={permission.key}
|
||||||
|
permission={permission}
|
||||||
|
isEditable={isEditable}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</StyledTableRows>
|
||||||
|
</StyledTable>
|
||||||
|
</Section>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -1,5 +1,5 @@
|
|||||||
|
import { SettingsRolePermissionsObjectPermission } from '@/settings/roles/role-permissions/objects-permissions/types/SettingsRolePermissionsObjectPermission';
|
||||||
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
|
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
|
||||||
import { SettingsRolePermissionsObjectPermission } from '@/settings/roles/types/SettingsRolePermissionsObjectPermission';
|
|
||||||
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
||||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
@ -1,32 +1,24 @@
|
|||||||
import { SettingsRolePermissionsObjectPermission } from '@/settings/roles/types/SettingsRolePermissionsObjectPermission';
|
import { SETTINGS_ROLE_OBJECT_PERMISSION_ICON_CONFIG } from '@/settings/roles/role-permissions/objects-permissions/constants/settingsRoleObjectPermissionIconConfig';
|
||||||
|
import { SettingsRolePermissionsObjectPermission } from '@/settings/roles/role-permissions/objects-permissions/types/SettingsRolePermissionsObjectPermission';
|
||||||
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
||||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { Checkbox } from 'twenty-ui/input';
|
import { t } from '@lingui/core/macro';
|
||||||
|
import pluralize from 'pluralize';
|
||||||
const StyledIconWrapper = styled.div`
|
import { Checkbox, CheckboxAccent } from 'twenty-ui/input';
|
||||||
align-items: center;
|
|
||||||
background: ${({ theme }) => theme.adaptiveColors.blue1};
|
|
||||||
border: 1px solid ${({ theme }) => theme.adaptiveColors.blue3};
|
|
||||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
|
||||||
display: flex;
|
|
||||||
height: ${({ theme }) => theme.spacing(4)};
|
|
||||||
justify-content: center;
|
|
||||||
width: ${({ theme }) => theme.spacing(4)};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledIcon = styled.div`
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
color: ${({ theme }) => theme.color.blue};
|
|
||||||
justify-content: center;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledLabel = styled.span`
|
const StyledLabel = styled.span`
|
||||||
color: ${({ theme }) => theme.font.color.primary};
|
color: ${({ theme }) => theme.font.color.primary};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const StyledOverrideInfo = styled.span`
|
||||||
|
background: ${({ theme }) => theme.adaptiveColors.orange1};
|
||||||
|
border-radius: ${({ theme }) => theme.spacing(1)};
|
||||||
|
color: ${({ theme }) => theme.color.orange};
|
||||||
|
padding: ${({ theme }) => theme.spacing(1)};
|
||||||
|
`;
|
||||||
|
|
||||||
const StyledPermissionCell = styled(TableCell)`
|
const StyledPermissionCell = styled(TableCell)`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -58,21 +50,30 @@ export const SettingsRolePermissionsObjectsTableRow = ({
|
|||||||
}: SettingsRolePermissionsObjectsTableRowProps) => {
|
}: SettingsRolePermissionsObjectsTableRowProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const isOverriddenBy = permission.overriddenBy;
|
||||||
|
const isOverridden = isOverriddenBy && isOverriddenBy > 0;
|
||||||
|
const label = permission.label;
|
||||||
|
const pluralizedObject = pluralize('object', isOverriddenBy);
|
||||||
|
|
||||||
|
const { Icon } = SETTINGS_ROLE_OBJECT_PERMISSION_ICON_CONFIG[permission.key];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledTableRow>
|
<StyledTableRow>
|
||||||
<StyledPermissionCell>
|
<StyledPermissionCell>
|
||||||
<StyledIconWrapper>
|
<Icon size={theme.icon.size.sm} />
|
||||||
<StyledIcon>
|
<StyledLabel>{label}</StyledLabel>
|
||||||
<permission.Icon size={theme.icon.size.sm} />
|
{isOverridden ? (
|
||||||
</StyledIcon>
|
<StyledOverrideInfo>
|
||||||
</StyledIconWrapper>
|
{t`Overridden on ${isOverriddenBy} ${pluralizedObject}`}
|
||||||
<StyledLabel>{permission.label}</StyledLabel>
|
</StyledOverrideInfo>
|
||||||
|
) : null}
|
||||||
</StyledPermissionCell>
|
</StyledPermissionCell>
|
||||||
<StyledCheckboxCell>
|
<StyledCheckboxCell>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={permission.value}
|
checked={permission.value ?? false}
|
||||||
onChange={() => permission.setValue(!permission.value)}
|
onChange={() => permission.setValue(!permission.value)}
|
||||||
disabled={!isEditable}
|
disabled={!isEditable}
|
||||||
|
accent={isOverridden ? CheckboxAccent.Orange : CheckboxAccent.Blue}
|
||||||
/>
|
/>
|
||||||
</StyledCheckboxCell>
|
</StyledCheckboxCell>
|
||||||
</StyledTableRow>
|
</StyledTableRow>
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
import {
|
||||||
|
IconComponent,
|
||||||
|
IconEye,
|
||||||
|
IconEyeOff,
|
||||||
|
IconPencil,
|
||||||
|
IconPencilOff,
|
||||||
|
IconTrash,
|
||||||
|
IconTrashOff,
|
||||||
|
IconTrashX,
|
||||||
|
} from 'twenty-ui/display';
|
||||||
|
|
||||||
|
type SettingsRoleObjectPermissionIconConfig = {
|
||||||
|
Icon: IconComponent;
|
||||||
|
IconForbidden: IconComponent;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SETTINGS_ROLE_OBJECT_PERMISSION_ICON_CONFIG: Record<
|
||||||
|
string,
|
||||||
|
SettingsRoleObjectPermissionIconConfig
|
||||||
|
> = {
|
||||||
|
canReadObjectRecords: {
|
||||||
|
Icon: IconEye,
|
||||||
|
IconForbidden: IconEyeOff,
|
||||||
|
},
|
||||||
|
canUpdateObjectRecords: {
|
||||||
|
Icon: IconPencil,
|
||||||
|
IconForbidden: IconPencilOff,
|
||||||
|
},
|
||||||
|
canSoftDeleteObjectRecords: {
|
||||||
|
Icon: IconTrash,
|
||||||
|
IconForbidden: IconTrashOff,
|
||||||
|
},
|
||||||
|
canDestroyObjectRecords: {
|
||||||
|
Icon: IconTrashX,
|
||||||
|
IconForbidden: IconTrashX,
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
import { ReactNode } from 'react';
|
||||||
|
export type SettingsRolePermissionsObjectPermission = {
|
||||||
|
key: string;
|
||||||
|
label: string | ReactNode;
|
||||||
|
value?: boolean | null;
|
||||||
|
setValue: (value: boolean) => void;
|
||||||
|
overriddenBy?: number;
|
||||||
|
};
|
||||||
@ -0,0 +1,130 @@
|
|||||||
|
import { SettingsOptionCardContentToggle } from '@/settings/components/SettingsOptions/SettingsOptionCardContentToggle';
|
||||||
|
import { SettingsRolePermissionsSettingsTableHeader } from '@/settings/roles/role-permissions/settings-permissions/components/SettingsRolePermissionsSettingsTableHeader';
|
||||||
|
import { SettingsRolePermissionsSettingsTableRow } from '@/settings/roles/role-permissions/settings-permissions/components/SettingsRolePermissionsSettingsTableRow';
|
||||||
|
import { SettingsRolePermissionsSettingPermission } from '@/settings/roles/role-permissions/settings-permissions/types/SettingsRolePermissionsSettingPermission';
|
||||||
|
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
|
||||||
|
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
import { t } from '@lingui/core/macro';
|
||||||
|
import { useRecoilState } from 'recoil';
|
||||||
|
import {
|
||||||
|
H2Title,
|
||||||
|
IconCode,
|
||||||
|
IconHierarchy,
|
||||||
|
IconKey,
|
||||||
|
IconLockOpen,
|
||||||
|
IconSettings,
|
||||||
|
IconUsers,
|
||||||
|
} from 'twenty-ui/display';
|
||||||
|
import { Card, Section } from 'twenty-ui/layout';
|
||||||
|
import {
|
||||||
|
FeatureFlagKey,
|
||||||
|
SettingPermissionType,
|
||||||
|
} from '~/generated-metadata/graphql';
|
||||||
|
|
||||||
|
const StyledTable = styled.div`
|
||||||
|
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledTableRows = styled.div`
|
||||||
|
padding-bottom: ${({ theme }) => theme.spacing(2)};
|
||||||
|
padding-top: ${({ theme }) => theme.spacing(2)};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledCard = styled(Card)`
|
||||||
|
margin-bottom: ${({ theme }) => theme.spacing(4)};
|
||||||
|
`;
|
||||||
|
|
||||||
|
type SettingsRolePermissionsSettingsSectionProps = {
|
||||||
|
roleId: string;
|
||||||
|
isEditable: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SettingsRolePermissionsSettingsSection = ({
|
||||||
|
roleId,
|
||||||
|
isEditable,
|
||||||
|
}: SettingsRolePermissionsSettingsSectionProps) => {
|
||||||
|
const isPermissionsV2Enabled = useIsFeatureEnabled(
|
||||||
|
FeatureFlagKey.IsPermissionsV2Enabled,
|
||||||
|
);
|
||||||
|
|
||||||
|
const [settingsDraftRole, setSettingsDraftRole] = useRecoilState(
|
||||||
|
settingsDraftRoleFamilyState(roleId),
|
||||||
|
);
|
||||||
|
|
||||||
|
const settingsPermissionsConfig: SettingsRolePermissionsSettingPermission[] =
|
||||||
|
[
|
||||||
|
{
|
||||||
|
key: SettingPermissionType.API_KEYS_AND_WEBHOOKS,
|
||||||
|
name: t`API Keys & Webhooks`,
|
||||||
|
description: t`Manage API keys and webhooks`,
|
||||||
|
Icon: IconCode,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: SettingPermissionType.WORKSPACE,
|
||||||
|
name: t`Workspace`,
|
||||||
|
description: t`Set global workspace preferences`,
|
||||||
|
Icon: IconSettings,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: SettingPermissionType.WORKSPACE_MEMBERS,
|
||||||
|
name: t`Users`,
|
||||||
|
description: t`Add or remove users`,
|
||||||
|
Icon: IconUsers,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: SettingPermissionType.ROLES,
|
||||||
|
name: t`Roles`,
|
||||||
|
description: t`Define user roles and access levels`,
|
||||||
|
Icon: IconLockOpen,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: SettingPermissionType.DATA_MODEL,
|
||||||
|
name: t`Data Model`,
|
||||||
|
description: t`Edit CRM data structure and fields`,
|
||||||
|
Icon: IconHierarchy,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: SettingPermissionType.SECURITY,
|
||||||
|
name: t`Security`,
|
||||||
|
description: t`Manage security policies`,
|
||||||
|
Icon: IconKey,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Section>
|
||||||
|
<H2Title title={t`Settings`} description={t`Settings permissions`} />
|
||||||
|
{isPermissionsV2Enabled && (
|
||||||
|
<StyledCard rounded>
|
||||||
|
<SettingsOptionCardContentToggle
|
||||||
|
Icon={IconSettings}
|
||||||
|
title={t`Settings All Access`}
|
||||||
|
description={t`Ability to edit all settings`}
|
||||||
|
checked={settingsDraftRole.canUpdateAllSettings}
|
||||||
|
disabled={!isEditable}
|
||||||
|
onChange={() => {
|
||||||
|
setSettingsDraftRole({
|
||||||
|
...settingsDraftRole,
|
||||||
|
canUpdateAllSettings: !settingsDraftRole.canUpdateAllSettings,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</StyledCard>
|
||||||
|
)}
|
||||||
|
<StyledTable>
|
||||||
|
<SettingsRolePermissionsSettingsTableHeader />
|
||||||
|
<StyledTableRows>
|
||||||
|
{settingsPermissionsConfig.map((permission) => (
|
||||||
|
<SettingsRolePermissionsSettingsTableRow
|
||||||
|
key={permission.key}
|
||||||
|
roleId={roleId}
|
||||||
|
permission={permission}
|
||||||
|
isEditable={isEditable}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</StyledTableRows>
|
||||||
|
</StyledTable>
|
||||||
|
</Section>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -1,5 +1,5 @@
|
|||||||
|
import { SettingsRolePermissionsSettingPermission } from '@/settings/roles/role-permissions/settings-permissions/types/SettingsRolePermissionsSettingPermission';
|
||||||
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
|
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
|
||||||
import { SettingsRolePermissionsSettingPermission } from '@/settings/roles/types/SettingsRolePermissionsSettingPermission';
|
|
||||||
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
||||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||||
@ -10,6 +10,8 @@ import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDr
|
|||||||
import { settingsPersistedRoleFamilyState } from '@/settings/roles/states/settingsPersistedRoleFamilyState';
|
import { settingsPersistedRoleFamilyState } from '@/settings/roles/states/settingsPersistedRoleFamilyState';
|
||||||
import { settingsRolesIsLoadingState } from '@/settings/roles/states/settingsRolesIsLoadingState';
|
import { settingsRolesIsLoadingState } from '@/settings/roles/states/settingsRolesIsLoadingState';
|
||||||
import { SettingsPath } from '@/types/SettingsPath';
|
import { SettingsPath } from '@/types/SettingsPath';
|
||||||
|
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
||||||
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
||||||
import { TabList } from '@/ui/layout/tab/components/TabList';
|
import { TabList } from '@/ui/layout/tab/components/TabList';
|
||||||
import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState';
|
import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState';
|
||||||
@ -78,6 +80,8 @@ export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => {
|
|||||||
settingsPersistedRoleFamilyState(roleId),
|
settingsPersistedRoleFamilyState(roleId),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { enqueueSnackBar } = useSnackBar();
|
||||||
|
|
||||||
if (!isDefined(settingsRolesIsLoading)) {
|
if (!isDefined(settingsRolesIsLoading)) {
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
@ -110,6 +114,13 @@ export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => {
|
|||||||
settingsPersistedRole,
|
settingsPersistedRole,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (isDefined(dirtyFields.label) && dirtyFields.label === '') {
|
||||||
|
enqueueSnackBar(t`Role name cannot be empty`, {
|
||||||
|
variant: SnackBarVariant.Error,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (isCreateMode) {
|
if (isCreateMode) {
|
||||||
const roleId = v4();
|
const roleId = v4();
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,13 @@ import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-sta
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useSetRecoilState } from 'recoil';
|
import { useSetRecoilState } from 'recoil';
|
||||||
|
|
||||||
export const SettingsRoleCreateEffect = ({ roleId }: { roleId: string }) => {
|
type SettingsRoleCreateEffectProps = {
|
||||||
|
roleId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SettingsRoleCreateEffect = ({
|
||||||
|
roleId,
|
||||||
|
}: SettingsRoleCreateEffectProps) => {
|
||||||
const setSettingsDraftRole = useSetRecoilState(
|
const setSettingsDraftRole = useSetRecoilState(
|
||||||
settingsDraftRoleFamilyState(roleId),
|
settingsDraftRoleFamilyState(roleId),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -4,10 +4,6 @@ import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDr
|
|||||||
import { TitleInput } from '@/ui/input/components/TitleInput';
|
import { TitleInput } from '@/ui/input/components/TitleInput';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
type SettingsRoleLabelContainerProps = {
|
|
||||||
roleId: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const ROLE_LABEL_EDIT_HOTKEY_SCOPE = 'role-label-edit';
|
const ROLE_LABEL_EDIT_HOTKEY_SCOPE = 'role-label-edit';
|
||||||
|
|
||||||
const StyledHeaderTitle = styled.div`
|
const StyledHeaderTitle = styled.div`
|
||||||
@ -21,6 +17,10 @@ const StyledHeaderTitle = styled.div`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
type SettingsRoleLabelContainerProps = {
|
||||||
|
roleId: string;
|
||||||
|
};
|
||||||
|
|
||||||
export const SettingsRoleLabelContainer = ({
|
export const SettingsRoleLabelContainer = ({
|
||||||
roleId,
|
roleId,
|
||||||
}: SettingsRoleLabelContainerProps) => {
|
}: SettingsRoleLabelContainerProps) => {
|
||||||
|
|||||||
@ -16,5 +16,6 @@ export const settingsDraftRoleFamilyState = createFamilyState<Role, string>({
|
|||||||
isEditable: false,
|
isEditable: false,
|
||||||
workspaceMembers: [],
|
workspaceMembers: [],
|
||||||
settingPermissions: [],
|
settingPermissions: [],
|
||||||
|
objectPermissions: [],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,8 +0,0 @@
|
|||||||
import { IconComponent } from 'twenty-ui/display';
|
|
||||||
export type SettingsRolePermissionsObjectPermission = {
|
|
||||||
key: string;
|
|
||||||
label: string;
|
|
||||||
value: boolean;
|
|
||||||
Icon: IconComponent;
|
|
||||||
setValue: (value: boolean) => void;
|
|
||||||
};
|
|
||||||
@ -44,4 +44,5 @@ export enum SettingsPath {
|
|||||||
Roles = 'roles',
|
Roles = 'roles',
|
||||||
RoleCreate = 'roles/create',
|
RoleCreate = 'roles/create',
|
||||||
RoleDetail = 'roles/:roleId',
|
RoleDetail = 'roles/:roleId',
|
||||||
|
RoleObjectLevel = 'roles/:roleId/object/:objectMetadataId',
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,27 @@
|
|||||||
|
import { Navigate, useParams } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { SettingsRolesQueryEffect } from '@/settings/roles/components/SettingsRolesQueryEffect';
|
||||||
|
import { SettingsRolePermissionsObjectLevelObjectForm } from '@/settings/roles/role-permissions/object-level-permissions/object-form/components/SettingsRolePermissionsObjectLevelObjectForm';
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
|
||||||
|
export const SettingsRoleObjectLevel = () => {
|
||||||
|
const { roleId, objectMetadataId } = useParams();
|
||||||
|
|
||||||
|
if (!isDefined(roleId)) {
|
||||||
|
return <Navigate to="/settings/roles" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isDefined(objectMetadataId)) {
|
||||||
|
return <Navigate to={`/settings/roles/${roleId}`} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SettingsRolesQueryEffect />
|
||||||
|
<SettingsRolePermissionsObjectLevelObjectForm
|
||||||
|
roleId={roleId}
|
||||||
|
objectMetadataId={objectMetadataId}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -3,6 +3,7 @@ import { Field, HideField, ObjectType } from '@nestjs/graphql';
|
|||||||
import { Relation } from 'typeorm';
|
import { Relation } from 'typeorm';
|
||||||
|
|
||||||
import { WorkspaceMember } from 'src/engine/core-modules/user/dtos/workspace-member.dto';
|
import { WorkspaceMember } from 'src/engine/core-modules/user/dtos/workspace-member.dto';
|
||||||
|
import { ObjectPermissionDTO } from 'src/engine/metadata-modules/object-permission/dtos/object-permission.dto';
|
||||||
import { UserWorkspaceRoleEntity } from 'src/engine/metadata-modules/role/user-workspace-role.entity';
|
import { UserWorkspaceRoleEntity } from 'src/engine/metadata-modules/role/user-workspace-role.entity';
|
||||||
import { SettingPermissionDTO } from 'src/engine/metadata-modules/setting-permission/dtos/setting-permission.dto';
|
import { SettingPermissionDTO } from 'src/engine/metadata-modules/setting-permission/dtos/setting-permission.dto';
|
||||||
|
|
||||||
@ -46,4 +47,7 @@ export class RoleDTO {
|
|||||||
|
|
||||||
@Field(() => [SettingPermissionDTO], { nullable: true })
|
@Field(() => [SettingPermissionDTO], { nullable: true })
|
||||||
settingPermissions?: SettingPermissionDTO[];
|
settingPermissions?: SettingPermissionDTO[];
|
||||||
|
|
||||||
|
@Field(() => [ObjectPermissionDTO], { nullable: true })
|
||||||
|
objectPermissions?: ObjectPermissionDTO[];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -39,7 +39,11 @@ export class RoleService {
|
|||||||
where: {
|
where: {
|
||||||
workspaceId,
|
workspaceId,
|
||||||
},
|
},
|
||||||
relations: ['userWorkspaceRoles', 'settingPermissions'],
|
relations: [
|
||||||
|
'userWorkspaceRoles',
|
||||||
|
'settingPermissions',
|
||||||
|
'objectPermissions',
|
||||||
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,18 +4,18 @@ export {
|
|||||||
IconAlertCircle,
|
IconAlertCircle,
|
||||||
IconAlertTriangle,
|
IconAlertTriangle,
|
||||||
IconApi,
|
IconApi,
|
||||||
IconAppWindow,
|
|
||||||
IconApps,
|
IconApps,
|
||||||
|
IconAppWindow,
|
||||||
IconArchive,
|
IconArchive,
|
||||||
IconArchiveOff,
|
IconArchiveOff,
|
||||||
IconArrowBackUp,
|
IconArrowBackUp,
|
||||||
IconArrowDown,
|
IconArrowDown,
|
||||||
IconArrowLeft,
|
IconArrowLeft,
|
||||||
IconArrowRight,
|
IconArrowRight,
|
||||||
IconArrowUp,
|
|
||||||
IconArrowUpRight,
|
|
||||||
IconArrowsDiagonal,
|
IconArrowsDiagonal,
|
||||||
IconArrowsVertical,
|
IconArrowsVertical,
|
||||||
|
IconArrowUp,
|
||||||
|
IconArrowUpRight,
|
||||||
IconAt,
|
IconAt,
|
||||||
IconBaselineDensitySmall,
|
IconBaselineDensitySmall,
|
||||||
IconBell,
|
IconBell,
|
||||||
@ -47,8 +47,8 @@ export {
|
|||||||
IconChevronDown,
|
IconChevronDown,
|
||||||
IconChevronLeft,
|
IconChevronLeft,
|
||||||
IconChevronRight,
|
IconChevronRight,
|
||||||
IconChevronUp,
|
|
||||||
IconChevronsRight,
|
IconChevronsRight,
|
||||||
|
IconChevronUp,
|
||||||
IconCircleDot,
|
IconCircleDot,
|
||||||
IconCircleOff,
|
IconCircleOff,
|
||||||
IconCirclePlus,
|
IconCirclePlus,
|
||||||
@ -217,6 +217,7 @@ export {
|
|||||||
IconPaperclip,
|
IconPaperclip,
|
||||||
IconPassword,
|
IconPassword,
|
||||||
IconPencil,
|
IconPencil,
|
||||||
|
IconPencilOff,
|
||||||
IconPercentage,
|
IconPercentage,
|
||||||
IconPhone,
|
IconPhone,
|
||||||
IconPhoto,
|
IconPhoto,
|
||||||
@ -283,6 +284,7 @@ export {
|
|||||||
IconTimelineEvent,
|
IconTimelineEvent,
|
||||||
IconTool,
|
IconTool,
|
||||||
IconTrash,
|
IconTrash,
|
||||||
|
IconTrashOff,
|
||||||
IconTrashX,
|
IconTrashX,
|
||||||
IconTypography,
|
IconTypography,
|
||||||
IconUnlink,
|
IconUnlink,
|
||||||
|
|||||||
@ -65,18 +65,18 @@ export {
|
|||||||
IconAlertCircle,
|
IconAlertCircle,
|
||||||
IconAlertTriangle,
|
IconAlertTriangle,
|
||||||
IconApi,
|
IconApi,
|
||||||
IconAppWindow,
|
|
||||||
IconApps,
|
IconApps,
|
||||||
|
IconAppWindow,
|
||||||
IconArchive,
|
IconArchive,
|
||||||
IconArchiveOff,
|
IconArchiveOff,
|
||||||
IconArrowBackUp,
|
IconArrowBackUp,
|
||||||
IconArrowDown,
|
IconArrowDown,
|
||||||
IconArrowLeft,
|
IconArrowLeft,
|
||||||
IconArrowRight,
|
IconArrowRight,
|
||||||
IconArrowUp,
|
|
||||||
IconArrowUpRight,
|
|
||||||
IconArrowsDiagonal,
|
IconArrowsDiagonal,
|
||||||
IconArrowsVertical,
|
IconArrowsVertical,
|
||||||
|
IconArrowUp,
|
||||||
|
IconArrowUpRight,
|
||||||
IconAt,
|
IconAt,
|
||||||
IconBaselineDensitySmall,
|
IconBaselineDensitySmall,
|
||||||
IconBell,
|
IconBell,
|
||||||
@ -108,8 +108,8 @@ export {
|
|||||||
IconChevronDown,
|
IconChevronDown,
|
||||||
IconChevronLeft,
|
IconChevronLeft,
|
||||||
IconChevronRight,
|
IconChevronRight,
|
||||||
IconChevronUp,
|
|
||||||
IconChevronsRight,
|
IconChevronsRight,
|
||||||
|
IconChevronUp,
|
||||||
IconCircleDot,
|
IconCircleDot,
|
||||||
IconCircleOff,
|
IconCircleOff,
|
||||||
IconCirclePlus,
|
IconCirclePlus,
|
||||||
@ -278,6 +278,7 @@ export {
|
|||||||
IconPaperclip,
|
IconPaperclip,
|
||||||
IconPassword,
|
IconPassword,
|
||||||
IconPencil,
|
IconPencil,
|
||||||
|
IconPencilOff,
|
||||||
IconPercentage,
|
IconPercentage,
|
||||||
IconPhone,
|
IconPhone,
|
||||||
IconPhoto,
|
IconPhoto,
|
||||||
@ -344,6 +345,7 @@ export {
|
|||||||
IconTimelineEvent,
|
IconTimelineEvent,
|
||||||
IconTool,
|
IconTool,
|
||||||
IconTrash,
|
IconTrash,
|
||||||
|
IconTrashOff,
|
||||||
IconTrashX,
|
IconTrashX,
|
||||||
IconTypography,
|
IconTypography,
|
||||||
IconUnlink,
|
IconUnlink,
|
||||||
|
|||||||
@ -19,6 +19,11 @@ export enum CheckboxSize {
|
|||||||
Small = 'small',
|
Small = 'small',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum CheckboxAccent {
|
||||||
|
Blue = 'blue',
|
||||||
|
Orange = 'orange',
|
||||||
|
}
|
||||||
|
|
||||||
type CheckboxProps = {
|
type CheckboxProps = {
|
||||||
checked: boolean;
|
checked: boolean;
|
||||||
indeterminate?: boolean;
|
indeterminate?: boolean;
|
||||||
@ -30,11 +35,13 @@ type CheckboxProps = {
|
|||||||
shape?: CheckboxShape;
|
shape?: CheckboxShape;
|
||||||
className?: string;
|
className?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
accent?: CheckboxAccent;
|
||||||
};
|
};
|
||||||
|
|
||||||
type InputProps = {
|
type InputProps = {
|
||||||
checkboxSize: CheckboxSize;
|
checkboxSize: CheckboxSize;
|
||||||
variant: CheckboxVariant;
|
variant: CheckboxVariant;
|
||||||
|
accent?: CheckboxAccent;
|
||||||
indeterminate?: boolean;
|
indeterminate?: boolean;
|
||||||
hoverable?: boolean;
|
hoverable?: boolean;
|
||||||
shape?: CheckboxShape;
|
shape?: CheckboxShape;
|
||||||
@ -68,12 +75,14 @@ const StyledInputContainer = styled.div<InputProps>`
|
|||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
position: relative;
|
position: relative;
|
||||||
${({ hoverable, isChecked, theme, indeterminate, disabled }) => {
|
${({ hoverable, isChecked, theme, indeterminate, disabled, accent }) => {
|
||||||
if (!hoverable || disabled === true) return '';
|
if (!hoverable || disabled === true) return '';
|
||||||
return `&:hover{
|
return `&:hover{
|
||||||
background-color: ${
|
background-color: ${
|
||||||
indeterminate || isChecked
|
indeterminate || isChecked
|
||||||
? theme.background.transparent.blue
|
? accent === CheckboxAccent.Blue
|
||||||
|
? theme.background.transparent.blue
|
||||||
|
: theme.background.transparent.orange
|
||||||
: theme.background.transparent.light
|
: theme.background.transparent.light
|
||||||
};
|
};
|
||||||
}}
|
}}
|
||||||
@ -100,9 +109,15 @@ const StyledInput = styled.input<InputProps>`
|
|||||||
& + label:before {
|
& + label:before {
|
||||||
--size: ${({ checkboxSize }) =>
|
--size: ${({ checkboxSize }) =>
|
||||||
checkboxSize === CheckboxSize.Large ? '18px' : '12px'};
|
checkboxSize === CheckboxSize.Large ? '18px' : '12px'};
|
||||||
background: ${({ theme, indeterminate, isChecked, disabled }) => {
|
background: ${({ theme, indeterminate, isChecked, disabled, accent }) => {
|
||||||
if (!(indeterminate || isChecked)) return 'transparent';
|
if (!(indeterminate || isChecked)) return 'transparent';
|
||||||
return disabled ? theme.adaptiveColors.blue3 : theme.color.blue;
|
return disabled
|
||||||
|
? accent === CheckboxAccent.Blue
|
||||||
|
? theme.adaptiveColors.blue3
|
||||||
|
: theme.adaptiveColors.orange3
|
||||||
|
: accent === CheckboxAccent.Blue
|
||||||
|
? theme.color.blue
|
||||||
|
: theme.color.orange;
|
||||||
}};
|
}};
|
||||||
border-color: ${({
|
border-color: ${({
|
||||||
theme,
|
theme,
|
||||||
@ -110,10 +125,17 @@ const StyledInput = styled.input<InputProps>`
|
|||||||
isChecked,
|
isChecked,
|
||||||
variant,
|
variant,
|
||||||
disabled,
|
disabled,
|
||||||
|
accent,
|
||||||
}) => {
|
}) => {
|
||||||
switch (true) {
|
switch (true) {
|
||||||
case indeterminate || isChecked:
|
case indeterminate || isChecked:
|
||||||
return disabled ? theme.adaptiveColors.blue3 : theme.color.blue;
|
return disabled
|
||||||
|
? accent === CheckboxAccent.Blue
|
||||||
|
? theme.adaptiveColors.blue3
|
||||||
|
: theme.adaptiveColors.orange3
|
||||||
|
: accent === CheckboxAccent.Blue
|
||||||
|
? theme.color.blue
|
||||||
|
: theme.color.orange;
|
||||||
case disabled:
|
case disabled:
|
||||||
return theme.border.color.strong;
|
return theme.border.color.strong;
|
||||||
case variant === CheckboxVariant.Primary:
|
case variant === CheckboxVariant.Primary:
|
||||||
@ -165,6 +187,7 @@ export const Checkbox = ({
|
|||||||
hoverable = true,
|
hoverable = true,
|
||||||
className,
|
className,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
|
accent = CheckboxAccent.Blue,
|
||||||
}: CheckboxProps) => {
|
}: CheckboxProps) => {
|
||||||
const [isInternalChecked, setIsInternalChecked] =
|
const [isInternalChecked, setIsInternalChecked] =
|
||||||
React.useState<boolean>(false);
|
React.useState<boolean>(false);
|
||||||
@ -191,6 +214,7 @@ export const Checkbox = ({
|
|||||||
indeterminate={indeterminate}
|
indeterminate={indeterminate}
|
||||||
className={className}
|
className={className}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
accent={accent}
|
||||||
>
|
>
|
||||||
<StyledInput
|
<StyledInput
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
@ -206,6 +230,7 @@ export const Checkbox = ({
|
|||||||
isChecked={isInternalChecked}
|
isChecked={isInternalChecked}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
accent={accent}
|
||||||
/>
|
/>
|
||||||
<label htmlFor={checkboxId}>
|
<label htmlFor={checkboxId}>
|
||||||
{indeterminate ? (
|
{indeterminate ? (
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import {
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
Checkbox,
|
Checkbox,
|
||||||
|
CheckboxAccent,
|
||||||
CheckboxShape,
|
CheckboxShape,
|
||||||
CheckboxSize,
|
CheckboxSize,
|
||||||
CheckboxVariant,
|
CheckboxVariant,
|
||||||
@ -29,6 +30,7 @@ export const Default: Story = {
|
|||||||
variant: CheckboxVariant.Primary,
|
variant: CheckboxVariant.Primary,
|
||||||
size: CheckboxSize.Small,
|
size: CheckboxSize.Small,
|
||||||
shape: CheckboxShape.Squared,
|
shape: CheckboxShape.Squared,
|
||||||
|
accent: CheckboxAccent.Blue,
|
||||||
},
|
},
|
||||||
decorators: [ComponentDecorator],
|
decorators: [ComponentDecorator],
|
||||||
};
|
};
|
||||||
@ -42,6 +44,7 @@ export const Catalog: CatalogStory<Story, typeof Checkbox> = {
|
|||||||
checked: { control: false },
|
checked: { control: false },
|
||||||
hoverable: { control: false },
|
hoverable: { control: false },
|
||||||
shape: { control: false },
|
shape: { control: false },
|
||||||
|
accent: { control: false },
|
||||||
},
|
},
|
||||||
parameters: {
|
parameters: {
|
||||||
catalog: {
|
catalog: {
|
||||||
@ -82,6 +85,11 @@ export const Catalog: CatalogStory<Story, typeof Checkbox> = {
|
|||||||
values: Object.values(CheckboxSize),
|
values: Object.values(CheckboxSize),
|
||||||
props: (size: CheckboxSize) => ({ size }),
|
props: (size: CheckboxSize) => ({ size }),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'accent',
|
||||||
|
values: Object.values(CheckboxAccent),
|
||||||
|
props: (accent: CheckboxAccent) => ({ accent }),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -82,6 +82,7 @@ export {
|
|||||||
CheckboxVariant,
|
CheckboxVariant,
|
||||||
CheckboxShape,
|
CheckboxShape,
|
||||||
CheckboxSize,
|
CheckboxSize,
|
||||||
|
CheckboxAccent,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
} from './components/Checkbox';
|
} from './components/Checkbox';
|
||||||
export { IconListViewGrip } from './components/IconListViewGrip';
|
export { IconListViewGrip } from './components/IconListViewGrip';
|
||||||
|
|||||||
@ -23,6 +23,7 @@ export const BACKGROUND_DARK = {
|
|||||||
lighter: RGBA(GRAY_SCALE.gray0, 0.03),
|
lighter: RGBA(GRAY_SCALE.gray0, 0.03),
|
||||||
danger: RGBA(COLOR.red, 0.08),
|
danger: RGBA(COLOR.red, 0.08),
|
||||||
blue: RGBA(COLOR.blue, 0.2),
|
blue: RGBA(COLOR.blue, 0.2),
|
||||||
|
orange: RGBA(COLOR.orange, 0.2),
|
||||||
},
|
},
|
||||||
overlayPrimary: RGBA(GRAY_SCALE.gray100, 0.8),
|
overlayPrimary: RGBA(GRAY_SCALE.gray100, 0.8),
|
||||||
overlaySecondary: RGBA(GRAY_SCALE.gray100, 0.6),
|
overlaySecondary: RGBA(GRAY_SCALE.gray100, 0.6),
|
||||||
|
|||||||
@ -23,6 +23,7 @@ export const BACKGROUND_LIGHT = {
|
|||||||
lighter: RGBA(GRAY_SCALE.gray100, 0.02),
|
lighter: RGBA(GRAY_SCALE.gray100, 0.02),
|
||||||
danger: RGBA(COLOR.red, 0.08),
|
danger: RGBA(COLOR.red, 0.08),
|
||||||
blue: RGBA(COLOR.blue, 0.08),
|
blue: RGBA(COLOR.blue, 0.08),
|
||||||
|
orange: RGBA(COLOR.orange, 0.08),
|
||||||
},
|
},
|
||||||
overlayPrimary: RGBA(GRAY_SCALE.gray80, 0.8),
|
overlayPrimary: RGBA(GRAY_SCALE.gray80, 0.8),
|
||||||
overlaySecondary: RGBA(GRAY_SCALE.gray80, 0.4),
|
overlaySecondary: RGBA(GRAY_SCALE.gray80, 0.4),
|
||||||
|
|||||||
Reference in New Issue
Block a user