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']>;
|
||||
description?: InputMaybe<Scalars['String']['input']>;
|
||||
icon?: InputMaybe<Scalars['String']['input']>;
|
||||
id?: InputMaybe<Scalars['String']['input']>;
|
||||
label: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
@ -515,6 +516,7 @@ export enum EnvironmentVariablesGroup {
|
||||
GoogleAuth = 'GoogleAuth',
|
||||
LLM = 'LLM',
|
||||
Logging = 'Logging',
|
||||
Metering = 'Metering',
|
||||
MicrosoftAuth = 'MicrosoftAuth',
|
||||
Other = 'Other',
|
||||
RateLimiting = 'RateLimiting',
|
||||
@ -557,7 +559,6 @@ export type FeatureFlag = {
|
||||
};
|
||||
|
||||
export enum FeatureFlagKey {
|
||||
IsAdvancedFiltersEnabled = 'IsAdvancedFiltersEnabled',
|
||||
IsAirtableIntegrationEnabled = 'IsAirtableIntegrationEnabled',
|
||||
IsAnalyticsV2Enabled = 'IsAnalyticsV2Enabled',
|
||||
IsApprovedAccessDomainsEnabled = 'IsApprovedAccessDomainsEnabled',
|
||||
|
||||
@ -316,6 +316,7 @@ export type CreateRoleInput = {
|
||||
canUpdateAllSettings?: InputMaybe<Scalars['Boolean']>;
|
||||
description?: InputMaybe<Scalars['String']>;
|
||||
icon?: InputMaybe<Scalars['String']>;
|
||||
id?: InputMaybe<Scalars['String']>;
|
||||
label: Scalars['String'];
|
||||
};
|
||||
|
||||
@ -446,6 +447,7 @@ export enum EnvironmentVariablesGroup {
|
||||
GoogleAuth = 'GoogleAuth',
|
||||
LLM = 'LLM',
|
||||
Logging = 'Logging',
|
||||
Metering = 'Metering',
|
||||
MicrosoftAuth = 'MicrosoftAuth',
|
||||
Other = 'Other',
|
||||
RateLimiting = 'RateLimiting',
|
||||
@ -488,7 +490,6 @@ export type FeatureFlag = {
|
||||
};
|
||||
|
||||
export enum FeatureFlagKey {
|
||||
IsAdvancedFiltersEnabled = 'IsAdvancedFiltersEnabled',
|
||||
IsAirtableIntegrationEnabled = 'IsAirtableIntegrationEnabled',
|
||||
IsAnalyticsV2Enabled = 'IsAnalyticsV2Enabled',
|
||||
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 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<{
|
||||
workspaceMemberId: Scalars['String'];
|
||||
roleId: Scalars['String'];
|
||||
@ -4605,6 +4620,72 @@ export function useUpdateLabPublicFeatureFlagMutation(baseOptions?: Apollo.Mutat
|
||||
export type UpdateLabPublicFeatureFlagMutationHookResult = ReturnType<typeof useUpdateLabPublicFeatureFlagMutation>;
|
||||
export type UpdateLabPublicFeatureFlagMutationResult = Apollo.MutationResult<UpdateLabPublicFeatureFlagMutation>;
|
||||
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`
|
||||
mutation UpdateWorkspaceMemberRole($workspaceMemberId: String!, $roleId: String!) {
|
||||
updateWorkspaceMemberRole(
|
||||
|
||||
@ -301,6 +301,12 @@ const SettingsRoles = lazy(() =>
|
||||
})),
|
||||
);
|
||||
|
||||
const SettingsRoleCreate = lazy(() =>
|
||||
import('~/pages/settings/roles/SettingsRoleCreate').then((module) => ({
|
||||
default: module.SettingsRoleCreate,
|
||||
})),
|
||||
);
|
||||
|
||||
const SettingsRoleEdit = lazy(() =>
|
||||
import('~/pages/settings/roles/SettingsRoleEdit').then((module) => ({
|
||||
default: module.SettingsRoleEdit,
|
||||
@ -392,6 +398,10 @@ export const SettingsRoutes = ({
|
||||
>
|
||||
<Route path={SettingsPath.Roles} element={<SettingsRoles />} />
|
||||
<Route path={SettingsPath.RoleDetail} element={<SettingsRoleEdit />} />
|
||||
<Route
|
||||
path={SettingsPath.RoleCreate}
|
||||
element={<SettingsRoleCreate />}
|
||||
/>
|
||||
</Route>
|
||||
<Route
|
||||
element={
|
||||
|
||||
@ -62,10 +62,10 @@ import { isAppWaitingForFreshObjectMetadataState } from '@/object-metadata/state
|
||||
import { workspaceAuthProvidersState } from '@/workspace/states/workspaceAuthProvidersState';
|
||||
import { i18n } from '@lingui/core';
|
||||
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 { isDefined } from 'twenty-shared/utils';
|
||||
import { getWorkspaceUrl } from '~/utils/getWorkspaceUrl';
|
||||
import { dynamicActivate } from '~/utils/i18n/dynamicActivate';
|
||||
|
||||
export const useAuth = () => {
|
||||
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 { t } from '@lingui/core/macro';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Card, H2Title, IconUserPin, Section } from 'twenty-ui';
|
||||
import {
|
||||
Role,
|
||||
UpdateWorkspaceMutation,
|
||||
useUpdateWorkspaceMutation,
|
||||
} 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 [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 { Trans } from '@lingui/react/macro';
|
||||
|
||||
export const RolesTableHeader = () => {
|
||||
export const SettingsRolesTableHeader = () => {
|
||||
return (
|
||||
<Table>
|
||||
<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 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 { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||
import { RoleAssignmentTableHeader } from '@/settings/roles/role-assignment/components/RoleAssignmentTableHeader';
|
||||
import { RoleAssignmentWorkspaceMemberPickerDropdown } from '@/settings/roles/role-assignment/components/RoleAssignmentWorkspaceMemberPickerDropdown';
|
||||
import { RoleAssignmentConfirmationModalSelectedWorkspaceMember } from '@/settings/roles/role-assignment/types/RoleAssignmentConfirmationModalSelectedWorkspaceMember';
|
||||
import { useUpdateWorkspaceMemberRole } from '@/settings/roles/hooks/useUpdateWorkspaceMemberRole';
|
||||
import { SettingsRoleAssignmentConfirmationModal } from '@/settings/roles/role-assignment/components/SettingsRoleAssignmentConfirmationModal';
|
||||
import { SettingsRoleAssignmentTableHeader } from '@/settings/roles/role-assignment/components/SettingsRoleAssignmentTableHeader';
|
||||
import { SettingsRoleAssignmentWorkspaceMemberPickerDropdown } from '@/settings/roles/role-assignment/components/SettingsRoleAssignmentWorkspaceMemberPickerDropdown';
|
||||
import { SettingsRoleAssignmentConfirmationModalSelectedWorkspaceMember } from '@/settings/roles/role-assignment/types/SettingsRoleAssignmentConfirmationModalSelectedWorkspaceMember';
|
||||
import { settingsAllRolesSelector } from '@/settings/roles/states/settingsAllRolesSelector';
|
||||
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { TextInput } from '@/ui/input/components/TextInput';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
@ -26,14 +30,8 @@ import {
|
||||
SearchRecord,
|
||||
WorkspaceMember,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import {
|
||||
GetRolesDocument,
|
||||
useGetRolesQuery,
|
||||
useUpdateWorkspaceMemberRoleMutation,
|
||||
} from '~/generated/graphql';
|
||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||
import { RoleAssignmentConfirmationModal } from './RoleAssignmentConfirmationModal';
|
||||
import { RoleAssignmentTableRow } from './RoleAssignmentTableRow';
|
||||
import { SettingsRoleAssignmentTableRow } from './SettingsRoleAssignmentTableRow';
|
||||
|
||||
const StyledAssignToMemberContainer = styled.div`
|
||||
display: flex;
|
||||
@ -67,43 +65,51 @@ const StyledNoMembers = styled(TableCell)`
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
`;
|
||||
|
||||
type RoleAssignmentProps = {
|
||||
role: Pick<Role, 'id' | 'label' | 'canUpdateAllSettings'> & {
|
||||
workspaceMembers: Array<WorkspaceMember>;
|
||||
};
|
||||
type SettingsRoleAssignmentProps = {
|
||||
roleId: string;
|
||||
isCreateMode?: boolean;
|
||||
};
|
||||
|
||||
export const RoleAssignment = ({ role }: RoleAssignmentProps) => {
|
||||
export const SettingsRoleAssignment = ({
|
||||
roleId,
|
||||
isCreateMode,
|
||||
}: SettingsRoleAssignmentProps) => {
|
||||
const settingsDraftRole = useRecoilValue(
|
||||
settingsDraftRoleFamilyState(roleId),
|
||||
);
|
||||
|
||||
const navigateSettings = useNavigateSettings();
|
||||
const [updateWorkspaceMemberRole] = useUpdateWorkspaceMemberRoleMutation({
|
||||
refetchQueries: [GetRolesDocument],
|
||||
});
|
||||
const {
|
||||
addWorkspaceMemberToRoleAndUpdateState,
|
||||
updateWorkspaceMemberRoleDraftState,
|
||||
} = useUpdateWorkspaceMemberRole(roleId);
|
||||
|
||||
const [confirmationModalIsOpen, setConfirmationModalIsOpen] =
|
||||
useState<boolean>(false);
|
||||
const [selectedWorkspaceMember, setSelectedWorkspaceMember] =
|
||||
useState<RoleAssignmentConfirmationModalSelectedWorkspaceMember | null>(
|
||||
useState<SettingsRoleAssignmentConfirmationModalSelectedWorkspaceMember | null>(
|
||||
null,
|
||||
);
|
||||
const { data: rolesData } = useGetRolesQuery();
|
||||
const { closeDropdown } = useDropdown('role-member-select');
|
||||
const [searchFilter, setSearchFilter] = useState('');
|
||||
const currentWorkspaceMembers = useRecoilValue(currentWorkspaceMembersState);
|
||||
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
||||
|
||||
const settingsAllRoles = useRecoilValue(settingsAllRolesSelector);
|
||||
|
||||
const workspaceMemberRoleMap = new Map<
|
||||
string,
|
||||
{ id: string; label: string }
|
||||
>();
|
||||
rolesData?.getRoles?.forEach((role) => {
|
||||
role.workspaceMembers.forEach((member) => {
|
||||
settingsAllRoles.forEach((role: Role) => {
|
||||
role.workspaceMembers.forEach((member: WorkspaceMember) => {
|
||||
workspaceMemberRoleMap.set(member.id, { id: role.id, label: role.label });
|
||||
});
|
||||
});
|
||||
|
||||
const filteredWorkspaceMembers = !searchFilter
|
||||
? role.workspaceMembers
|
||||
: role.workspaceMembers.filter((member) => {
|
||||
? settingsDraftRole.workspaceMembers
|
||||
: settingsDraftRole.workspaceMembers.filter((member) => {
|
||||
const searchTerm = searchFilter.toLowerCase();
|
||||
const firstName = member.name.firstName?.toLowerCase() || '';
|
||||
const lastName = member.name.lastName?.toLowerCase() || '';
|
||||
@ -116,7 +122,7 @@ export const RoleAssignment = ({ role }: RoleAssignmentProps) => {
|
||||
);
|
||||
});
|
||||
|
||||
const assignedWorkspaceMemberIds = role.workspaceMembers.map(
|
||||
const assignedWorkspaceMemberIds = settingsDraftRole.workspaceMembers.map(
|
||||
(workspaceMember) => workspaceMember.id,
|
||||
);
|
||||
|
||||
@ -153,12 +159,28 @@ export const RoleAssignment = ({ role }: RoleAssignmentProps) => {
|
||||
const handleConfirm = async () => {
|
||||
if (!selectedWorkspaceMember || !confirmationModalIsOpen) return;
|
||||
|
||||
await updateWorkspaceMemberRole({
|
||||
variables: {
|
||||
if (!isCreateMode) {
|
||||
await addWorkspaceMemberToRoleAndUpdateState({
|
||||
workspaceMemberId: selectedWorkspaceMember.id,
|
||||
roleId: role.id,
|
||||
},
|
||||
});
|
||||
});
|
||||
} else {
|
||||
const workspaceMember = currentWorkspaceMembers.find(
|
||||
(member) => member.id === selectedWorkspaceMember.id,
|
||||
);
|
||||
|
||||
if (!workspaceMember) {
|
||||
throw new Error('Workspace member not found');
|
||||
}
|
||||
|
||||
updateWorkspaceMemberRoleDraftState({
|
||||
workspaceMember: {
|
||||
id: workspaceMember.id,
|
||||
name: workspaceMember.name,
|
||||
colorScheme: '',
|
||||
userEmail: '',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
handleModalClose();
|
||||
};
|
||||
@ -190,11 +212,11 @@ export const RoleAssignment = ({ role }: RoleAssignmentProps) => {
|
||||
/>
|
||||
</StyledSearchContainer>
|
||||
<StyledTable>
|
||||
<RoleAssignmentTableHeader />
|
||||
<SettingsRoleAssignmentTableHeader />
|
||||
<StyledTableRows>
|
||||
{filteredWorkspaceMembers.length > 0 ? (
|
||||
filteredWorkspaceMembers.map((workspaceMember) => (
|
||||
<RoleAssignmentTableRow
|
||||
<SettingsRoleAssignmentTableRow
|
||||
key={workspaceMember.id}
|
||||
workspaceMember={workspaceMember}
|
||||
/>
|
||||
@ -233,7 +255,7 @@ export const RoleAssignment = ({ role }: RoleAssignmentProps) => {
|
||||
</>
|
||||
}
|
||||
dropdownComponents={
|
||||
<RoleAssignmentWorkspaceMemberPickerDropdown
|
||||
<SettingsRoleAssignmentWorkspaceMemberPickerDropdown
|
||||
excludedWorkspaceMemberIds={[
|
||||
...assignedWorkspaceMemberIds,
|
||||
currentWorkspaceMember?.id,
|
||||
@ -246,7 +268,7 @@ export const RoleAssignment = ({ role }: RoleAssignmentProps) => {
|
||||
</Section>
|
||||
|
||||
{confirmationModalIsOpen && selectedWorkspaceMember && (
|
||||
<RoleAssignmentConfirmationModal
|
||||
<SettingsRoleAssignmentConfirmationModal
|
||||
selectedWorkspaceMember={selectedWorkspaceMember}
|
||||
isOpen={true}
|
||||
onClose={handleModalClose}
|
||||
@ -1,23 +1,23 @@
|
||||
import { RoleAssignmentConfirmationModalSubtitle } from '@/settings/roles/role-assignment/components/RoleAssignmentConfirmationModalSubtitle';
|
||||
import { RoleAssignmentConfirmationModalSelectedWorkspaceMember } from '@/settings/roles/role-assignment/types/RoleAssignmentConfirmationModalSelectedWorkspaceMember';
|
||||
import { SettingsRoleAssignmentConfirmationModalSubtitle } from '@/settings/roles/role-assignment/components/SettingsRoleAssignmentConfirmationModalSubtitle';
|
||||
import { SettingsRoleAssignmentConfirmationModalSelectedWorkspaceMember } from '@/settings/roles/role-assignment/types/SettingsRoleAssignmentConfirmationModalSelectedWorkspaceMember';
|
||||
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
|
||||
import { t } from '@lingui/core/macro';
|
||||
|
||||
type RoleAssignmentConfirmationModalProps = {
|
||||
selectedWorkspaceMember: RoleAssignmentConfirmationModalSelectedWorkspaceMember;
|
||||
type SettingsRoleAssignmentConfirmationModalProps = {
|
||||
selectedWorkspaceMember: SettingsRoleAssignmentConfirmationModalSelectedWorkspaceMember;
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onConfirm: () => void;
|
||||
onRoleClick: (roleId: string) => void;
|
||||
};
|
||||
|
||||
export const RoleAssignmentConfirmationModal = ({
|
||||
export const SettingsRoleAssignmentConfirmationModal = ({
|
||||
selectedWorkspaceMember,
|
||||
isOpen,
|
||||
onClose,
|
||||
onConfirm,
|
||||
onRoleClick,
|
||||
}: RoleAssignmentConfirmationModalProps) => {
|
||||
}: SettingsRoleAssignmentConfirmationModalProps) => {
|
||||
const workspaceMemberName = selectedWorkspaceMember.name;
|
||||
|
||||
const title = t`Assign ${workspaceMemberName}?`;
|
||||
@ -28,7 +28,7 @@ export const RoleAssignmentConfirmationModal = ({
|
||||
setIsOpen={onClose}
|
||||
title={title}
|
||||
subtitle={
|
||||
<RoleAssignmentConfirmationModalSubtitle
|
||||
<SettingsRoleAssignmentConfirmationModalSubtitle
|
||||
selectedWorkspaceMember={selectedWorkspaceMember}
|
||||
onRoleClick={onRoleClick}
|
||||
/>
|
||||
@ -1,5 +1,5 @@
|
||||
import { SettingsCard } from '@/settings/components/SettingsCard';
|
||||
import { RoleAssignmentConfirmationModalSelectedWorkspaceMember } from '@/settings/roles/role-assignment/types/RoleAssignmentConfirmationModalSelectedWorkspaceMember';
|
||||
import { SettingsRoleAssignmentConfirmationModalSelectedWorkspaceMember } from '@/settings/roles/role-assignment/types/SettingsRoleAssignmentConfirmationModalSelectedWorkspaceMember';
|
||||
import styled from '@emotion/styled';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { Avatar } from 'twenty-ui';
|
||||
@ -8,15 +8,15 @@ const StyledSettingsCardContainer = styled.div`
|
||||
margin-top: ${({ theme }) => theme.spacing(6)};
|
||||
`;
|
||||
|
||||
type RoleAssignmentConfirmationModalSubtitleProps = {
|
||||
selectedWorkspaceMember: RoleAssignmentConfirmationModalSelectedWorkspaceMember;
|
||||
type SettingsRoleAssignmentConfirmationModalSubtitleProps = {
|
||||
selectedWorkspaceMember: SettingsRoleAssignmentConfirmationModalSelectedWorkspaceMember;
|
||||
onRoleClick: (roleId: string) => void;
|
||||
};
|
||||
|
||||
export const RoleAssignmentConfirmationModalSubtitle = ({
|
||||
export const SettingsRoleAssignmentConfirmationModalSubtitle = ({
|
||||
selectedWorkspaceMember,
|
||||
onRoleClick,
|
||||
}: RoleAssignmentConfirmationModalSubtitleProps) => {
|
||||
}: SettingsRoleAssignmentConfirmationModalSubtitleProps) => {
|
||||
const workspaceMemberName = selectedWorkspaceMember.name;
|
||||
|
||||
return (
|
||||
@ -2,7 +2,7 @@ import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import { t } from '@lingui/core/macro';
|
||||
|
||||
export const RoleAssignmentTableHeader = () => (
|
||||
export const SettingsRoleAssignmentTableHeader = () => (
|
||||
<TableRow gridAutoColumns="2fr 4fr">
|
||||
<TableHeader>{t`Name`}</TableHeader>
|
||||
<TableHeader>{t`Email`}</TableHeader>
|
||||
@ -28,13 +28,13 @@ const StyledTableCell = styled(TableCell)`
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
type RoleAssignmentTableRowProps = {
|
||||
type SettingsRoleAssignmentTableRowProps = {
|
||||
workspaceMember: WorkspaceMember;
|
||||
};
|
||||
|
||||
export const RoleAssignmentTableRow = ({
|
||||
export const SettingsRoleAssignmentTableRow = ({
|
||||
workspaceMember,
|
||||
}: RoleAssignmentTableRowProps) => {
|
||||
}: SettingsRoleAssignmentTableRowProps) => {
|
||||
return (
|
||||
<TableRow gridAutoColumns="2fr 4fr">
|
||||
<StyledTableCell>
|
||||
@ -1,6 +1,6 @@
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useObjectRecordSearchRecords } from '@/object-record/hooks/useObjectRecordSearchRecords';
|
||||
import { RoleAssignmentWorkspaceMemberPickerDropdownContent } from '@/settings/roles/role-assignment/components/RoleAssignmentWorkspaceMemberPickerDropdownContent';
|
||||
import { SettingsRoleAssignmentWorkspaceMemberPickerDropdownContent } from '@/settings/roles/role-assignment/components/SettingsRoleAssignmentWorkspaceMemberPickerDropdownContent';
|
||||
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
|
||||
@ -9,15 +9,15 @@ import { useLingui } from '@lingui/react/macro';
|
||||
import { ChangeEvent, useState } from 'react';
|
||||
import { SearchRecord } from '~/generated-metadata/graphql';
|
||||
|
||||
type RoleAssignmentWorkspaceMemberPickerDropdownProps = {
|
||||
type SettingsRoleAssignmentWorkspaceMemberPickerDropdownProps = {
|
||||
excludedWorkspaceMemberIds: string[];
|
||||
onSelect: (workspaceMemberSearchRecord: SearchRecord) => void;
|
||||
};
|
||||
|
||||
export const RoleAssignmentWorkspaceMemberPickerDropdown = ({
|
||||
export const SettingsRoleAssignmentWorkspaceMemberPickerDropdown = ({
|
||||
excludedWorkspaceMemberIds,
|
||||
onSelect,
|
||||
}: RoleAssignmentWorkspaceMemberPickerDropdownProps) => {
|
||||
}: SettingsRoleAssignmentWorkspaceMemberPickerDropdownProps) => {
|
||||
const [searchFilter, setSearchFilter] = useState('');
|
||||
|
||||
const { loading, searchRecords: workspaceMembers } =
|
||||
@ -46,7 +46,7 @@ export const RoleAssignmentWorkspaceMemberPickerDropdown = ({
|
||||
/>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer hasMaxHeight>
|
||||
<RoleAssignmentWorkspaceMemberPickerDropdownContent
|
||||
<SettingsRoleAssignmentWorkspaceMemberPickerDropdownContent
|
||||
loading={loading}
|
||||
searchFilter={searchFilter}
|
||||
filteredWorkspaceMembers={filteredWorkspaceMembers}
|
||||
@ -2,19 +2,19 @@ import { t } from '@lingui/core/macro';
|
||||
import { MenuItem, MenuItemAvatar } from 'twenty-ui';
|
||||
import { SearchRecord } from '~/generated-metadata/graphql';
|
||||
|
||||
type RoleAssignmentWorkspaceMemberPickerDropdownContentProps = {
|
||||
type SettingsRoleAssignmentWorkspaceMemberPickerDropdownContentProps = {
|
||||
loading: boolean;
|
||||
searchFilter: string;
|
||||
filteredWorkspaceMembers: SearchRecord[];
|
||||
onSelect: (workspaceMemberSearchRecord: SearchRecord) => void;
|
||||
};
|
||||
|
||||
export const RoleAssignmentWorkspaceMemberPickerDropdownContent = ({
|
||||
export const SettingsRoleAssignmentWorkspaceMemberPickerDropdownContent = ({
|
||||
loading,
|
||||
searchFilter,
|
||||
filteredWorkspaceMembers,
|
||||
onSelect,
|
||||
}: RoleAssignmentWorkspaceMemberPickerDropdownContentProps) => {
|
||||
}: SettingsRoleAssignmentWorkspaceMemberPickerDropdownContentProps) => {
|
||||
if (loading) {
|
||||
return null;
|
||||
}
|
||||
@ -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 { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { Checkbox } from 'twenty-ui';
|
||||
|
||||
@ -46,25 +47,33 @@ const StyledTableRow = styled(TableRow)`
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
type RolePermissionsObjectsTableRowProps = {
|
||||
permission: RolePermissionsObjectPermission;
|
||||
type SettingsRolePermissionsObjectsTableRowProps = {
|
||||
permission: SettingsRolePermissionsObjectPermission;
|
||||
isEditable: boolean;
|
||||
};
|
||||
|
||||
export const RolePermissionsObjectsTableRow = ({
|
||||
export const SettingsRolePermissionsObjectsTableRow = ({
|
||||
permission,
|
||||
}: RolePermissionsObjectsTableRowProps) => {
|
||||
isEditable,
|
||||
}: SettingsRolePermissionsObjectsTableRowProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<StyledTableRow key={permission.key}>
|
||||
<StyledTableRow>
|
||||
<StyledPermissionCell>
|
||||
<StyledIconWrapper>
|
||||
<StyledIcon>
|
||||
<permission.Icon size={14} />
|
||||
<permission.Icon size={theme.icon.size.sm} />
|
||||
</StyledIcon>
|
||||
</StyledIconWrapper>
|
||||
<StyledLabel>{permission.label}</StyledLabel>
|
||||
</StyledPermissionCell>
|
||||
<StyledCheckboxCell>
|
||||
<Checkbox checked={permission.value} disabled />
|
||||
<Checkbox
|
||||
checked={permission.value}
|
||||
onChange={() => permission.setValue(!permission.value)}
|
||||
disabled={!isEditable}
|
||||
/>
|
||||
</StyledCheckboxCell>
|
||||
</StyledTableRow>
|
||||
);
|
||||
@ -6,7 +6,10 @@ import { Checkbox } from 'twenty-ui';
|
||||
|
||||
const StyledNameHeader = styled(TableHeader)`
|
||||
flex: 1;
|
||||
padding-left: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledTypeHeader = styled(TableHeader)`
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
const StyledActionsHeader = styled(TableHeader)`
|
||||
@ -16,22 +19,24 @@ const StyledActionsHeader = styled(TableHeader)`
|
||||
padding-right: ${({ theme }) => theme.spacing(4)};
|
||||
`;
|
||||
|
||||
const StyledTypeHeader = styled(TableHeader)`
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
type RolePermissionsSettingsTableHeaderProps = {
|
||||
type SettingsRolePermissionsSettingsTableHeaderProps = {
|
||||
allPermissions: boolean;
|
||||
onToggleAll?: () => void;
|
||||
};
|
||||
|
||||
export const RolePermissionsSettingsTableHeader = ({
|
||||
export const SettingsRolePermissionsSettingsTableHeader = ({
|
||||
allPermissions,
|
||||
}: RolePermissionsSettingsTableHeaderProps) => (
|
||||
onToggleAll,
|
||||
}: SettingsRolePermissionsSettingsTableHeaderProps) => (
|
||||
<TableRow gridAutoColumns="3fr 4fr 24px">
|
||||
<StyledNameHeader>{t`Name`}</StyledNameHeader>
|
||||
<StyledTypeHeader>{t`Description`}</StyledTypeHeader>
|
||||
<StyledActionsHeader aria-label={t`Actions`}>
|
||||
<Checkbox checked={allPermissions} disabled />
|
||||
<Checkbox
|
||||
checked={allPermissions}
|
||||
disabled={!onToggleAll}
|
||||
onChange={onToggleAll}
|
||||
/>
|
||||
</StyledActionsHeader>
|
||||
</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 { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import { useTheme } from '@emotion/react';
|
||||
@ -33,13 +33,13 @@ const StyledIconContainer = styled.div`
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
type RolePermissionsSettingsTableRowProps = {
|
||||
permission: RolePermissionsSettingPermission;
|
||||
type SettingsRolePermissionsSettingsTableRowProps = {
|
||||
permission: SettingsRolePermissionsSettingPermission;
|
||||
};
|
||||
|
||||
export const RolePermissionsSettingsTableRow = ({
|
||||
export const SettingsRolePermissionsSettingsTableRow = ({
|
||||
permission,
|
||||
}: RolePermissionsSettingsTableRowProps) => {
|
||||
}: SettingsRolePermissionsSettingsTableRowProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
@ -47,7 +47,7 @@ export const RolePermissionsSettingsTableRow = ({
|
||||
<StyledPermissionCell>
|
||||
<StyledIconContainer>
|
||||
<permission.Icon
|
||||
size={16}
|
||||
size={theme.icon.size.md}
|
||||
color={theme.font.color.primary}
|
||||
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';
|
||||
|
||||
export type RolePermissionsObjectPermission = {
|
||||
export type SettingsRolePermissionsObjectPermission = {
|
||||
key: string;
|
||||
label: string;
|
||||
value: boolean;
|
||||
Icon: IconComponent;
|
||||
setValue: (value: boolean) => void;
|
||||
};
|
||||
@ -1,6 +1,6 @@
|
||||
import { IconComponent } from 'twenty-ui';
|
||||
|
||||
export type RolePermissionsSettingPermission = {
|
||||
export type SettingsRolePermissionsSettingPermission = {
|
||||
key: string;
|
||||
name: string;
|
||||
description: string;
|
||||
@ -42,5 +42,6 @@ export enum SettingsPath {
|
||||
AdminPanelOtherEnvVariables = 'admin-panel/other-env-variables',
|
||||
Lab = 'lab',
|
||||
Roles = 'roles',
|
||||
RoleCreate = 'roles/create',
|
||||
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 { useEffect } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { H3Title, IconLockOpen, IconSettings, IconUserPlus } from 'twenty-ui';
|
||||
|
||||
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;
|
||||
import { SettingsRolesQueryEffect } from '@/settings/roles/components/SettingsRolesQueryEffect';
|
||||
import { SettingsRole } from '@/settings/roles/role/components/SettingsRole';
|
||||
import { SettingsRoleEditEffect } from '@/settings/roles/role/components/SettingsRoleEditEffect';
|
||||
import { Navigate, useParams } from 'react-router-dom';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const SettingsRoleEdit = () => {
|
||||
const { roleId = '' } = useParams();
|
||||
const navigateSettings = useNavigateSettings();
|
||||
const { data: rolesData, loading: rolesLoading } = useGetRolesQuery({
|
||||
fetchPolicy: 'network-only',
|
||||
});
|
||||
const { roleId } = useParams();
|
||||
|
||||
const role = rolesData?.getRoles.find((r) => r.id === roleId);
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
if (!isDefined(roleId)) {
|
||||
return <Navigate to="/settings/roles" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<SubMenuTopBarContainer
|
||||
title={role && <H3Title title={role.label} />}
|
||||
links={[
|
||||
{
|
||||
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>
|
||||
<>
|
||||
<SettingsRolesQueryEffect />
|
||||
<SettingsRoleEditEffect roleId={roleId} />
|
||||
<SettingsRole roleId={roleId} isCreateMode={false} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,39 +1,11 @@
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
|
||||
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';
|
||||
import { SettingsRolesContainer } from '@/settings/roles/components/SettingsRolesContainer';
|
||||
import { SettingsRolesQueryEffect } from '@/settings/roles/components/SettingsRolesQueryEffect';
|
||||
|
||||
export const SettingsRoles = () => {
|
||||
const { t } = useLingui();
|
||||
const { data: rolesData, loading: rolesLoading } = useGetRolesQuery({
|
||||
fetchPolicy: 'network-only',
|
||||
});
|
||||
|
||||
return (
|
||||
<SubMenuTopBarContainer
|
||||
title={rolesData && <H3Title title={t`Roles`} />}
|
||||
links={[
|
||||
{
|
||||
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>
|
||||
<>
|
||||
<SettingsRolesQueryEffect />
|
||||
<SettingsRolesContainer />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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';
|
||||
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';
|
||||
|
||||
const meta: Meta<PageDecoratorArgs> = {
|
||||
title: 'Pages/Settings/Roles/SettingsRoles',
|
||||
component: SettingsRoles,
|
||||
decorators: [PageDecorator],
|
||||
args: { routePath: '/settings/roles' },
|
||||
args: {
|
||||
routePath: '/settings/roles',
|
||||
},
|
||||
parameters: {
|
||||
msw: graphqlMocks,
|
||||
},
|
||||
@ -23,3 +28,19 @@ export default meta;
|
||||
export type Story = StoryObj<typeof SettingsRoles>;
|
||||
|
||||
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 { IsBoolean, IsOptional, IsString } from 'class-validator';
|
||||
import { IsBoolean, IsOptional, IsString, IsUUID } from 'class-validator';
|
||||
|
||||
@InputType()
|
||||
export class CreateRoleInput {
|
||||
@IsUUID()
|
||||
@IsOptional()
|
||||
@Field({ nullable: true })
|
||||
id?: string;
|
||||
|
||||
@IsString()
|
||||
@Field({ nullable: false })
|
||||
label: string;
|
||||
|
||||
@ -100,12 +100,10 @@ const StyledInput = styled.input<InputProps>`
|
||||
& + label:before {
|
||||
--size: ${({ checkboxSize }) =>
|
||||
checkboxSize === CheckboxSize.Large ? '18px' : '12px'};
|
||||
background: ${({ theme, indeterminate, isChecked, disabled }) =>
|
||||
disabled && isChecked
|
||||
? theme.adaptiveColors.blue3
|
||||
: indeterminate || isChecked
|
||||
? theme.color.blue
|
||||
: 'transparent'};
|
||||
background: ${({ theme, indeterminate, isChecked, disabled }) => {
|
||||
if (!(indeterminate || isChecked)) return 'transparent';
|
||||
return disabled ? theme.adaptiveColors.blue3 : theme.color.blue;
|
||||
}};
|
||||
border-color: ${({
|
||||
theme,
|
||||
indeterminate,
|
||||
|
||||
Reference in New Issue
Block a user