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:
@ -380,6 +380,7 @@ export type CreateRoleInput = {
|
|||||||
canUpdateAllSettings?: InputMaybe<Scalars['Boolean']['input']>;
|
canUpdateAllSettings?: InputMaybe<Scalars['Boolean']['input']>;
|
||||||
description?: InputMaybe<Scalars['String']['input']>;
|
description?: InputMaybe<Scalars['String']['input']>;
|
||||||
icon?: InputMaybe<Scalars['String']['input']>;
|
icon?: InputMaybe<Scalars['String']['input']>;
|
||||||
|
id?: InputMaybe<Scalars['String']['input']>;
|
||||||
label: Scalars['String']['input'];
|
label: Scalars['String']['input'];
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -515,6 +516,7 @@ export enum EnvironmentVariablesGroup {
|
|||||||
GoogleAuth = 'GoogleAuth',
|
GoogleAuth = 'GoogleAuth',
|
||||||
LLM = 'LLM',
|
LLM = 'LLM',
|
||||||
Logging = 'Logging',
|
Logging = 'Logging',
|
||||||
|
Metering = 'Metering',
|
||||||
MicrosoftAuth = 'MicrosoftAuth',
|
MicrosoftAuth = 'MicrosoftAuth',
|
||||||
Other = 'Other',
|
Other = 'Other',
|
||||||
RateLimiting = 'RateLimiting',
|
RateLimiting = 'RateLimiting',
|
||||||
@ -557,7 +559,6 @@ export type FeatureFlag = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export enum FeatureFlagKey {
|
export enum FeatureFlagKey {
|
||||||
IsAdvancedFiltersEnabled = 'IsAdvancedFiltersEnabled',
|
|
||||||
IsAirtableIntegrationEnabled = 'IsAirtableIntegrationEnabled',
|
IsAirtableIntegrationEnabled = 'IsAirtableIntegrationEnabled',
|
||||||
IsAnalyticsV2Enabled = 'IsAnalyticsV2Enabled',
|
IsAnalyticsV2Enabled = 'IsAnalyticsV2Enabled',
|
||||||
IsApprovedAccessDomainsEnabled = 'IsApprovedAccessDomainsEnabled',
|
IsApprovedAccessDomainsEnabled = 'IsApprovedAccessDomainsEnabled',
|
||||||
|
|||||||
@ -316,6 +316,7 @@ export type CreateRoleInput = {
|
|||||||
canUpdateAllSettings?: InputMaybe<Scalars['Boolean']>;
|
canUpdateAllSettings?: InputMaybe<Scalars['Boolean']>;
|
||||||
description?: InputMaybe<Scalars['String']>;
|
description?: InputMaybe<Scalars['String']>;
|
||||||
icon?: InputMaybe<Scalars['String']>;
|
icon?: InputMaybe<Scalars['String']>;
|
||||||
|
id?: InputMaybe<Scalars['String']>;
|
||||||
label: Scalars['String'];
|
label: Scalars['String'];
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -446,6 +447,7 @@ export enum EnvironmentVariablesGroup {
|
|||||||
GoogleAuth = 'GoogleAuth',
|
GoogleAuth = 'GoogleAuth',
|
||||||
LLM = 'LLM',
|
LLM = 'LLM',
|
||||||
Logging = 'Logging',
|
Logging = 'Logging',
|
||||||
|
Metering = 'Metering',
|
||||||
MicrosoftAuth = 'MicrosoftAuth',
|
MicrosoftAuth = 'MicrosoftAuth',
|
||||||
Other = 'Other',
|
Other = 'Other',
|
||||||
RateLimiting = 'RateLimiting',
|
RateLimiting = 'RateLimiting',
|
||||||
@ -488,7 +490,6 @@ export type FeatureFlag = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export enum FeatureFlagKey {
|
export enum FeatureFlagKey {
|
||||||
IsAdvancedFiltersEnabled = 'IsAdvancedFiltersEnabled',
|
|
||||||
IsAirtableIntegrationEnabled = 'IsAirtableIntegrationEnabled',
|
IsAirtableIntegrationEnabled = 'IsAirtableIntegrationEnabled',
|
||||||
IsAnalyticsV2Enabled = 'IsAnalyticsV2Enabled',
|
IsAnalyticsV2Enabled = 'IsAnalyticsV2Enabled',
|
||||||
IsApprovedAccessDomainsEnabled = 'IsApprovedAccessDomainsEnabled',
|
IsApprovedAccessDomainsEnabled = 'IsApprovedAccessDomainsEnabled',
|
||||||
@ -2594,6 +2595,20 @@ export type UpdateLabPublicFeatureFlagMutation = { __typename?: 'Mutation', upda
|
|||||||
|
|
||||||
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 CreateOneRoleMutationVariables = Exact<{
|
||||||
|
createRoleInput: CreateRoleInput;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
export type CreateOneRoleMutation = { __typename?: 'Mutation', createOneRole: { __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 UpdateOneRoleMutationVariables = Exact<{
|
||||||
|
updateRoleInput: UpdateRoleInput;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
export type UpdateOneRoleMutation = { __typename?: 'Mutation', updateOneRole: { __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 UpdateWorkspaceMemberRoleMutationVariables = Exact<{
|
export type UpdateWorkspaceMemberRoleMutationVariables = Exact<{
|
||||||
workspaceMemberId: Scalars['String'];
|
workspaceMemberId: Scalars['String'];
|
||||||
roleId: Scalars['String'];
|
roleId: Scalars['String'];
|
||||||
@ -4605,6 +4620,72 @@ export function useUpdateLabPublicFeatureFlagMutation(baseOptions?: Apollo.Mutat
|
|||||||
export type UpdateLabPublicFeatureFlagMutationHookResult = ReturnType<typeof useUpdateLabPublicFeatureFlagMutation>;
|
export type UpdateLabPublicFeatureFlagMutationHookResult = ReturnType<typeof useUpdateLabPublicFeatureFlagMutation>;
|
||||||
export type UpdateLabPublicFeatureFlagMutationResult = Apollo.MutationResult<UpdateLabPublicFeatureFlagMutation>;
|
export type UpdateLabPublicFeatureFlagMutationResult = Apollo.MutationResult<UpdateLabPublicFeatureFlagMutation>;
|
||||||
export type UpdateLabPublicFeatureFlagMutationOptions = Apollo.BaseMutationOptions<UpdateLabPublicFeatureFlagMutation, UpdateLabPublicFeatureFlagMutationVariables>;
|
export type UpdateLabPublicFeatureFlagMutationOptions = Apollo.BaseMutationOptions<UpdateLabPublicFeatureFlagMutation, UpdateLabPublicFeatureFlagMutationVariables>;
|
||||||
|
export const CreateOneRoleDocument = gql`
|
||||||
|
mutation CreateOneRole($createRoleInput: CreateRoleInput!) {
|
||||||
|
createOneRole(createRoleInput: $createRoleInput) {
|
||||||
|
...RoleFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
${RoleFragmentFragmentDoc}`;
|
||||||
|
export type CreateOneRoleMutationFn = Apollo.MutationFunction<CreateOneRoleMutation, CreateOneRoleMutationVariables>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useCreateOneRoleMutation__
|
||||||
|
*
|
||||||
|
* To run a mutation, you first call `useCreateOneRoleMutation` within a React component and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useCreateOneRoleMutation` returns a tuple that includes:
|
||||||
|
* - A mutate function that you can call at any time to execute the mutation
|
||||||
|
* - An object with fields that represent the current status of the mutation's execution
|
||||||
|
*
|
||||||
|
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const [createOneRoleMutation, { data, loading, error }] = useCreateOneRoleMutation({
|
||||||
|
* variables: {
|
||||||
|
* createRoleInput: // value for 'createRoleInput'
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useCreateOneRoleMutation(baseOptions?: Apollo.MutationHookOptions<CreateOneRoleMutation, CreateOneRoleMutationVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useMutation<CreateOneRoleMutation, CreateOneRoleMutationVariables>(CreateOneRoleDocument, options);
|
||||||
|
}
|
||||||
|
export type CreateOneRoleMutationHookResult = ReturnType<typeof useCreateOneRoleMutation>;
|
||||||
|
export type CreateOneRoleMutationResult = Apollo.MutationResult<CreateOneRoleMutation>;
|
||||||
|
export type CreateOneRoleMutationOptions = Apollo.BaseMutationOptions<CreateOneRoleMutation, CreateOneRoleMutationVariables>;
|
||||||
|
export const UpdateOneRoleDocument = gql`
|
||||||
|
mutation UpdateOneRole($updateRoleInput: UpdateRoleInput!) {
|
||||||
|
updateOneRole(updateRoleInput: $updateRoleInput) {
|
||||||
|
...RoleFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
${RoleFragmentFragmentDoc}`;
|
||||||
|
export type UpdateOneRoleMutationFn = Apollo.MutationFunction<UpdateOneRoleMutation, UpdateOneRoleMutationVariables>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useUpdateOneRoleMutation__
|
||||||
|
*
|
||||||
|
* To run a mutation, you first call `useUpdateOneRoleMutation` within a React component and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useUpdateOneRoleMutation` returns a tuple that includes:
|
||||||
|
* - A mutate function that you can call at any time to execute the mutation
|
||||||
|
* - An object with fields that represent the current status of the mutation's execution
|
||||||
|
*
|
||||||
|
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const [updateOneRoleMutation, { data, loading, error }] = useUpdateOneRoleMutation({
|
||||||
|
* variables: {
|
||||||
|
* updateRoleInput: // value for 'updateRoleInput'
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useUpdateOneRoleMutation(baseOptions?: Apollo.MutationHookOptions<UpdateOneRoleMutation, UpdateOneRoleMutationVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useMutation<UpdateOneRoleMutation, UpdateOneRoleMutationVariables>(UpdateOneRoleDocument, options);
|
||||||
|
}
|
||||||
|
export type UpdateOneRoleMutationHookResult = ReturnType<typeof useUpdateOneRoleMutation>;
|
||||||
|
export type UpdateOneRoleMutationResult = Apollo.MutationResult<UpdateOneRoleMutation>;
|
||||||
|
export type UpdateOneRoleMutationOptions = Apollo.BaseMutationOptions<UpdateOneRoleMutation, UpdateOneRoleMutationVariables>;
|
||||||
export const UpdateWorkspaceMemberRoleDocument = gql`
|
export const UpdateWorkspaceMemberRoleDocument = gql`
|
||||||
mutation UpdateWorkspaceMemberRole($workspaceMemberId: String!, $roleId: String!) {
|
mutation UpdateWorkspaceMemberRole($workspaceMemberId: String!, $roleId: String!) {
|
||||||
updateWorkspaceMemberRole(
|
updateWorkspaceMemberRole(
|
||||||
|
|||||||
@ -301,6 +301,12 @@ const SettingsRoles = lazy(() =>
|
|||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const SettingsRoleCreate = lazy(() =>
|
||||||
|
import('~/pages/settings/roles/SettingsRoleCreate').then((module) => ({
|
||||||
|
default: module.SettingsRoleCreate,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
|
||||||
const SettingsRoleEdit = lazy(() =>
|
const SettingsRoleEdit = lazy(() =>
|
||||||
import('~/pages/settings/roles/SettingsRoleEdit').then((module) => ({
|
import('~/pages/settings/roles/SettingsRoleEdit').then((module) => ({
|
||||||
default: module.SettingsRoleEdit,
|
default: module.SettingsRoleEdit,
|
||||||
@ -392,6 +398,10 @@ export const SettingsRoutes = ({
|
|||||||
>
|
>
|
||||||
<Route path={SettingsPath.Roles} element={<SettingsRoles />} />
|
<Route path={SettingsPath.Roles} element={<SettingsRoles />} />
|
||||||
<Route path={SettingsPath.RoleDetail} element={<SettingsRoleEdit />} />
|
<Route path={SettingsPath.RoleDetail} element={<SettingsRoleEdit />} />
|
||||||
|
<Route
|
||||||
|
path={SettingsPath.RoleCreate}
|
||||||
|
element={<SettingsRoleCreate />}
|
||||||
|
/>
|
||||||
</Route>
|
</Route>
|
||||||
<Route
|
<Route
|
||||||
element={
|
element={
|
||||||
|
|||||||
@ -62,10 +62,10 @@ import { isAppWaitingForFreshObjectMetadataState } from '@/object-metadata/state
|
|||||||
import { workspaceAuthProvidersState } from '@/workspace/states/workspaceAuthProvidersState';
|
import { workspaceAuthProvidersState } from '@/workspace/states/workspaceAuthProvidersState';
|
||||||
import { i18n } from '@lingui/core';
|
import { i18n } from '@lingui/core';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
import { getWorkspaceUrl } from '~/utils/getWorkspaceUrl';
|
|
||||||
import { dynamicActivate } from '~/utils/i18n/dynamicActivate';
|
|
||||||
import { APP_LOCALES } from 'twenty-shared/translations';
|
import { APP_LOCALES } from 'twenty-shared/translations';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
import { getWorkspaceUrl } from '~/utils/getWorkspaceUrl';
|
||||||
|
import { dynamicActivate } from '~/utils/i18n/dynamicActivate';
|
||||||
|
|
||||||
export const useAuth = () => {
|
export const useAuth = () => {
|
||||||
const setTokenPair = useSetRecoilState(tokenPairState);
|
const setTokenPair = useSetRecoilState(tokenPairState);
|
||||||
|
|||||||
@ -1,49 +0,0 @@
|
|||||||
import { Table } from '@/ui/layout/table/components/Table';
|
|
||||||
import styled from '@emotion/styled';
|
|
||||||
import { t } from '@lingui/core/macro';
|
|
||||||
|
|
||||||
import { RolesTableHeader } from '@/settings/roles/components/RolesTableHeader';
|
|
||||||
import { RolesTableRow } from '@/settings/roles/components/RolesTableRow';
|
|
||||||
import { Button, H2Title, IconPlus, Section } from 'twenty-ui';
|
|
||||||
import { Role } from '~/generated-metadata/graphql';
|
|
||||||
|
|
||||||
const StyledCreateRoleSection = 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)};
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const Roles = ({ roles }: { roles: Role[] }) => {
|
|
||||||
return (
|
|
||||||
<Section>
|
|
||||||
<H2Title
|
|
||||||
title={t`All roles`}
|
|
||||||
description={t`Assign roles to specify each member's access permissions`}
|
|
||||||
/>
|
|
||||||
<Table>
|
|
||||||
<RolesTableHeader />
|
|
||||||
<StyledTableRows>
|
|
||||||
{roles.map((role) => (
|
|
||||||
<RolesTableRow key={role.id} role={role} />
|
|
||||||
))}
|
|
||||||
</StyledTableRows>
|
|
||||||
</Table>
|
|
||||||
<StyledCreateRoleSection>
|
|
||||||
<Button
|
|
||||||
Icon={IconPlus}
|
|
||||||
title={t`Create Role`}
|
|
||||||
variant="secondary"
|
|
||||||
size="small"
|
|
||||||
soon
|
|
||||||
/>
|
|
||||||
</StyledCreateRoleSection>
|
|
||||||
</Section>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
import { H3Title } from 'twenty-ui';
|
||||||
|
|
||||||
|
import { SettingsPath } from '@/types/SettingsPath';
|
||||||
|
|
||||||
|
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
||||||
|
|
||||||
|
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||||
|
import { SettingsRoleDefaultRole } from '@/settings/roles/components/SettingsRolesDefaultRole';
|
||||||
|
|
||||||
|
import { SettingsRolesList } from '@/settings/roles/components/SettingsRolesList';
|
||||||
|
import { settingsAllRolesSelector } from '@/settings/roles/states/settingsAllRolesSelector';
|
||||||
|
import { settingsRolesIsLoadingState } from '@/settings/roles/states/settingsRolesIsLoadingState';
|
||||||
|
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
||||||
|
import { Trans, useLingui } from '@lingui/react/macro';
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
|
export const SettingsRolesContainer = () => {
|
||||||
|
const { t } = useLingui();
|
||||||
|
|
||||||
|
const settingsAllRoles = useRecoilValue(settingsAllRolesSelector);
|
||||||
|
const settingsRolesIsLoading = useRecoilValue(settingsRolesIsLoadingState);
|
||||||
|
|
||||||
|
if (settingsRolesIsLoading) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SubMenuTopBarContainer
|
||||||
|
title={<H3Title title={t`Roles`} />}
|
||||||
|
links={[
|
||||||
|
{
|
||||||
|
children: <Trans>Workspace</Trans>,
|
||||||
|
href: getSettingsPath(SettingsPath.Workspace),
|
||||||
|
},
|
||||||
|
{ children: <Trans>Roles</Trans> },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<SettingsPageContainer>
|
||||||
|
<SettingsRolesList />
|
||||||
|
<SettingsRoleDefaultRole roles={settingsAllRoles} />
|
||||||
|
</SettingsPageContainer>
|
||||||
|
</SubMenuTopBarContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -6,15 +6,15 @@ import { SettingsOptionCardContentSelect } from '@/settings/components/SettingsO
|
|||||||
import { Select } from '@/ui/input/components/Select';
|
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 { Card, H2Title, IconUserPin, Section } from 'twenty-ui';
|
import { Card, H2Title, IconUserPin, Section } from 'twenty-ui';
|
||||||
import {
|
import {
|
||||||
Role,
|
Role,
|
||||||
UpdateWorkspaceMutation,
|
UpdateWorkspaceMutation,
|
||||||
useUpdateWorkspaceMutation,
|
useUpdateWorkspaceMutation,
|
||||||
} from '~/generated/graphql';
|
} from '~/generated/graphql';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
|
||||||
|
|
||||||
export const RolesDefaultRole = ({ roles }: { roles: Role[] }) => {
|
export const SettingsRoleDefaultRole = ({ roles }: { roles: Role[] }) => {
|
||||||
const [updateWorkspace] = useUpdateWorkspaceMutation();
|
const [updateWorkspace] = useUpdateWorkspaceMutation();
|
||||||
|
|
||||||
const [currentWorkspace, setCurrentWorkspace] = useRecoilState(
|
const [currentWorkspace, setCurrentWorkspace] = useRecoilState(
|
||||||
@ -0,0 +1,72 @@
|
|||||||
|
import { Table } from '@/ui/layout/table/components/Table';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
import { t } from '@lingui/core/macro';
|
||||||
|
|
||||||
|
import { SettingsRolesTableHeader } from '@/settings/roles/components/SettingsRolesTableHeader';
|
||||||
|
import { SettingsRolesTableRow } from '@/settings/roles/components/SettingsRolesTableRow';
|
||||||
|
import { settingsAllRolesSelector } from '@/settings/roles/states/settingsAllRolesSelector';
|
||||||
|
import { SettingsPath } from '@/types/SettingsPath';
|
||||||
|
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
||||||
|
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
|
import { Button, H2Title, IconPlus, Section } from 'twenty-ui';
|
||||||
|
import { FeatureFlagKey } from '~/generated/graphql';
|
||||||
|
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||||
|
|
||||||
|
const StyledCreateRoleSection = 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)};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledNoRoles = styled(TableCell)`
|
||||||
|
color: ${({ theme }) => theme.font.color.tertiary};
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const SettingsRolesList = () => {
|
||||||
|
const navigateSettings = useNavigateSettings();
|
||||||
|
const isPermissionsV2Enabled = useIsFeatureEnabled(
|
||||||
|
FeatureFlagKey.IsPermissionsV2Enabled,
|
||||||
|
);
|
||||||
|
|
||||||
|
const settingsAllRoles = useRecoilValue(settingsAllRolesSelector);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Section>
|
||||||
|
<H2Title
|
||||||
|
title={t`All roles`}
|
||||||
|
description={t`Assign roles to specify each member's access permissions`}
|
||||||
|
/>
|
||||||
|
<Table>
|
||||||
|
<SettingsRolesTableHeader />
|
||||||
|
<StyledTableRows>
|
||||||
|
{settingsAllRoles.length === 0 ? (
|
||||||
|
<StyledNoRoles>{t`No roles found`}</StyledNoRoles>
|
||||||
|
) : (
|
||||||
|
settingsAllRoles.map((role) => (
|
||||||
|
<SettingsRolesTableRow key={role.id} role={role} />
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</StyledTableRows>
|
||||||
|
</Table>
|
||||||
|
<StyledCreateRoleSection>
|
||||||
|
<Button
|
||||||
|
Icon={IconPlus}
|
||||||
|
title={t`Create Role`}
|
||||||
|
variant="secondary"
|
||||||
|
size="small"
|
||||||
|
soon={!isPermissionsV2Enabled}
|
||||||
|
disabled={!isPermissionsV2Enabled}
|
||||||
|
onClick={() => navigateSettings(SettingsPath.RoleCreate)}
|
||||||
|
/>
|
||||||
|
</StyledCreateRoleSection>
|
||||||
|
</Section>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
import { settingsPersistedRoleFamilyState } from '@/settings/roles/states/settingsPersistedRoleFamilyState';
|
||||||
|
import { settingsRoleIdsState } from '@/settings/roles/states/settingsRoleIdsState';
|
||||||
|
import { settingsRolesIsLoadingState } from '@/settings/roles/states/settingsRolesIsLoadingState';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useRecoilCallback, useSetRecoilState } from 'recoil';
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
import { Role, useGetRolesQuery } from '~/generated/graphql';
|
||||||
|
|
||||||
|
export const SettingsRolesQueryEffect = () => {
|
||||||
|
const { data, loading } = useGetRolesQuery({
|
||||||
|
fetchPolicy: 'network-only',
|
||||||
|
});
|
||||||
|
|
||||||
|
const setSettingsRolesIsLoading = useSetRecoilState(
|
||||||
|
settingsRolesIsLoadingState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const populateRoles = useRecoilCallback(
|
||||||
|
({ set }) =>
|
||||||
|
(roles: Role[]) => {
|
||||||
|
const roleIds = roles.map((role) => role.id);
|
||||||
|
set(settingsRoleIdsState, roleIds);
|
||||||
|
roles.forEach((role) => {
|
||||||
|
set(settingsPersistedRoleFamilyState(role.id), role);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSettingsRolesIsLoading(loading);
|
||||||
|
if (!loading) {
|
||||||
|
const roles = data?.getRoles;
|
||||||
|
if (!isDefined(roles)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
populateRoles(roles);
|
||||||
|
}
|
||||||
|
}, [data, loading, populateRoles, setSettingsRolesIsLoading]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
@ -3,7 +3,7 @@ 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 { Trans } from '@lingui/react/macro';
|
import { Trans } from '@lingui/react/macro';
|
||||||
|
|
||||||
export const RolesTableHeader = () => {
|
export const SettingsRolesTableHeader = () => {
|
||||||
return (
|
return (
|
||||||
<Table>
|
<Table>
|
||||||
<TableRow gridAutoColumns="332px 3fr 2fr 1fr">
|
<TableRow gridAutoColumns="332px 3fr 2fr 1fr">
|
||||||
@ -52,7 +52,7 @@ const StyledTableRow = styled(TableRow)`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const RolesTableRow = ({ role }: { role: Role }) => {
|
export const SettingsRolesTableRow = ({ role }: { role: Role }) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
const navigateSettings = useNavigateSettings();
|
const navigateSettings = useNavigateSettings();
|
||||||
@ -1,24 +0,0 @@
|
|||||||
import { Meta, StoryObj } from '@storybook/react';
|
|
||||||
import { ComponentDecorator, RouterDecorator } from 'twenty-ui';
|
|
||||||
|
|
||||||
import { Roles } from '@/settings/roles/components/Roles';
|
|
||||||
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
|
||||||
import { getRolesMock } from '~/testing/mock-data/roles';
|
|
||||||
|
|
||||||
const meta: Meta<typeof Roles> = {
|
|
||||||
title: 'Modules/Settings/Roles/Roles',
|
|
||||||
component: Roles,
|
|
||||||
decorators: [ComponentDecorator, I18nFrontDecorator, RouterDecorator],
|
|
||||||
parameters: {
|
|
||||||
maxWidth: 800,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default meta;
|
|
||||||
type Story = StoryObj<typeof Roles>;
|
|
||||||
|
|
||||||
export const Default: Story = {
|
|
||||||
args: {
|
|
||||||
roles: getRolesMock(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@ -1,43 +0,0 @@
|
|||||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
|
||||||
import { RolesDefaultRole } from '@/settings/roles/components/RolesDefaultRole';
|
|
||||||
import { Meta, StoryObj } from '@storybook/react';
|
|
||||||
import { RecoilRoot } from 'recoil';
|
|
||||||
import { ComponentDecorator } from 'twenty-ui';
|
|
||||||
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
|
||||||
import { getRolesMock } from '~/testing/mock-data/roles';
|
|
||||||
import { mockCurrentWorkspace } from '~/testing/mock-data/users';
|
|
||||||
|
|
||||||
const rolesMock = getRolesMock();
|
|
||||||
|
|
||||||
const RolesDefaultRoleWrapper = () => {
|
|
||||||
return (
|
|
||||||
<RecoilRoot
|
|
||||||
initializeState={(snapshot) => {
|
|
||||||
snapshot.set(currentWorkspaceState, {
|
|
||||||
...mockCurrentWorkspace,
|
|
||||||
defaultRole: rolesMock[1],
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<RolesDefaultRole roles={rolesMock} />
|
|
||||||
</RecoilRoot>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const meta: Meta<typeof RolesDefaultRoleWrapper> = {
|
|
||||||
title: 'Modules/Settings/Roles/RolesDefaultRole',
|
|
||||||
component: RolesDefaultRoleWrapper,
|
|
||||||
decorators: [ComponentDecorator, I18nFrontDecorator],
|
|
||||||
parameters: {
|
|
||||||
maxWidth: 800,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default meta;
|
|
||||||
type Story = StoryObj<typeof RolesDefaultRoleWrapper>;
|
|
||||||
|
|
||||||
export const Default: Story = {
|
|
||||||
args: {
|
|
||||||
roles: rolesMock,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
import { ROLE_FRAGMENT } from '@/settings/roles/graphql/fragments/roleFragment';
|
||||||
|
import { gql } from '@apollo/client';
|
||||||
|
|
||||||
|
export const CREATE_ROLE = gql`
|
||||||
|
${ROLE_FRAGMENT}
|
||||||
|
mutation CreateOneRole($createRoleInput: CreateRoleInput!) {
|
||||||
|
createOneRole(createRoleInput: $createRoleInput) {
|
||||||
|
...RoleFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
import { ROLE_FRAGMENT } from '@/settings/roles/graphql/fragments/roleFragment';
|
||||||
|
import { gql } from '@apollo/client';
|
||||||
|
|
||||||
|
export const UPDATE_ROLE = gql`
|
||||||
|
${ROLE_FRAGMENT}
|
||||||
|
mutation UpdateOneRole($updateRoleInput: UpdateRoleInput!) {
|
||||||
|
updateOneRole(updateRoleInput: $updateRoleInput) {
|
||||||
|
...RoleFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
@ -0,0 +1,105 @@
|
|||||||
|
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
|
||||||
|
import { settingsPersistedRoleFamilyState } from '@/settings/roles/states/settingsPersistedRoleFamilyState';
|
||||||
|
import { useRecoilState, useSetRecoilState } from 'recoil';
|
||||||
|
import {
|
||||||
|
useUpdateWorkspaceMemberRoleMutation,
|
||||||
|
WorkspaceMember,
|
||||||
|
} from '~/generated/graphql';
|
||||||
|
|
||||||
|
type AddWorkspaceMemberToRoleAndUpdateStateParams = {
|
||||||
|
workspaceMemberId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type UpdateWorkspaceMemberRoleDraftStateParams = {
|
||||||
|
workspaceMember: WorkspaceMember;
|
||||||
|
};
|
||||||
|
|
||||||
|
type AddWorkspaceMembersToRoleParams = {
|
||||||
|
roleId: string;
|
||||||
|
workspaceMemberIds: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useUpdateWorkspaceMemberRole = (roleId: string) => {
|
||||||
|
const setSettingsPersistedRole = useSetRecoilState(
|
||||||
|
settingsPersistedRoleFamilyState(roleId),
|
||||||
|
);
|
||||||
|
const [settingsDraftRole, setSettingsDraftRole] = useRecoilState(
|
||||||
|
settingsDraftRoleFamilyState(roleId),
|
||||||
|
);
|
||||||
|
|
||||||
|
const [updateWorkspaceMemberRoleMutation] =
|
||||||
|
useUpdateWorkspaceMemberRoleMutation();
|
||||||
|
|
||||||
|
const updateWorkspaceMemberRoleDraftState = ({
|
||||||
|
workspaceMember,
|
||||||
|
}: UpdateWorkspaceMemberRoleDraftStateParams) => {
|
||||||
|
setSettingsDraftRole({
|
||||||
|
...settingsDraftRole,
|
||||||
|
workspaceMembers: [
|
||||||
|
...settingsDraftRole.workspaceMembers,
|
||||||
|
{
|
||||||
|
id: workspaceMember.id,
|
||||||
|
name: workspaceMember.name,
|
||||||
|
colorScheme: workspaceMember.colorScheme,
|
||||||
|
userEmail: workspaceMember.userEmail,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const addWorkspaceMemberToRoleAndUpdateState = async ({
|
||||||
|
workspaceMemberId,
|
||||||
|
}: AddWorkspaceMemberToRoleAndUpdateStateParams) => {
|
||||||
|
const { data } = await updateWorkspaceMemberRoleMutation({
|
||||||
|
variables: {
|
||||||
|
workspaceMemberId,
|
||||||
|
roleId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data?.updateWorkspaceMemberRole !== undefined) {
|
||||||
|
const updatedWorkspaceMember = data.updateWorkspaceMemberRole;
|
||||||
|
const updatedWorkspaceMembers = [
|
||||||
|
...settingsDraftRole.workspaceMembers,
|
||||||
|
{
|
||||||
|
id: updatedWorkspaceMember.id,
|
||||||
|
name: updatedWorkspaceMember.name,
|
||||||
|
colorScheme: updatedWorkspaceMember.colorScheme,
|
||||||
|
userEmail: updatedWorkspaceMember.userEmail,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const updatedRole = {
|
||||||
|
...settingsDraftRole,
|
||||||
|
workspaceMembers: updatedWorkspaceMembers,
|
||||||
|
};
|
||||||
|
|
||||||
|
setSettingsPersistedRole(updatedRole);
|
||||||
|
setSettingsDraftRole(updatedRole);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data?.updateWorkspaceMemberRole;
|
||||||
|
};
|
||||||
|
|
||||||
|
const addWorkspaceMembersToRole = async ({
|
||||||
|
roleId,
|
||||||
|
workspaceMemberIds,
|
||||||
|
}: AddWorkspaceMembersToRoleParams) => {
|
||||||
|
await Promise.all(
|
||||||
|
workspaceMemberIds.map((workspaceMemberId) =>
|
||||||
|
updateWorkspaceMemberRoleMutation({
|
||||||
|
variables: {
|
||||||
|
roleId,
|
||||||
|
workspaceMemberId,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
addWorkspaceMemberToRoleAndUpdateState,
|
||||||
|
updateWorkspaceMemberRoleDraftState,
|
||||||
|
addWorkspaceMembersToRole,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -1,8 +1,12 @@
|
|||||||
import { currentWorkspaceMembersState } from '@/auth/states/currentWorkspaceMembersStates';
|
import { currentWorkspaceMembersState } from '@/auth/states/currentWorkspaceMembersStates';
|
||||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||||
import { RoleAssignmentTableHeader } from '@/settings/roles/role-assignment/components/RoleAssignmentTableHeader';
|
import { useUpdateWorkspaceMemberRole } from '@/settings/roles/hooks/useUpdateWorkspaceMemberRole';
|
||||||
import { RoleAssignmentWorkspaceMemberPickerDropdown } from '@/settings/roles/role-assignment/components/RoleAssignmentWorkspaceMemberPickerDropdown';
|
import { SettingsRoleAssignmentConfirmationModal } from '@/settings/roles/role-assignment/components/SettingsRoleAssignmentConfirmationModal';
|
||||||
import { RoleAssignmentConfirmationModalSelectedWorkspaceMember } from '@/settings/roles/role-assignment/types/RoleAssignmentConfirmationModalSelectedWorkspaceMember';
|
import { SettingsRoleAssignmentTableHeader } from '@/settings/roles/role-assignment/components/SettingsRoleAssignmentTableHeader';
|
||||||
|
import { SettingsRoleAssignmentWorkspaceMemberPickerDropdown } from '@/settings/roles/role-assignment/components/SettingsRoleAssignmentWorkspaceMemberPickerDropdown';
|
||||||
|
import { SettingsRoleAssignmentConfirmationModalSelectedWorkspaceMember } from '@/settings/roles/role-assignment/types/SettingsRoleAssignmentConfirmationModalSelectedWorkspaceMember';
|
||||||
|
import { settingsAllRolesSelector } from '@/settings/roles/states/settingsAllRolesSelector';
|
||||||
|
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
|
||||||
import { SettingsPath } from '@/types/SettingsPath';
|
import { SettingsPath } from '@/types/SettingsPath';
|
||||||
import { TextInput } from '@/ui/input/components/TextInput';
|
import { TextInput } from '@/ui/input/components/TextInput';
|
||||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||||
@ -26,14 +30,8 @@ import {
|
|||||||
SearchRecord,
|
SearchRecord,
|
||||||
WorkspaceMember,
|
WorkspaceMember,
|
||||||
} from '~/generated-metadata/graphql';
|
} from '~/generated-metadata/graphql';
|
||||||
import {
|
|
||||||
GetRolesDocument,
|
|
||||||
useGetRolesQuery,
|
|
||||||
useUpdateWorkspaceMemberRoleMutation,
|
|
||||||
} from '~/generated/graphql';
|
|
||||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||||
import { RoleAssignmentConfirmationModal } from './RoleAssignmentConfirmationModal';
|
import { SettingsRoleAssignmentTableRow } from './SettingsRoleAssignmentTableRow';
|
||||||
import { RoleAssignmentTableRow } from './RoleAssignmentTableRow';
|
|
||||||
|
|
||||||
const StyledAssignToMemberContainer = styled.div`
|
const StyledAssignToMemberContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -67,43 +65,51 @@ const StyledNoMembers = styled(TableCell)`
|
|||||||
color: ${({ theme }) => theme.font.color.tertiary};
|
color: ${({ theme }) => theme.font.color.tertiary};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
type RoleAssignmentProps = {
|
type SettingsRoleAssignmentProps = {
|
||||||
role: Pick<Role, 'id' | 'label' | 'canUpdateAllSettings'> & {
|
roleId: string;
|
||||||
workspaceMembers: Array<WorkspaceMember>;
|
isCreateMode?: boolean;
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RoleAssignment = ({ role }: RoleAssignmentProps) => {
|
export const SettingsRoleAssignment = ({
|
||||||
|
roleId,
|
||||||
|
isCreateMode,
|
||||||
|
}: SettingsRoleAssignmentProps) => {
|
||||||
|
const settingsDraftRole = useRecoilValue(
|
||||||
|
settingsDraftRoleFamilyState(roleId),
|
||||||
|
);
|
||||||
|
|
||||||
const navigateSettings = useNavigateSettings();
|
const navigateSettings = useNavigateSettings();
|
||||||
const [updateWorkspaceMemberRole] = useUpdateWorkspaceMemberRoleMutation({
|
const {
|
||||||
refetchQueries: [GetRolesDocument],
|
addWorkspaceMemberToRoleAndUpdateState,
|
||||||
});
|
updateWorkspaceMemberRoleDraftState,
|
||||||
|
} = useUpdateWorkspaceMemberRole(roleId);
|
||||||
|
|
||||||
const [confirmationModalIsOpen, setConfirmationModalIsOpen] =
|
const [confirmationModalIsOpen, setConfirmationModalIsOpen] =
|
||||||
useState<boolean>(false);
|
useState<boolean>(false);
|
||||||
const [selectedWorkspaceMember, setSelectedWorkspaceMember] =
|
const [selectedWorkspaceMember, setSelectedWorkspaceMember] =
|
||||||
useState<RoleAssignmentConfirmationModalSelectedWorkspaceMember | null>(
|
useState<SettingsRoleAssignmentConfirmationModalSelectedWorkspaceMember | null>(
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
const { data: rolesData } = useGetRolesQuery();
|
|
||||||
const { closeDropdown } = useDropdown('role-member-select');
|
const { closeDropdown } = useDropdown('role-member-select');
|
||||||
const [searchFilter, setSearchFilter] = useState('');
|
const [searchFilter, setSearchFilter] = useState('');
|
||||||
const currentWorkspaceMembers = useRecoilValue(currentWorkspaceMembersState);
|
const currentWorkspaceMembers = useRecoilValue(currentWorkspaceMembersState);
|
||||||
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
||||||
|
|
||||||
|
const settingsAllRoles = useRecoilValue(settingsAllRolesSelector);
|
||||||
|
|
||||||
const workspaceMemberRoleMap = new Map<
|
const workspaceMemberRoleMap = new Map<
|
||||||
string,
|
string,
|
||||||
{ id: string; label: string }
|
{ id: string; label: string }
|
||||||
>();
|
>();
|
||||||
rolesData?.getRoles?.forEach((role) => {
|
settingsAllRoles.forEach((role: Role) => {
|
||||||
role.workspaceMembers.forEach((member) => {
|
role.workspaceMembers.forEach((member: WorkspaceMember) => {
|
||||||
workspaceMemberRoleMap.set(member.id, { id: role.id, label: role.label });
|
workspaceMemberRoleMap.set(member.id, { id: role.id, label: role.label });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const filteredWorkspaceMembers = !searchFilter
|
const filteredWorkspaceMembers = !searchFilter
|
||||||
? role.workspaceMembers
|
? settingsDraftRole.workspaceMembers
|
||||||
: role.workspaceMembers.filter((member) => {
|
: settingsDraftRole.workspaceMembers.filter((member) => {
|
||||||
const searchTerm = searchFilter.toLowerCase();
|
const searchTerm = searchFilter.toLowerCase();
|
||||||
const firstName = member.name.firstName?.toLowerCase() || '';
|
const firstName = member.name.firstName?.toLowerCase() || '';
|
||||||
const lastName = member.name.lastName?.toLowerCase() || '';
|
const lastName = member.name.lastName?.toLowerCase() || '';
|
||||||
@ -116,7 +122,7 @@ export const RoleAssignment = ({ role }: RoleAssignmentProps) => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const assignedWorkspaceMemberIds = role.workspaceMembers.map(
|
const assignedWorkspaceMemberIds = settingsDraftRole.workspaceMembers.map(
|
||||||
(workspaceMember) => workspaceMember.id,
|
(workspaceMember) => workspaceMember.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -153,12 +159,28 @@ export const RoleAssignment = ({ role }: RoleAssignmentProps) => {
|
|||||||
const handleConfirm = async () => {
|
const handleConfirm = async () => {
|
||||||
if (!selectedWorkspaceMember || !confirmationModalIsOpen) return;
|
if (!selectedWorkspaceMember || !confirmationModalIsOpen) return;
|
||||||
|
|
||||||
await updateWorkspaceMemberRole({
|
if (!isCreateMode) {
|
||||||
variables: {
|
await addWorkspaceMemberToRoleAndUpdateState({
|
||||||
workspaceMemberId: selectedWorkspaceMember.id,
|
workspaceMemberId: selectedWorkspaceMember.id,
|
||||||
roleId: role.id,
|
});
|
||||||
},
|
} else {
|
||||||
});
|
const workspaceMember = currentWorkspaceMembers.find(
|
||||||
|
(member) => member.id === selectedWorkspaceMember.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!workspaceMember) {
|
||||||
|
throw new Error('Workspace member not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
updateWorkspaceMemberRoleDraftState({
|
||||||
|
workspaceMember: {
|
||||||
|
id: workspaceMember.id,
|
||||||
|
name: workspaceMember.name,
|
||||||
|
colorScheme: '',
|
||||||
|
userEmail: '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
handleModalClose();
|
handleModalClose();
|
||||||
};
|
};
|
||||||
@ -190,11 +212,11 @@ export const RoleAssignment = ({ role }: RoleAssignmentProps) => {
|
|||||||
/>
|
/>
|
||||||
</StyledSearchContainer>
|
</StyledSearchContainer>
|
||||||
<StyledTable>
|
<StyledTable>
|
||||||
<RoleAssignmentTableHeader />
|
<SettingsRoleAssignmentTableHeader />
|
||||||
<StyledTableRows>
|
<StyledTableRows>
|
||||||
{filteredWorkspaceMembers.length > 0 ? (
|
{filteredWorkspaceMembers.length > 0 ? (
|
||||||
filteredWorkspaceMembers.map((workspaceMember) => (
|
filteredWorkspaceMembers.map((workspaceMember) => (
|
||||||
<RoleAssignmentTableRow
|
<SettingsRoleAssignmentTableRow
|
||||||
key={workspaceMember.id}
|
key={workspaceMember.id}
|
||||||
workspaceMember={workspaceMember}
|
workspaceMember={workspaceMember}
|
||||||
/>
|
/>
|
||||||
@ -233,7 +255,7 @@ export const RoleAssignment = ({ role }: RoleAssignmentProps) => {
|
|||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
dropdownComponents={
|
dropdownComponents={
|
||||||
<RoleAssignmentWorkspaceMemberPickerDropdown
|
<SettingsRoleAssignmentWorkspaceMemberPickerDropdown
|
||||||
excludedWorkspaceMemberIds={[
|
excludedWorkspaceMemberIds={[
|
||||||
...assignedWorkspaceMemberIds,
|
...assignedWorkspaceMemberIds,
|
||||||
currentWorkspaceMember?.id,
|
currentWorkspaceMember?.id,
|
||||||
@ -246,7 +268,7 @@ export const RoleAssignment = ({ role }: RoleAssignmentProps) => {
|
|||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
{confirmationModalIsOpen && selectedWorkspaceMember && (
|
{confirmationModalIsOpen && selectedWorkspaceMember && (
|
||||||
<RoleAssignmentConfirmationModal
|
<SettingsRoleAssignmentConfirmationModal
|
||||||
selectedWorkspaceMember={selectedWorkspaceMember}
|
selectedWorkspaceMember={selectedWorkspaceMember}
|
||||||
isOpen={true}
|
isOpen={true}
|
||||||
onClose={handleModalClose}
|
onClose={handleModalClose}
|
||||||
@ -1,23 +1,23 @@
|
|||||||
import { RoleAssignmentConfirmationModalSubtitle } from '@/settings/roles/role-assignment/components/RoleAssignmentConfirmationModalSubtitle';
|
import { SettingsRoleAssignmentConfirmationModalSubtitle } from '@/settings/roles/role-assignment/components/SettingsRoleAssignmentConfirmationModalSubtitle';
|
||||||
import { RoleAssignmentConfirmationModalSelectedWorkspaceMember } from '@/settings/roles/role-assignment/types/RoleAssignmentConfirmationModalSelectedWorkspaceMember';
|
import { SettingsRoleAssignmentConfirmationModalSelectedWorkspaceMember } from '@/settings/roles/role-assignment/types/SettingsRoleAssignmentConfirmationModalSelectedWorkspaceMember';
|
||||||
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
|
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
|
||||||
import { t } from '@lingui/core/macro';
|
import { t } from '@lingui/core/macro';
|
||||||
|
|
||||||
type RoleAssignmentConfirmationModalProps = {
|
type SettingsRoleAssignmentConfirmationModalProps = {
|
||||||
selectedWorkspaceMember: RoleAssignmentConfirmationModalSelectedWorkspaceMember;
|
selectedWorkspaceMember: SettingsRoleAssignmentConfirmationModalSelectedWorkspaceMember;
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onConfirm: () => void;
|
onConfirm: () => void;
|
||||||
onRoleClick: (roleId: string) => void;
|
onRoleClick: (roleId: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RoleAssignmentConfirmationModal = ({
|
export const SettingsRoleAssignmentConfirmationModal = ({
|
||||||
selectedWorkspaceMember,
|
selectedWorkspaceMember,
|
||||||
isOpen,
|
isOpen,
|
||||||
onClose,
|
onClose,
|
||||||
onConfirm,
|
onConfirm,
|
||||||
onRoleClick,
|
onRoleClick,
|
||||||
}: RoleAssignmentConfirmationModalProps) => {
|
}: SettingsRoleAssignmentConfirmationModalProps) => {
|
||||||
const workspaceMemberName = selectedWorkspaceMember.name;
|
const workspaceMemberName = selectedWorkspaceMember.name;
|
||||||
|
|
||||||
const title = t`Assign ${workspaceMemberName}?`;
|
const title = t`Assign ${workspaceMemberName}?`;
|
||||||
@ -28,7 +28,7 @@ export const RoleAssignmentConfirmationModal = ({
|
|||||||
setIsOpen={onClose}
|
setIsOpen={onClose}
|
||||||
title={title}
|
title={title}
|
||||||
subtitle={
|
subtitle={
|
||||||
<RoleAssignmentConfirmationModalSubtitle
|
<SettingsRoleAssignmentConfirmationModalSubtitle
|
||||||
selectedWorkspaceMember={selectedWorkspaceMember}
|
selectedWorkspaceMember={selectedWorkspaceMember}
|
||||||
onRoleClick={onRoleClick}
|
onRoleClick={onRoleClick}
|
||||||
/>
|
/>
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { SettingsCard } from '@/settings/components/SettingsCard';
|
import { SettingsCard } from '@/settings/components/SettingsCard';
|
||||||
import { RoleAssignmentConfirmationModalSelectedWorkspaceMember } from '@/settings/roles/role-assignment/types/RoleAssignmentConfirmationModalSelectedWorkspaceMember';
|
import { SettingsRoleAssignmentConfirmationModalSelectedWorkspaceMember } from '@/settings/roles/role-assignment/types/SettingsRoleAssignmentConfirmationModalSelectedWorkspaceMember';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { t } from '@lingui/core/macro';
|
import { t } from '@lingui/core/macro';
|
||||||
import { Avatar } from 'twenty-ui';
|
import { Avatar } from 'twenty-ui';
|
||||||
@ -8,15 +8,15 @@ const StyledSettingsCardContainer = styled.div`
|
|||||||
margin-top: ${({ theme }) => theme.spacing(6)};
|
margin-top: ${({ theme }) => theme.spacing(6)};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
type RoleAssignmentConfirmationModalSubtitleProps = {
|
type SettingsRoleAssignmentConfirmationModalSubtitleProps = {
|
||||||
selectedWorkspaceMember: RoleAssignmentConfirmationModalSelectedWorkspaceMember;
|
selectedWorkspaceMember: SettingsRoleAssignmentConfirmationModalSelectedWorkspaceMember;
|
||||||
onRoleClick: (roleId: string) => void;
|
onRoleClick: (roleId: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RoleAssignmentConfirmationModalSubtitle = ({
|
export const SettingsRoleAssignmentConfirmationModalSubtitle = ({
|
||||||
selectedWorkspaceMember,
|
selectedWorkspaceMember,
|
||||||
onRoleClick,
|
onRoleClick,
|
||||||
}: RoleAssignmentConfirmationModalSubtitleProps) => {
|
}: SettingsRoleAssignmentConfirmationModalSubtitleProps) => {
|
||||||
const workspaceMemberName = selectedWorkspaceMember.name;
|
const workspaceMemberName = selectedWorkspaceMember.name;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -2,7 +2,7 @@ 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 { t } from '@lingui/core/macro';
|
import { t } from '@lingui/core/macro';
|
||||||
|
|
||||||
export const RoleAssignmentTableHeader = () => (
|
export const SettingsRoleAssignmentTableHeader = () => (
|
||||||
<TableRow gridAutoColumns="2fr 4fr">
|
<TableRow gridAutoColumns="2fr 4fr">
|
||||||
<TableHeader>{t`Name`}</TableHeader>
|
<TableHeader>{t`Name`}</TableHeader>
|
||||||
<TableHeader>{t`Email`}</TableHeader>
|
<TableHeader>{t`Email`}</TableHeader>
|
||||||
@ -28,13 +28,13 @@ const StyledTableCell = styled(TableCell)`
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
type RoleAssignmentTableRowProps = {
|
type SettingsRoleAssignmentTableRowProps = {
|
||||||
workspaceMember: WorkspaceMember;
|
workspaceMember: WorkspaceMember;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RoleAssignmentTableRow = ({
|
export const SettingsRoleAssignmentTableRow = ({
|
||||||
workspaceMember,
|
workspaceMember,
|
||||||
}: RoleAssignmentTableRowProps) => {
|
}: SettingsRoleAssignmentTableRowProps) => {
|
||||||
return (
|
return (
|
||||||
<TableRow gridAutoColumns="2fr 4fr">
|
<TableRow gridAutoColumns="2fr 4fr">
|
||||||
<StyledTableCell>
|
<StyledTableCell>
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
import { useObjectRecordSearchRecords } from '@/object-record/hooks/useObjectRecordSearchRecords';
|
import { useObjectRecordSearchRecords } from '@/object-record/hooks/useObjectRecordSearchRecords';
|
||||||
import { RoleAssignmentWorkspaceMemberPickerDropdownContent } from '@/settings/roles/role-assignment/components/RoleAssignmentWorkspaceMemberPickerDropdownContent';
|
import { SettingsRoleAssignmentWorkspaceMemberPickerDropdownContent } from '@/settings/roles/role-assignment/components/SettingsRoleAssignmentWorkspaceMemberPickerDropdownContent';
|
||||||
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
|
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
|
||||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||||
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
|
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
|
||||||
@ -9,15 +9,15 @@ import { useLingui } from '@lingui/react/macro';
|
|||||||
import { ChangeEvent, useState } from 'react';
|
import { ChangeEvent, useState } from 'react';
|
||||||
import { SearchRecord } from '~/generated-metadata/graphql';
|
import { SearchRecord } from '~/generated-metadata/graphql';
|
||||||
|
|
||||||
type RoleAssignmentWorkspaceMemberPickerDropdownProps = {
|
type SettingsRoleAssignmentWorkspaceMemberPickerDropdownProps = {
|
||||||
excludedWorkspaceMemberIds: string[];
|
excludedWorkspaceMemberIds: string[];
|
||||||
onSelect: (workspaceMemberSearchRecord: SearchRecord) => void;
|
onSelect: (workspaceMemberSearchRecord: SearchRecord) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RoleAssignmentWorkspaceMemberPickerDropdown = ({
|
export const SettingsRoleAssignmentWorkspaceMemberPickerDropdown = ({
|
||||||
excludedWorkspaceMemberIds,
|
excludedWorkspaceMemberIds,
|
||||||
onSelect,
|
onSelect,
|
||||||
}: RoleAssignmentWorkspaceMemberPickerDropdownProps) => {
|
}: SettingsRoleAssignmentWorkspaceMemberPickerDropdownProps) => {
|
||||||
const [searchFilter, setSearchFilter] = useState('');
|
const [searchFilter, setSearchFilter] = useState('');
|
||||||
|
|
||||||
const { loading, searchRecords: workspaceMembers } =
|
const { loading, searchRecords: workspaceMembers } =
|
||||||
@ -46,7 +46,7 @@ export const RoleAssignmentWorkspaceMemberPickerDropdown = ({
|
|||||||
/>
|
/>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<DropdownMenuItemsContainer hasMaxHeight>
|
<DropdownMenuItemsContainer hasMaxHeight>
|
||||||
<RoleAssignmentWorkspaceMemberPickerDropdownContent
|
<SettingsRoleAssignmentWorkspaceMemberPickerDropdownContent
|
||||||
loading={loading}
|
loading={loading}
|
||||||
searchFilter={searchFilter}
|
searchFilter={searchFilter}
|
||||||
filteredWorkspaceMembers={filteredWorkspaceMembers}
|
filteredWorkspaceMembers={filteredWorkspaceMembers}
|
||||||
@ -2,19 +2,19 @@ import { t } from '@lingui/core/macro';
|
|||||||
import { MenuItem, MenuItemAvatar } from 'twenty-ui';
|
import { MenuItem, MenuItemAvatar } from 'twenty-ui';
|
||||||
import { SearchRecord } from '~/generated-metadata/graphql';
|
import { SearchRecord } from '~/generated-metadata/graphql';
|
||||||
|
|
||||||
type RoleAssignmentWorkspaceMemberPickerDropdownContentProps = {
|
type SettingsRoleAssignmentWorkspaceMemberPickerDropdownContentProps = {
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
searchFilter: string;
|
searchFilter: string;
|
||||||
filteredWorkspaceMembers: SearchRecord[];
|
filteredWorkspaceMembers: SearchRecord[];
|
||||||
onSelect: (workspaceMemberSearchRecord: SearchRecord) => void;
|
onSelect: (workspaceMemberSearchRecord: SearchRecord) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RoleAssignmentWorkspaceMemberPickerDropdownContent = ({
|
export const SettingsRoleAssignmentWorkspaceMemberPickerDropdownContent = ({
|
||||||
loading,
|
loading,
|
||||||
searchFilter,
|
searchFilter,
|
||||||
filteredWorkspaceMembers,
|
filteredWorkspaceMembers,
|
||||||
onSelect,
|
onSelect,
|
||||||
}: RoleAssignmentWorkspaceMemberPickerDropdownContentProps) => {
|
}: SettingsRoleAssignmentWorkspaceMemberPickerDropdownContentProps) => {
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -0,0 +1,46 @@
|
|||||||
|
import { SettingsRoleAssignment } from '@/settings/roles/role-assignment/components/SettingsRoleAssignment';
|
||||||
|
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
|
||||||
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
|
import { useSetRecoilState } from 'recoil';
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
import { ComponentDecorator, RouterDecorator } from 'twenty-ui';
|
||||||
|
import { PENDING_ROLE_ID } from '~/pages/settings/roles/SettingsRoleCreate';
|
||||||
|
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
||||||
|
import { getRolesMock } from '~/testing/mock-data/roles';
|
||||||
|
|
||||||
|
const SettingsRoleAssignmentWrapper = (
|
||||||
|
args: React.ComponentProps<typeof SettingsRoleAssignment>,
|
||||||
|
) => {
|
||||||
|
const setDraftRole = useSetRecoilState(
|
||||||
|
settingsDraftRoleFamilyState(args.roleId),
|
||||||
|
);
|
||||||
|
|
||||||
|
const role = getRolesMock().find((role) => role.id === args.roleId);
|
||||||
|
|
||||||
|
if (isDefined(role)) {
|
||||||
|
setDraftRole(role);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <SettingsRoleAssignment roleId={args.roleId} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
const meta: Meta<typeof SettingsRoleAssignmentWrapper> = {
|
||||||
|
title: 'Modules/Settings/Roles/RoleAssignment/SettingsRoleAssignment',
|
||||||
|
component: SettingsRoleAssignmentWrapper,
|
||||||
|
decorators: [RouterDecorator, ComponentDecorator, I18nFrontDecorator],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof SettingsRoleAssignmentWrapper>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
args: {
|
||||||
|
roleId: '1',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PendingRole: Story = {
|
||||||
|
args: {
|
||||||
|
roleId: PENDING_ROLE_ID,
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -1,6 +0,0 @@
|
|||||||
export type RoleAssignmentConfirmationModalSelectedWorkspaceMember = {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
role?: { id: string; label: string };
|
|
||||||
avatarUrl?: string | null;
|
|
||||||
};
|
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
export type SettingsRoleAssignmentConfirmationModalSelectedWorkspaceMember = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
role?: { id: string; label: string };
|
||||||
|
avatarUrl?: string | null;
|
||||||
|
colorScheme?: string;
|
||||||
|
userEmail?: string;
|
||||||
|
};
|
||||||
@ -1,175 +0,0 @@
|
|||||||
import { RolePermissionsObjectsTableHeader } from '@/settings/roles/role-permissions/components/RolePermissionsObjectsTableHeader';
|
|
||||||
import { RolePermissionsSettingsTableHeader } from '@/settings/roles/role-permissions/components/RolePermissionsSettingsTableHeader';
|
|
||||||
import { RolePermissionsSettingsTableRow } from '@/settings/roles/role-permissions/components/RolePermissionsSettingsTableRow';
|
|
||||||
import { RolePermissionsObjectPermission } from '@/settings/roles/types/RolePermissionsObjectPermission';
|
|
||||||
import { RolePermissionsSettingPermission } from '@/settings/roles/types/RolePermissionsSettingPermission';
|
|
||||||
import styled from '@emotion/styled';
|
|
||||||
import { t } from '@lingui/core/macro';
|
|
||||||
import {
|
|
||||||
H2Title,
|
|
||||||
IconCode,
|
|
||||||
IconEye,
|
|
||||||
IconHierarchy,
|
|
||||||
IconKey,
|
|
||||||
IconLockOpen,
|
|
||||||
IconPencil,
|
|
||||||
IconServer,
|
|
||||||
IconSettings,
|
|
||||||
IconTrash,
|
|
||||||
IconTrashX,
|
|
||||||
IconUsers,
|
|
||||||
Section,
|
|
||||||
} from 'twenty-ui';
|
|
||||||
import { Role } from '~/generated-metadata/graphql';
|
|
||||||
import { SettingPermissionType } from '~/generated/graphql';
|
|
||||||
import { RolePermissionsObjectsTableRow } from './RolePermissionsObjectsTableRow';
|
|
||||||
|
|
||||||
const StyledRolePermissionsContainer = styled.div`
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
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)};
|
|
||||||
`;
|
|
||||||
|
|
||||||
type RolePermissionsProps = {
|
|
||||||
role: Pick<
|
|
||||||
Role,
|
|
||||||
| 'id'
|
|
||||||
| 'canUpdateAllSettings'
|
|
||||||
| 'canReadAllObjectRecords'
|
|
||||||
| 'canUpdateAllObjectRecords'
|
|
||||||
| 'canSoftDeleteAllObjectRecords'
|
|
||||||
| 'canDestroyAllObjectRecords'
|
|
||||||
>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const RolePermissions = ({ role }: RolePermissionsProps) => {
|
|
||||||
const objectPermissionsConfig: RolePermissionsObjectPermission[] = [
|
|
||||||
{
|
|
||||||
key: 'seeRecords',
|
|
||||||
label: 'See Records on All Objects',
|
|
||||||
Icon: IconEye,
|
|
||||||
value: role.canReadAllObjectRecords,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'editRecords',
|
|
||||||
label: 'Edit Records on All Objects',
|
|
||||||
Icon: IconPencil,
|
|
||||||
value: role.canUpdateAllObjectRecords,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'deleteRecords',
|
|
||||||
label: 'Delete Records on All Objects',
|
|
||||||
Icon: IconTrash,
|
|
||||||
value: role.canSoftDeleteAllObjectRecords,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'destroyRecords',
|
|
||||||
label: 'Destroy Records on All Objects',
|
|
||||||
Icon: IconTrashX,
|
|
||||||
value: role.canDestroyAllObjectRecords,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const settingsPermissionsConfig: RolePermissionsSettingPermission[] = [
|
|
||||||
{
|
|
||||||
key: SettingPermissionType.API_KEYS_AND_WEBHOOKS,
|
|
||||||
name: 'API Keys & Webhooks',
|
|
||||||
description: 'Manage API keys and webhooks',
|
|
||||||
value: role.canUpdateAllSettings,
|
|
||||||
Icon: IconCode,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: SettingPermissionType.WORKSPACE,
|
|
||||||
name: 'Workspace',
|
|
||||||
description: 'Set global workspace preferences',
|
|
||||||
value: role.canUpdateAllSettings,
|
|
||||||
Icon: IconSettings,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: SettingPermissionType.WORKSPACE_MEMBERS,
|
|
||||||
name: 'Users',
|
|
||||||
description: 'Add or remove users',
|
|
||||||
value: role.canUpdateAllSettings,
|
|
||||||
Icon: IconUsers,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: SettingPermissionType.ROLES,
|
|
||||||
name: 'Roles',
|
|
||||||
description: 'Define user roles and access levels',
|
|
||||||
value: role.canUpdateAllSettings,
|
|
||||||
Icon: IconLockOpen,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: SettingPermissionType.DATA_MODEL,
|
|
||||||
name: 'Data Model',
|
|
||||||
description: 'Edit CRM data structure and fields',
|
|
||||||
value: role.canUpdateAllSettings,
|
|
||||||
Icon: IconHierarchy,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: SettingPermissionType.ADMIN_PANEL,
|
|
||||||
name: 'Admin Panel',
|
|
||||||
description: 'Admin settings and system tools',
|
|
||||||
value: role.canUpdateAllSettings,
|
|
||||||
Icon: IconServer,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: SettingPermissionType.SECURITY,
|
|
||||||
name: 'Security',
|
|
||||||
description: 'Manage security policies',
|
|
||||||
value: role.canUpdateAllSettings,
|
|
||||||
Icon: IconKey,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<StyledRolePermissionsContainer>
|
|
||||||
<Section>
|
|
||||||
<H2Title
|
|
||||||
title={t`Objects`}
|
|
||||||
description={t`Ability to interact with each object`}
|
|
||||||
/>
|
|
||||||
<StyledTable>
|
|
||||||
<RolePermissionsObjectsTableHeader
|
|
||||||
allPermissions={objectPermissionsConfig.every(
|
|
||||||
(permission) => permission.value,
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<StyledTableRows>
|
|
||||||
{objectPermissionsConfig.map((permission) => (
|
|
||||||
<RolePermissionsObjectsTableRow
|
|
||||||
key={permission.key}
|
|
||||||
permission={permission}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</StyledTableRows>
|
|
||||||
</StyledTable>
|
|
||||||
</Section>
|
|
||||||
<Section>
|
|
||||||
<H2Title title={t`Settings`} description={t`Settings permissions`} />
|
|
||||||
<StyledTable>
|
|
||||||
<RolePermissionsSettingsTableHeader
|
|
||||||
allPermissions={role.canUpdateAllSettings}
|
|
||||||
/>
|
|
||||||
<StyledTableRows>
|
|
||||||
{settingsPermissionsConfig.map((permission) => (
|
|
||||||
<RolePermissionsSettingsTableRow
|
|
||||||
key={permission.key}
|
|
||||||
permission={permission}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</StyledTableRows>
|
|
||||||
</StyledTable>
|
|
||||||
</Section>
|
|
||||||
</StyledRolePermissionsContainer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -1,35 +0,0 @@
|
|||||||
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
|
||||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
|
||||||
import styled from '@emotion/styled';
|
|
||||||
import { t } from '@lingui/core/macro';
|
|
||||||
import { Checkbox } from 'twenty-ui';
|
|
||||||
|
|
||||||
const StyledNameHeader = styled(TableHeader)`
|
|
||||||
flex: 1;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledActionsHeader = styled(TableHeader)`
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
padding-right: ${({ theme }) => theme.spacing(4)};
|
|
||||||
`;
|
|
||||||
|
|
||||||
type RolePermissionsObjectsTableHeaderProps = {
|
|
||||||
allPermissions: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const RolePermissionsObjectsTableHeader = ({
|
|
||||||
allPermissions,
|
|
||||||
}: RolePermissionsObjectsTableHeaderProps) => (
|
|
||||||
<TableRow>
|
|
||||||
<StyledNameHeader>{t`Name`}</StyledNameHeader>
|
|
||||||
<StyledActionsHeader aria-label={t`Actions`}>
|
|
||||||
<Checkbox
|
|
||||||
checked={allPermissions}
|
|
||||||
indeterminate={!allPermissions}
|
|
||||||
disabled
|
|
||||||
/>
|
|
||||||
</StyledActionsHeader>
|
|
||||||
</TableRow>
|
|
||||||
);
|
|
||||||
@ -0,0 +1,202 @@
|
|||||||
|
import { SettingsRolePermissionsObjectsTableHeader } from '@/settings/roles/role-permissions/components/SettingsRolePermissionsObjectsTableHeader';
|
||||||
|
import { SettingsRolePermissionsObjectsTableRow } from '@/settings/roles/role-permissions/components/SettingsRolePermissionsObjectsTableRow';
|
||||||
|
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 styled from '@emotion/styled';
|
||||||
|
import { t } from '@lingui/core/macro';
|
||||||
|
import { useRecoilState } from 'recoil';
|
||||||
|
import {
|
||||||
|
H2Title,
|
||||||
|
IconCode,
|
||||||
|
IconEye,
|
||||||
|
IconHierarchy,
|
||||||
|
IconKey,
|
||||||
|
IconLockOpen,
|
||||||
|
IconPencil,
|
||||||
|
IconServer,
|
||||||
|
IconSettings,
|
||||||
|
IconTrash,
|
||||||
|
IconTrashX,
|
||||||
|
IconUsers,
|
||||||
|
Section,
|
||||||
|
} from 'twenty-ui';
|
||||||
|
import { SettingPermissionType } from '~/generated-metadata/graphql';
|
||||||
|
|
||||||
|
const StyledRolePermissionsContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
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)};
|
||||||
|
`;
|
||||||
|
|
||||||
|
type SettingsRolePermissionsProps = {
|
||||||
|
roleId: string;
|
||||||
|
isEditable: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SettingsRolePermissions = ({
|
||||||
|
roleId,
|
||||||
|
isEditable,
|
||||||
|
}: 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`,
|
||||||
|
value: settingsDraftRole.canUpdateAllSettings,
|
||||||
|
Icon: IconCode,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: SettingPermissionType.WORKSPACE,
|
||||||
|
name: t`Workspace`,
|
||||||
|
description: t`Set global workspace preferences`,
|
||||||
|
value: settingsDraftRole.canUpdateAllSettings,
|
||||||
|
Icon: IconSettings,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: SettingPermissionType.WORKSPACE_MEMBERS,
|
||||||
|
name: t`Users`,
|
||||||
|
description: t`Add or remove users`,
|
||||||
|
value: settingsDraftRole.canUpdateAllSettings,
|
||||||
|
Icon: IconUsers,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: SettingPermissionType.ROLES,
|
||||||
|
name: t`Roles`,
|
||||||
|
description: t`Define user roles and access levels`,
|
||||||
|
value: settingsDraftRole.canUpdateAllSettings,
|
||||||
|
Icon: IconLockOpen,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: SettingPermissionType.DATA_MODEL,
|
||||||
|
name: t`Data Model`,
|
||||||
|
description: t`Edit CRM data structure and fields`,
|
||||||
|
value: settingsDraftRole.canUpdateAllSettings,
|
||||||
|
Icon: IconHierarchy,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: SettingPermissionType.ADMIN_PANEL,
|
||||||
|
name: t`Admin Panel`,
|
||||||
|
description: t`Admin settings and system tools`,
|
||||||
|
value: settingsDraftRole.canUpdateAllSettings,
|
||||||
|
Icon: IconServer,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: SettingPermissionType.SECURITY,
|
||||||
|
name: t`Security`,
|
||||||
|
description: t`Manage security policies`,
|
||||||
|
value: settingsDraftRole.canUpdateAllSettings,
|
||||||
|
Icon: IconKey,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledRolePermissionsContainer>
|
||||||
|
<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>
|
||||||
|
<Section>
|
||||||
|
<H2Title title={t`Settings`} description={t`Settings permissions`} />
|
||||||
|
<StyledTable>
|
||||||
|
<SettingsRolePermissionsSettingsTableHeader
|
||||||
|
allPermissions={settingsDraftRole.canUpdateAllSettings}
|
||||||
|
/>
|
||||||
|
<StyledTableRows>
|
||||||
|
{settingsPermissionsConfig.map((permission) => (
|
||||||
|
<SettingsRolePermissionsSettingsTableRow
|
||||||
|
key={permission.key}
|
||||||
|
permission={permission}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</StyledTableRows>
|
||||||
|
</StyledTable>
|
||||||
|
</Section>
|
||||||
|
</StyledRolePermissionsContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,68 @@
|
|||||||
|
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
|
||||||
|
import { SettingsRolePermissionsObjectPermission } from '@/settings/roles/types/SettingsRolePermissionsObjectPermission';
|
||||||
|
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
||||||
|
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
import { t } from '@lingui/core/macro';
|
||||||
|
import { useRecoilState } from 'recoil';
|
||||||
|
import { Checkbox } from 'twenty-ui';
|
||||||
|
|
||||||
|
const StyledNameHeader = styled(TableHeader)`
|
||||||
|
flex: 1;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledActionsHeader = styled(TableHeader)`
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding-right: ${({ theme }) => theme.spacing(4)};
|
||||||
|
`;
|
||||||
|
|
||||||
|
type SettingsRolePermissionsObjectsTableHeaderProps = {
|
||||||
|
roleId: string;
|
||||||
|
objectPermissionsConfig: SettingsRolePermissionsObjectPermission[];
|
||||||
|
isEditable: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SettingsRolePermissionsObjectsTableHeader = ({
|
||||||
|
roleId,
|
||||||
|
objectPermissionsConfig,
|
||||||
|
isEditable,
|
||||||
|
}: SettingsRolePermissionsObjectsTableHeaderProps) => {
|
||||||
|
const [settingsDraftRole, setSettingsDraftRole] = useRecoilState(
|
||||||
|
settingsDraftRoleFamilyState(roleId),
|
||||||
|
);
|
||||||
|
|
||||||
|
const allPermissionsEnabled = objectPermissionsConfig.every(
|
||||||
|
(permission) => permission.value,
|
||||||
|
);
|
||||||
|
|
||||||
|
const somePermissionsEnabled = objectPermissionsConfig.some(
|
||||||
|
(permission) => permission.value,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableRow>
|
||||||
|
<StyledNameHeader>{t`Name`}</StyledNameHeader>
|
||||||
|
<StyledActionsHeader aria-label={t`Actions`}>
|
||||||
|
<Checkbox
|
||||||
|
checked={allPermissionsEnabled}
|
||||||
|
indeterminate={somePermissionsEnabled && !allPermissionsEnabled}
|
||||||
|
disabled={!isEditable}
|
||||||
|
aria-label={t`Toggle all object permissions`}
|
||||||
|
onChange={() => {
|
||||||
|
const newValue = !allPermissionsEnabled;
|
||||||
|
|
||||||
|
setSettingsDraftRole({
|
||||||
|
...settingsDraftRole,
|
||||||
|
canReadAllObjectRecords: newValue,
|
||||||
|
canUpdateAllObjectRecords: newValue,
|
||||||
|
canSoftDeleteAllObjectRecords: newValue,
|
||||||
|
canDestroyAllObjectRecords: newValue,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</StyledActionsHeader>
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -1,6 +1,7 @@
|
|||||||
import { RolePermissionsObjectPermission } from '@/settings/roles/types/RolePermissionsObjectPermission';
|
import { SettingsRolePermissionsObjectPermission } from '@/settings/roles/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 styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { Checkbox } from 'twenty-ui';
|
import { Checkbox } from 'twenty-ui';
|
||||||
|
|
||||||
@ -46,25 +47,33 @@ const StyledTableRow = styled(TableRow)`
|
|||||||
display: flex;
|
display: flex;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
type RolePermissionsObjectsTableRowProps = {
|
type SettingsRolePermissionsObjectsTableRowProps = {
|
||||||
permission: RolePermissionsObjectPermission;
|
permission: SettingsRolePermissionsObjectPermission;
|
||||||
|
isEditable: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RolePermissionsObjectsTableRow = ({
|
export const SettingsRolePermissionsObjectsTableRow = ({
|
||||||
permission,
|
permission,
|
||||||
}: RolePermissionsObjectsTableRowProps) => {
|
isEditable,
|
||||||
|
}: SettingsRolePermissionsObjectsTableRowProps) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledTableRow key={permission.key}>
|
<StyledTableRow>
|
||||||
<StyledPermissionCell>
|
<StyledPermissionCell>
|
||||||
<StyledIconWrapper>
|
<StyledIconWrapper>
|
||||||
<StyledIcon>
|
<StyledIcon>
|
||||||
<permission.Icon size={14} />
|
<permission.Icon size={theme.icon.size.sm} />
|
||||||
</StyledIcon>
|
</StyledIcon>
|
||||||
</StyledIconWrapper>
|
</StyledIconWrapper>
|
||||||
<StyledLabel>{permission.label}</StyledLabel>
|
<StyledLabel>{permission.label}</StyledLabel>
|
||||||
</StyledPermissionCell>
|
</StyledPermissionCell>
|
||||||
<StyledCheckboxCell>
|
<StyledCheckboxCell>
|
||||||
<Checkbox checked={permission.value} disabled />
|
<Checkbox
|
||||||
|
checked={permission.value}
|
||||||
|
onChange={() => permission.setValue(!permission.value)}
|
||||||
|
disabled={!isEditable}
|
||||||
|
/>
|
||||||
</StyledCheckboxCell>
|
</StyledCheckboxCell>
|
||||||
</StyledTableRow>
|
</StyledTableRow>
|
||||||
);
|
);
|
||||||
@ -6,7 +6,10 @@ import { Checkbox } from 'twenty-ui';
|
|||||||
|
|
||||||
const StyledNameHeader = styled(TableHeader)`
|
const StyledNameHeader = styled(TableHeader)`
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding-left: ${({ theme }) => theme.spacing(2)};
|
`;
|
||||||
|
|
||||||
|
const StyledTypeHeader = styled(TableHeader)`
|
||||||
|
flex: 1;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledActionsHeader = styled(TableHeader)`
|
const StyledActionsHeader = styled(TableHeader)`
|
||||||
@ -16,22 +19,24 @@ const StyledActionsHeader = styled(TableHeader)`
|
|||||||
padding-right: ${({ theme }) => theme.spacing(4)};
|
padding-right: ${({ theme }) => theme.spacing(4)};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledTypeHeader = styled(TableHeader)`
|
type SettingsRolePermissionsSettingsTableHeaderProps = {
|
||||||
flex: 1;
|
|
||||||
`;
|
|
||||||
|
|
||||||
type RolePermissionsSettingsTableHeaderProps = {
|
|
||||||
allPermissions: boolean;
|
allPermissions: boolean;
|
||||||
|
onToggleAll?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RolePermissionsSettingsTableHeader = ({
|
export const SettingsRolePermissionsSettingsTableHeader = ({
|
||||||
allPermissions,
|
allPermissions,
|
||||||
}: RolePermissionsSettingsTableHeaderProps) => (
|
onToggleAll,
|
||||||
|
}: SettingsRolePermissionsSettingsTableHeaderProps) => (
|
||||||
<TableRow gridAutoColumns="3fr 4fr 24px">
|
<TableRow gridAutoColumns="3fr 4fr 24px">
|
||||||
<StyledNameHeader>{t`Name`}</StyledNameHeader>
|
<StyledNameHeader>{t`Name`}</StyledNameHeader>
|
||||||
<StyledTypeHeader>{t`Description`}</StyledTypeHeader>
|
<StyledTypeHeader>{t`Description`}</StyledTypeHeader>
|
||||||
<StyledActionsHeader aria-label={t`Actions`}>
|
<StyledActionsHeader aria-label={t`Actions`}>
|
||||||
<Checkbox checked={allPermissions} disabled />
|
<Checkbox
|
||||||
|
checked={allPermissions}
|
||||||
|
disabled={!onToggleAll}
|
||||||
|
onChange={onToggleAll}
|
||||||
|
/>
|
||||||
</StyledActionsHeader>
|
</StyledActionsHeader>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
);
|
);
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { RolePermissionsSettingPermission } from '@/settings/roles/types/RolePermissionsSettingPermission';
|
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 { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
@ -33,13 +33,13 @@ const StyledIconContainer = styled.div`
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
type RolePermissionsSettingsTableRowProps = {
|
type SettingsRolePermissionsSettingsTableRowProps = {
|
||||||
permission: RolePermissionsSettingPermission;
|
permission: SettingsRolePermissionsSettingPermission;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RolePermissionsSettingsTableRow = ({
|
export const SettingsRolePermissionsSettingsTableRow = ({
|
||||||
permission,
|
permission,
|
||||||
}: RolePermissionsSettingsTableRowProps) => {
|
}: SettingsRolePermissionsSettingsTableRowProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -47,7 +47,7 @@ export const RolePermissionsSettingsTableRow = ({
|
|||||||
<StyledPermissionCell>
|
<StyledPermissionCell>
|
||||||
<StyledIconContainer>
|
<StyledIconContainer>
|
||||||
<permission.Icon
|
<permission.Icon
|
||||||
size={16}
|
size={theme.icon.size.md}
|
||||||
color={theme.font.color.primary}
|
color={theme.font.color.primary}
|
||||||
stroke={theme.icon.stroke.sm}
|
stroke={theme.icon.stroke.sm}
|
||||||
/>
|
/>
|
||||||
@ -0,0 +1,59 @@
|
|||||||
|
import { SettingsRolePermissions } from '@/settings/roles/role-permissions/components/SettingsRolePermissions';
|
||||||
|
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
|
||||||
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
|
import { useSetRecoilState } from 'recoil';
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
import { ComponentDecorator, RouterDecorator } from 'twenty-ui';
|
||||||
|
import { PENDING_ROLE_ID } from '~/pages/settings/roles/SettingsRoleCreate';
|
||||||
|
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
||||||
|
import { getRolesMock } from '~/testing/mock-data/roles';
|
||||||
|
|
||||||
|
const SettingsRolePermissionsWrapper = (
|
||||||
|
args: React.ComponentProps<typeof SettingsRolePermissions>,
|
||||||
|
) => {
|
||||||
|
const setDraftRole = useSetRecoilState(
|
||||||
|
settingsDraftRoleFamilyState(args.roleId),
|
||||||
|
);
|
||||||
|
|
||||||
|
const role = getRolesMock().find((role) => role.id === args.roleId);
|
||||||
|
|
||||||
|
if (isDefined(role)) {
|
||||||
|
setDraftRole(role);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SettingsRolePermissions
|
||||||
|
roleId={args.roleId}
|
||||||
|
isEditable={args.isEditable}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const meta: Meta<typeof SettingsRolePermissionsWrapper> = {
|
||||||
|
title: 'Modules/Settings/Roles/RolePermissions/SettingsRolePermissions',
|
||||||
|
component: SettingsRolePermissionsWrapper,
|
||||||
|
decorators: [RouterDecorator, ComponentDecorator, I18nFrontDecorator],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof SettingsRolePermissionsWrapper>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
args: {
|
||||||
|
roleId: '1',
|
||||||
|
isEditable: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ReadOnly: Story = {
|
||||||
|
args: {
|
||||||
|
roleId: '1',
|
||||||
|
isEditable: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PendingRole: Story = {
|
||||||
|
args: {
|
||||||
|
roleId: PENDING_ROLE_ID,
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -1,47 +0,0 @@
|
|||||||
import styled from '@emotion/styled';
|
|
||||||
import { t } from '@lingui/core/macro';
|
|
||||||
|
|
||||||
import { IconPicker } from '@/ui/input/components/IconPicker';
|
|
||||||
import { TextArea } from '@/ui/input/components/TextArea';
|
|
||||||
import { TextInput } from '@/ui/input/components/TextInput';
|
|
||||||
import { Section } from 'twenty-ui';
|
|
||||||
import { Role } from '~/generated-metadata/graphql';
|
|
||||||
|
|
||||||
const StyledInputsContainer = styled.div`
|
|
||||||
display: flex;
|
|
||||||
gap: ${({ theme }) => theme.spacing(2)};
|
|
||||||
width: 100%;
|
|
||||||
margin-bottom: ${({ theme }) => theme.spacing(2)};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledInputContainer = styled.div`
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
`;
|
|
||||||
|
|
||||||
type RoleSettingsProps = {
|
|
||||||
role: Pick<Role, 'id' | 'label' | 'description' | 'icon'>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const RoleSettings = ({ role }: RoleSettingsProps) => {
|
|
||||||
return (
|
|
||||||
<Section>
|
|
||||||
<StyledInputsContainer>
|
|
||||||
<StyledInputContainer>
|
|
||||||
<IconPicker
|
|
||||||
disabled={true}
|
|
||||||
selectedIconKey={role.icon ?? 'IconUser'}
|
|
||||||
onChange={() => {}}
|
|
||||||
/>
|
|
||||||
</StyledInputContainer>
|
|
||||||
<TextInput value={role.label} disabled fullWidth />
|
|
||||||
</StyledInputsContainer>
|
|
||||||
<TextArea
|
|
||||||
minRows={4}
|
|
||||||
placeholder={t`Write a description`}
|
|
||||||
value={role.description || ''}
|
|
||||||
disabled
|
|
||||||
/>
|
|
||||||
</Section>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -0,0 +1,79 @@
|
|||||||
|
import styled from '@emotion/styled';
|
||||||
|
import { t } from '@lingui/core/macro';
|
||||||
|
|
||||||
|
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
|
||||||
|
import { IconPicker } from '@/ui/input/components/IconPicker';
|
||||||
|
import { TextArea } from '@/ui/input/components/TextArea';
|
||||||
|
import { TextInput } from '@/ui/input/components/TextInput';
|
||||||
|
import { useRecoilState } from 'recoil';
|
||||||
|
import { Section } from 'twenty-ui';
|
||||||
|
|
||||||
|
const StyledInputsContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
gap: ${({ theme }) => theme.spacing(2)};
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: ${({ theme }) => theme.spacing(2)};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledInputContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
`;
|
||||||
|
|
||||||
|
type SettingsRoleSettingsProps = {
|
||||||
|
roleId: string;
|
||||||
|
isEditable: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SettingsRoleSettings = ({
|
||||||
|
roleId,
|
||||||
|
isEditable,
|
||||||
|
}: SettingsRoleSettingsProps) => {
|
||||||
|
const [settingsDraftRole, setSettingsDraftRole] = useRecoilState(
|
||||||
|
settingsDraftRoleFamilyState(roleId),
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Section>
|
||||||
|
<StyledInputsContainer>
|
||||||
|
<StyledInputContainer>
|
||||||
|
<IconPicker
|
||||||
|
selectedIconKey={settingsDraftRole.icon ?? 'IconUser'}
|
||||||
|
dropdownId="role-settings-icon-picker"
|
||||||
|
onChange={({ iconKey }: { iconKey: string }) => {
|
||||||
|
setSettingsDraftRole({
|
||||||
|
...settingsDraftRole,
|
||||||
|
icon: iconKey,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
disabled={!isEditable}
|
||||||
|
/>
|
||||||
|
</StyledInputContainer>
|
||||||
|
<TextInput
|
||||||
|
value={settingsDraftRole.label}
|
||||||
|
fullWidth
|
||||||
|
onChange={(value: string) => {
|
||||||
|
setSettingsDraftRole({
|
||||||
|
...settingsDraftRole,
|
||||||
|
label: value,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
placeholder={t`Role name`}
|
||||||
|
disabled={!isEditable}
|
||||||
|
/>
|
||||||
|
</StyledInputsContainer>
|
||||||
|
<TextArea
|
||||||
|
minRows={4}
|
||||||
|
placeholder={t`Write a description`}
|
||||||
|
value={settingsDraftRole.description || ''}
|
||||||
|
onChange={(value: string) => {
|
||||||
|
setSettingsDraftRole({
|
||||||
|
...settingsDraftRole,
|
||||||
|
description: value,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
disabled={!isEditable}
|
||||||
|
/>
|
||||||
|
</Section>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,56 @@
|
|||||||
|
import { SettingsRoleSettings } from '@/settings/roles/role-settings/components/SettingsRoleSettings';
|
||||||
|
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
|
||||||
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
|
import { useSetRecoilState } from 'recoil';
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
import { ComponentDecorator, RouterDecorator } from 'twenty-ui';
|
||||||
|
import { PENDING_ROLE_ID } from '~/pages/settings/roles/SettingsRoleCreate';
|
||||||
|
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
||||||
|
import { getRolesMock } from '~/testing/mock-data/roles';
|
||||||
|
|
||||||
|
const SettingsRoleSettingsWrapper = (
|
||||||
|
args: React.ComponentProps<typeof SettingsRoleSettings>,
|
||||||
|
) => {
|
||||||
|
const setDraftRole = useSetRecoilState(
|
||||||
|
settingsDraftRoleFamilyState(args.roleId),
|
||||||
|
);
|
||||||
|
|
||||||
|
const role = getRolesMock().find((role) => role.id === args.roleId);
|
||||||
|
|
||||||
|
if (isDefined(role)) {
|
||||||
|
setDraftRole(role);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SettingsRoleSettings roleId={args.roleId} isEditable={args.isEditable} />
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const meta: Meta<typeof SettingsRoleSettingsWrapper> = {
|
||||||
|
title: 'Modules/Settings/Roles/RoleSettings/SettingsRoleSettings',
|
||||||
|
component: SettingsRoleSettingsWrapper,
|
||||||
|
decorators: [RouterDecorator, ComponentDecorator, I18nFrontDecorator],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof SettingsRoleSettingsWrapper>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
args: {
|
||||||
|
roleId: '1',
|
||||||
|
isEditable: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ReadOnly: Story = {
|
||||||
|
args: {
|
||||||
|
roleId: '1',
|
||||||
|
isEditable: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PendingRole: Story = {
|
||||||
|
args: {
|
||||||
|
roleId: PENDING_ROLE_ID,
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -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;
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
import { selector } from 'recoil';
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
import { settingsPersistedRoleFamilyState } from './settingsPersistedRoleFamilyState';
|
||||||
|
import { settingsRoleIdsState } from './settingsRoleIdsState';
|
||||||
|
|
||||||
|
export const settingsAllRolesSelector = selector({
|
||||||
|
key: 'settingsAllRolesSelector',
|
||||||
|
get: ({ get }) => {
|
||||||
|
const roleIds = get(settingsRoleIdsState);
|
||||||
|
return roleIds
|
||||||
|
.map((roleId) => get(settingsPersistedRoleFamilyState(roleId)))
|
||||||
|
.filter(isDefined);
|
||||||
|
},
|
||||||
|
});
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
import { createFamilyState } from '@/ui/utilities/state/utils/createFamilyState';
|
||||||
|
import { Role } from '~/generated/graphql';
|
||||||
|
|
||||||
|
export const settingsDraftRoleFamilyState = createFamilyState<Role, string>({
|
||||||
|
key: 'settingsDraftRoleFamilyState',
|
||||||
|
defaultValue: {
|
||||||
|
id: '',
|
||||||
|
label: '',
|
||||||
|
description: '',
|
||||||
|
icon: '',
|
||||||
|
canDestroyAllObjectRecords: false,
|
||||||
|
canReadAllObjectRecords: false,
|
||||||
|
canSoftDeleteAllObjectRecords: false,
|
||||||
|
canUpdateAllObjectRecords: false,
|
||||||
|
canUpdateAllSettings: false,
|
||||||
|
isEditable: false,
|
||||||
|
workspaceMembers: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
import { createFamilyState } from '@/ui/utilities/state/utils/createFamilyState';
|
||||||
|
import { Role } from '~/generated/graphql';
|
||||||
|
|
||||||
|
export const settingsPersistedRoleFamilyState = createFamilyState<
|
||||||
|
Role | undefined,
|
||||||
|
string
|
||||||
|
>({
|
||||||
|
key: 'settingsPersistedRoleFamilyState',
|
||||||
|
defaultValue: undefined,
|
||||||
|
});
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
import { atom } from 'recoil';
|
||||||
|
|
||||||
|
export const settingsRoleIdsState = atom<string[]>({
|
||||||
|
key: 'settingsRoleIdsState',
|
||||||
|
default: [],
|
||||||
|
});
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
import { atom } from 'recoil';
|
||||||
|
|
||||||
|
export const settingsRolesIsLoadingState = atom<boolean>({
|
||||||
|
key: 'settingsRolesIsLoadingState',
|
||||||
|
default: true,
|
||||||
|
});
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
import { createFamilyState } from '@/ui/utilities/state/utils/createFamilyState';
|
||||||
|
import { Role } from '~/generated-metadata/graphql';
|
||||||
|
|
||||||
|
export const settingsValidateRoleFamilyState = createFamilyState<
|
||||||
|
Record<keyof Pick<Role, 'label'>, boolean>,
|
||||||
|
string
|
||||||
|
>({
|
||||||
|
key: 'settingsValidateRoleFamilyState',
|
||||||
|
defaultValue: {
|
||||||
|
label: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
@ -1,8 +1,9 @@
|
|||||||
import { IconComponent } from 'twenty-ui';
|
import { IconComponent } from 'twenty-ui';
|
||||||
|
|
||||||
export type RolePermissionsObjectPermission = {
|
export type SettingsRolePermissionsObjectPermission = {
|
||||||
key: string;
|
key: string;
|
||||||
label: string;
|
label: string;
|
||||||
value: boolean;
|
value: boolean;
|
||||||
Icon: IconComponent;
|
Icon: IconComponent;
|
||||||
|
setValue: (value: boolean) => void;
|
||||||
};
|
};
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import { IconComponent } from 'twenty-ui';
|
import { IconComponent } from 'twenty-ui';
|
||||||
|
|
||||||
export type RolePermissionsSettingPermission = {
|
export type SettingsRolePermissionsSettingPermission = {
|
||||||
key: string;
|
key: string;
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
@ -42,5 +42,6 @@ export enum SettingsPath {
|
|||||||
AdminPanelOtherEnvVariables = 'admin-panel/other-env-variables',
|
AdminPanelOtherEnvVariables = 'admin-panel/other-env-variables',
|
||||||
Lab = 'lab',
|
Lab = 'lab',
|
||||||
Roles = 'roles',
|
Roles = 'roles',
|
||||||
|
RoleCreate = 'roles/create',
|
||||||
RoleDetail = 'roles/:roleId',
|
RoleDetail = 'roles/:roleId',
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,15 @@
|
|||||||
|
import { SettingsRolesQueryEffect } from '@/settings/roles/components/SettingsRolesQueryEffect';
|
||||||
|
import { SettingsRole } from '@/settings/roles/role/components/SettingsRole';
|
||||||
|
import { SettingsRoleCreateEffect } from '@/settings/roles/role/components/SettingsRoleCreateEffect';
|
||||||
|
|
||||||
|
export const PENDING_ROLE_ID = 'pending-role-id';
|
||||||
|
|
||||||
|
export const SettingsRoleCreate = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SettingsRolesQueryEffect />
|
||||||
|
<SettingsRoleCreateEffect roleId={PENDING_ROLE_ID} />
|
||||||
|
<SettingsRole roleId={PENDING_ROLE_ID} isCreateMode={true} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -1,119 +1,21 @@
|
|||||||
import { t } from '@lingui/core/macro';
|
import { SettingsRolesQueryEffect } from '@/settings/roles/components/SettingsRolesQueryEffect';
|
||||||
import { useEffect } from 'react';
|
import { SettingsRole } from '@/settings/roles/role/components/SettingsRole';
|
||||||
import { useParams } from 'react-router-dom';
|
import { SettingsRoleEditEffect } from '@/settings/roles/role/components/SettingsRoleEditEffect';
|
||||||
import { H3Title, IconLockOpen, IconSettings, IconUserPlus } from 'twenty-ui';
|
import { Navigate, useParams } from 'react-router-dom';
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
|
||||||
import { RoleAssignment } from '@/settings/roles/role-assignment/components/RoleAssignment';
|
|
||||||
import { RolePermissions } from '@/settings/roles/role-permissions/components/RolePermissions';
|
|
||||||
import { RoleSettings } from '@/settings/roles/role-settings/components/RoleSettings';
|
|
||||||
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 { useGetRolesQuery } from '~/generated/graphql';
|
|
||||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
|
||||||
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
|
||||||
|
|
||||||
export const SETTINGS_ROLE_DETAIL_TABS = {
|
|
||||||
COMPONENT_INSTANCE_ID: 'settings-role-detail-tabs',
|
|
||||||
TABS_IDS: {
|
|
||||||
ASSIGNMENT: 'assignment',
|
|
||||||
PERMISSIONS: 'permissions',
|
|
||||||
SETTINGS: 'settings',
|
|
||||||
},
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export const SettingsRoleEdit = () => {
|
export const SettingsRoleEdit = () => {
|
||||||
const { roleId = '' } = useParams();
|
const { roleId } = useParams();
|
||||||
const navigateSettings = useNavigateSettings();
|
|
||||||
const { data: rolesData, loading: rolesLoading } = useGetRolesQuery({
|
|
||||||
fetchPolicy: 'network-only',
|
|
||||||
});
|
|
||||||
|
|
||||||
const role = rolesData?.getRoles.find((r) => r.id === roleId);
|
if (!isDefined(roleId)) {
|
||||||
|
return <Navigate to="/settings/roles" />;
|
||||||
const activeTabId = useRecoilComponentValueV2(
|
}
|
||||||
activeTabIdComponentState,
|
|
||||||
SETTINGS_ROLE_DETAIL_TABS.COMPONENT_INSTANCE_ID,
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!rolesLoading && !role) {
|
|
||||||
navigateSettings(SettingsPath.Roles);
|
|
||||||
}
|
|
||||||
}, [role, navigateSettings, rolesLoading]);
|
|
||||||
|
|
||||||
const tabs = [
|
|
||||||
{
|
|
||||||
id: SETTINGS_ROLE_DETAIL_TABS.TABS_IDS.ASSIGNMENT,
|
|
||||||
title: t`Assignment`,
|
|
||||||
Icon: IconUserPlus,
|
|
||||||
hide: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: SETTINGS_ROLE_DETAIL_TABS.TABS_IDS.PERMISSIONS,
|
|
||||||
title: t`Permissions`,
|
|
||||||
Icon: IconLockOpen,
|
|
||||||
hide: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: SETTINGS_ROLE_DETAIL_TABS.TABS_IDS.SETTINGS,
|
|
||||||
title: t`Settings`,
|
|
||||||
Icon: IconSettings,
|
|
||||||
hide: false,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const renderActiveTabContent = () => {
|
|
||||||
if (!role) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (activeTabId) {
|
|
||||||
case SETTINGS_ROLE_DETAIL_TABS.TABS_IDS.ASSIGNMENT:
|
|
||||||
return <RoleAssignment role={role} />;
|
|
||||||
case SETTINGS_ROLE_DETAIL_TABS.TABS_IDS.PERMISSIONS:
|
|
||||||
return <RolePermissions role={role} />;
|
|
||||||
case SETTINGS_ROLE_DETAIL_TABS.TABS_IDS.SETTINGS:
|
|
||||||
return <RoleSettings role={role} />;
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SubMenuTopBarContainer
|
<>
|
||||||
title={role && <H3Title title={role.label} />}
|
<SettingsRolesQueryEffect />
|
||||||
links={[
|
<SettingsRoleEditEffect roleId={roleId} />
|
||||||
{
|
<SettingsRole roleId={roleId} isCreateMode={false} />
|
||||||
children: 'Workspace',
|
</>
|
||||||
href: getSettingsPath(SettingsPath.Workspace),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
children: 'Roles',
|
|
||||||
href: getSettingsPath(SettingsPath.Roles),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
children: role?.label,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
{!rolesLoading && role ? (
|
|
||||||
<SettingsPageContainer>
|
|
||||||
<TabList
|
|
||||||
tabs={tabs}
|
|
||||||
className="tab-list"
|
|
||||||
componentInstanceId={
|
|
||||||
SETTINGS_ROLE_DETAIL_TABS.COMPONENT_INSTANCE_ID
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
{renderActiveTabContent()}
|
|
||||||
</SettingsPageContainer>
|
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
)}
|
|
||||||
</SubMenuTopBarContainer>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,39 +1,11 @@
|
|||||||
import { Trans, useLingui } from '@lingui/react/macro';
|
import { SettingsRolesContainer } from '@/settings/roles/components/SettingsRolesContainer';
|
||||||
|
import { SettingsRolesQueryEffect } from '@/settings/roles/components/SettingsRolesQueryEffect';
|
||||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
|
||||||
import { Roles } from '@/settings/roles/components/Roles';
|
|
||||||
import { RolesDefaultRole } from '@/settings/roles/components/RolesDefaultRole';
|
|
||||||
import { SettingsPath } from '@/types/SettingsPath';
|
|
||||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
|
||||||
import { H3Title } from 'twenty-ui';
|
|
||||||
import { useGetRolesQuery } from '~/generated/graphql';
|
|
||||||
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
|
||||||
|
|
||||||
export const SettingsRoles = () => {
|
export const SettingsRoles = () => {
|
||||||
const { t } = useLingui();
|
|
||||||
const { data: rolesData, loading: rolesLoading } = useGetRolesQuery({
|
|
||||||
fetchPolicy: 'network-only',
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SubMenuTopBarContainer
|
<>
|
||||||
title={rolesData && <H3Title title={t`Roles`} />}
|
<SettingsRolesQueryEffect />
|
||||||
links={[
|
<SettingsRolesContainer />
|
||||||
{
|
</>
|
||||||
children: <Trans>Workspace</Trans>,
|
|
||||||
href: getSettingsPath(SettingsPath.Workspace),
|
|
||||||
},
|
|
||||||
{ children: <Trans>Roles</Trans> },
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<SettingsPageContainer>
|
|
||||||
{!rolesLoading && rolesData && (
|
|
||||||
<>
|
|
||||||
<Roles roles={rolesData.getRoles ?? []} />
|
|
||||||
<RolesDefaultRole roles={rolesData.getRoles ?? []} />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</SettingsPageContainer>
|
|
||||||
</SubMenuTopBarContainer>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,27 @@
|
|||||||
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
PageDecorator,
|
||||||
|
PageDecoratorArgs,
|
||||||
|
} from '~/testing/decorators/PageDecorator';
|
||||||
|
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||||
|
|
||||||
|
import { SettingsRoleCreate } from '../SettingsRoleCreate';
|
||||||
|
|
||||||
|
const meta: Meta<PageDecoratorArgs> = {
|
||||||
|
title: 'Pages/Settings/Roles/SettingsRoleCreate',
|
||||||
|
component: SettingsRoleCreate,
|
||||||
|
decorators: [PageDecorator],
|
||||||
|
args: {
|
||||||
|
routePath: '/settings/roles/create',
|
||||||
|
},
|
||||||
|
parameters: {
|
||||||
|
msw: graphqlMocks,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
|
||||||
|
export type Story = StoryObj<typeof SettingsRoleCreate>;
|
||||||
|
|
||||||
|
export const Default: Story = {};
|
||||||
@ -6,13 +6,18 @@ import {
|
|||||||
} from '~/testing/decorators/PageDecorator';
|
} from '~/testing/decorators/PageDecorator';
|
||||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||||
|
|
||||||
|
import { GET_ROLES } from '@/settings/roles/graphql/queries/getRolesQuery';
|
||||||
|
import { getOperationName } from '@apollo/client/utilities';
|
||||||
|
import { graphql, HttpResponse } from 'msw';
|
||||||
import { SettingsRoles } from '../SettingsRoles';
|
import { SettingsRoles } from '../SettingsRoles';
|
||||||
|
|
||||||
const meta: Meta<PageDecoratorArgs> = {
|
const meta: Meta<PageDecoratorArgs> = {
|
||||||
title: 'Pages/Settings/Roles/SettingsRoles',
|
title: 'Pages/Settings/Roles/SettingsRoles',
|
||||||
component: SettingsRoles,
|
component: SettingsRoles,
|
||||||
decorators: [PageDecorator],
|
decorators: [PageDecorator],
|
||||||
args: { routePath: '/settings/roles' },
|
args: {
|
||||||
|
routePath: '/settings/roles',
|
||||||
|
},
|
||||||
parameters: {
|
parameters: {
|
||||||
msw: graphqlMocks,
|
msw: graphqlMocks,
|
||||||
},
|
},
|
||||||
@ -23,3 +28,19 @@ export default meta;
|
|||||||
export type Story = StoryObj<typeof SettingsRoles>;
|
export type Story = StoryObj<typeof SettingsRoles>;
|
||||||
|
|
||||||
export const Default: Story = {};
|
export const Default: Story = {};
|
||||||
|
|
||||||
|
export const NoRoles: Story = {
|
||||||
|
parameters: {
|
||||||
|
msw: {
|
||||||
|
handlers: [
|
||||||
|
graphql.query(getOperationName(GET_ROLES) ?? '', () => {
|
||||||
|
return HttpResponse.json({
|
||||||
|
data: {
|
||||||
|
getRoles: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|||||||
@ -1,9 +1,14 @@
|
|||||||
import { Field, InputType } from '@nestjs/graphql';
|
import { Field, InputType } from '@nestjs/graphql';
|
||||||
|
|
||||||
import { IsBoolean, IsOptional, IsString } from 'class-validator';
|
import { IsBoolean, IsOptional, IsString, IsUUID } from 'class-validator';
|
||||||
|
|
||||||
@InputType()
|
@InputType()
|
||||||
export class CreateRoleInput {
|
export class CreateRoleInput {
|
||||||
|
@IsUUID()
|
||||||
|
@IsOptional()
|
||||||
|
@Field({ nullable: true })
|
||||||
|
id?: string;
|
||||||
|
|
||||||
@IsString()
|
@IsString()
|
||||||
@Field({ nullable: false })
|
@Field({ nullable: false })
|
||||||
label: string;
|
label: string;
|
||||||
|
|||||||
@ -100,12 +100,10 @@ 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 }) => {
|
||||||
disabled && isChecked
|
if (!(indeterminate || isChecked)) return 'transparent';
|
||||||
? theme.adaptiveColors.blue3
|
return disabled ? theme.adaptiveColors.blue3 : theme.color.blue;
|
||||||
: indeterminate || isChecked
|
}};
|
||||||
? theme.color.blue
|
|
||||||
: 'transparent'};
|
|
||||||
border-color: ${({
|
border-color: ${({
|
||||||
theme,
|
theme,
|
||||||
indeterminate,
|
indeterminate,
|
||||||
|
|||||||
Reference in New Issue
Block a user