Add default role to workspace (#10444)
## Context Adding a defaultRole to each workspace, this role will be automatically added when a member joins a workspace via invite link or public link (seeds work differently though). Took the occasion to refactor a bit the frontend components, splitting them in smaller components for more readability. ## Test <img width="948" alt="Screenshot 2025-02-24 at 14 54 02" src="https://github.com/user-attachments/assets/13ef1452-d3c9-4385-940c-2ced0f0b05ef" />
This commit is contained in:
@ -2045,6 +2045,7 @@ export type UpdateWorkflowVersionStepInput = {
|
||||
export type UpdateWorkspaceInput = {
|
||||
allowImpersonation?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
customDomain?: InputMaybe<Scalars['String']['input']>;
|
||||
defaultRoleId?: InputMaybe<Scalars['String']['input']>;
|
||||
displayName?: InputMaybe<Scalars['String']['input']>;
|
||||
inviteHash?: InputMaybe<Scalars['String']['input']>;
|
||||
isGoogleAuthEnabled?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
@ -2197,6 +2198,7 @@ export type Workspace = {
|
||||
customDomain?: Maybe<Scalars['String']['output']>;
|
||||
databaseSchema: Scalars['String']['output'];
|
||||
databaseUrl: Scalars['String']['output'];
|
||||
defaultRole?: Maybe<Role>;
|
||||
deletedAt?: Maybe<Scalars['DateTime']['output']>;
|
||||
displayName?: Maybe<Scalars['String']['output']>;
|
||||
featureFlags?: Maybe<Array<FeatureFlag>>;
|
||||
|
||||
@ -1833,6 +1833,7 @@ export type UpdateWorkflowVersionStepInput = {
|
||||
export type UpdateWorkspaceInput = {
|
||||
allowImpersonation?: InputMaybe<Scalars['Boolean']>;
|
||||
customDomain?: InputMaybe<Scalars['String']>;
|
||||
defaultRoleId?: InputMaybe<Scalars['String']>;
|
||||
displayName?: InputMaybe<Scalars['String']>;
|
||||
inviteHash?: InputMaybe<Scalars['String']>;
|
||||
isGoogleAuthEnabled?: InputMaybe<Scalars['Boolean']>;
|
||||
@ -1975,6 +1976,7 @@ export type Workspace = {
|
||||
customDomain?: Maybe<Scalars['String']>;
|
||||
databaseSchema: Scalars['String'];
|
||||
databaseUrl: Scalars['String'];
|
||||
defaultRole?: Maybe<Role>;
|
||||
deletedAt?: Maybe<Scalars['DateTime']>;
|
||||
displayName?: Maybe<Scalars['String']>;
|
||||
featureFlags?: Maybe<Array<FeatureFlag>>;
|
||||
@ -2436,7 +2438,7 @@ export type GetSsoIdentityProvidersQueryVariables = Exact<{ [key: string]: never
|
||||
|
||||
export type GetSsoIdentityProvidersQuery = { __typename?: 'Query', getSSOIdentityProviders: Array<{ __typename?: 'FindAvailableSSOIDPOutput', type: IdentityProviderType, id: string, name: string, issuer: string, status: SsoIdentityProviderStatus }> };
|
||||
|
||||
export type UserQueryFragmentFragment = { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canAccessFullAdminPanel: boolean, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, analyticsTinybirdJwts?: { __typename?: 'AnalyticsTinybirdJwtMap', getWebhookAnalytics: string, getPageviewsAnalytics: string, getUsersAnalytics: string, getServerlessFunctionDuration: string, getServerlessFunctionSuccessRate: string, getServerlessFunctionErrorCount: string } | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', settingsPermissions?: Array<SettingsPermissions> | null, objectRecordsPermissions?: Array<PermissionsOnAllObjectRecords> | null } | null, currentWorkspace?: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEnterpriseKey: boolean, customDomain?: string | null, metadataVersion: number, workspaceMembersCount?: number | null, workspaceUrls: { __typename?: 'workspaceUrls', subdomainUrl: string, customUrl?: string | null }, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: FeatureFlagKey, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus }> } | null, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, subdomain: string, customDomain?: string | null, workspaceUrls: { __typename?: 'workspaceUrls', subdomainUrl: string, customUrl?: string | null } } | null }> };
|
||||
export type UserQueryFragmentFragment = { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canAccessFullAdminPanel: boolean, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, analyticsTinybirdJwts?: { __typename?: 'AnalyticsTinybirdJwtMap', getWebhookAnalytics: string, getPageviewsAnalytics: string, getUsersAnalytics: string, getServerlessFunctionDuration: string, getServerlessFunctionSuccessRate: string, getServerlessFunctionErrorCount: string } | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', settingsPermissions?: Array<SettingsPermissions> | null, objectRecordsPermissions?: Array<PermissionsOnAllObjectRecords> | null } | null, currentWorkspace?: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEnterpriseKey: boolean, customDomain?: string | null, metadataVersion: number, workspaceMembersCount?: number | null, workspaceUrls: { __typename?: 'workspaceUrls', subdomainUrl: string, customUrl?: string | null }, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: FeatureFlagKey, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus }>, defaultRole?: { __typename?: 'Role', id: string, label: string, description?: string | null, canUpdateAllSettings: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean } | null } | null, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, subdomain: string, customDomain?: string | null, workspaceUrls: { __typename?: 'workspaceUrls', subdomainUrl: string, customUrl?: string | null } } | null }> };
|
||||
|
||||
export type DeleteUserAccountMutationVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
@ -2453,7 +2455,7 @@ export type UploadProfilePictureMutation = { __typename?: 'Mutation', uploadProf
|
||||
export type GetCurrentUserQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canAccessFullAdminPanel: boolean, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, analyticsTinybirdJwts?: { __typename?: 'AnalyticsTinybirdJwtMap', getWebhookAnalytics: string, getPageviewsAnalytics: string, getUsersAnalytics: string, getServerlessFunctionDuration: string, getServerlessFunctionSuccessRate: string, getServerlessFunctionErrorCount: string } | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', settingsPermissions?: Array<SettingsPermissions> | null, objectRecordsPermissions?: Array<PermissionsOnAllObjectRecords> | null } | null, currentWorkspace?: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEnterpriseKey: boolean, customDomain?: string | null, metadataVersion: number, workspaceMembersCount?: number | null, workspaceUrls: { __typename?: 'workspaceUrls', subdomainUrl: string, customUrl?: string | null }, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: FeatureFlagKey, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus }> } | null, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, subdomain: string, customDomain?: string | null, workspaceUrls: { __typename?: 'workspaceUrls', subdomainUrl: string, customUrl?: string | null } } | null }> } };
|
||||
export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canAccessFullAdminPanel: boolean, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, analyticsTinybirdJwts?: { __typename?: 'AnalyticsTinybirdJwtMap', getWebhookAnalytics: string, getPageviewsAnalytics: string, getUsersAnalytics: string, getServerlessFunctionDuration: string, getServerlessFunctionSuccessRate: string, getServerlessFunctionErrorCount: string } | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', settingsPermissions?: Array<SettingsPermissions> | null, objectRecordsPermissions?: Array<PermissionsOnAllObjectRecords> | null } | null, currentWorkspace?: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEnterpriseKey: boolean, customDomain?: string | null, metadataVersion: number, workspaceMembersCount?: number | null, workspaceUrls: { __typename?: 'workspaceUrls', subdomainUrl: string, customUrl?: string | null }, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: FeatureFlagKey, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus }>, defaultRole?: { __typename?: 'Role', id: string, label: string, description?: string | null, canUpdateAllSettings: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean } | null } | null, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, subdomain: string, customDomain?: string | null, workspaceUrls: { __typename?: 'workspaceUrls', subdomainUrl: string, customUrl?: string | null } } | null }> } };
|
||||
|
||||
export type ActivateWorkflowVersionMutationVariables = Exact<{
|
||||
workflowVersionId: Scalars['String'];
|
||||
@ -2556,7 +2558,7 @@ export type UpdateWorkspaceMutationVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type UpdateWorkspaceMutation = { __typename?: 'Mutation', updateWorkspace: { __typename?: 'Workspace', id: any, customDomain?: string | null, subdomain: string, displayName?: string | null, logo?: string | null, allowImpersonation: boolean, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean } };
|
||||
export type UpdateWorkspaceMutation = { __typename?: 'Mutation', updateWorkspace: { __typename?: 'Workspace', id: any, customDomain?: string | null, subdomain: string, displayName?: string | null, logo?: string | null, allowImpersonation: boolean, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, defaultRole?: { __typename?: 'Role', id: string, label: string, description?: string | null, canUpdateAllSettings: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean } | null } };
|
||||
|
||||
export type UploadWorkspaceLogoMutationVariables = Exact<{
|
||||
file: Scalars['Upload'];
|
||||
@ -2676,19 +2678,6 @@ export const AvailableSsoIdentityProvidersFragmentFragmentDoc = gql`
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const RoleFragmentFragmentDoc = gql`
|
||||
fragment RoleFragment on Role {
|
||||
id
|
||||
label
|
||||
description
|
||||
canUpdateAllSettings
|
||||
isEditable
|
||||
canReadAllObjectRecords
|
||||
canUpdateAllObjectRecords
|
||||
canSoftDeleteAllObjectRecords
|
||||
canDestroyAllObjectRecords
|
||||
}
|
||||
`;
|
||||
export const WorkspaceMemberQueryFragmentFragmentDoc = gql`
|
||||
fragment WorkspaceMemberQueryFragment on WorkspaceMember {
|
||||
id
|
||||
@ -2705,6 +2694,19 @@ export const WorkspaceMemberQueryFragmentFragmentDoc = gql`
|
||||
timeFormat
|
||||
}
|
||||
`;
|
||||
export const RoleFragmentFragmentDoc = gql`
|
||||
fragment RoleFragment on Role {
|
||||
id
|
||||
label
|
||||
description
|
||||
canUpdateAllSettings
|
||||
isEditable
|
||||
canReadAllObjectRecords
|
||||
canUpdateAllObjectRecords
|
||||
canSoftDeleteAllObjectRecords
|
||||
canDestroyAllObjectRecords
|
||||
}
|
||||
`;
|
||||
export const UserQueryFragmentFragmentDoc = gql`
|
||||
fragment UserQueryFragment on User {
|
||||
id
|
||||
@ -2768,6 +2770,9 @@ export const UserQueryFragmentFragmentDoc = gql`
|
||||
status
|
||||
}
|
||||
workspaceMembersCount
|
||||
defaultRole {
|
||||
...RoleFragment
|
||||
}
|
||||
}
|
||||
workspaces {
|
||||
workspace {
|
||||
@ -2784,7 +2789,8 @@ export const UserQueryFragmentFragmentDoc = gql`
|
||||
}
|
||||
userVars
|
||||
}
|
||||
${WorkspaceMemberQueryFragmentFragmentDoc}`;
|
||||
${WorkspaceMemberQueryFragmentFragmentDoc}
|
||||
${RoleFragmentFragmentDoc}`;
|
||||
export const GetTimelineCalendarEventsFromCompanyIdDocument = gql`
|
||||
query GetTimelineCalendarEventsFromCompanyId($companyId: UUID!, $page: Int!, $pageSize: Int!) {
|
||||
getTimelineCalendarEventsFromCompanyId(
|
||||
@ -5217,9 +5223,12 @@ export const UpdateWorkspaceDocument = gql`
|
||||
isGoogleAuthEnabled
|
||||
isMicrosoftAuthEnabled
|
||||
isPasswordAuthEnabled
|
||||
defaultRole {
|
||||
...RoleFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
${RoleFragmentFragmentDoc}`;
|
||||
export type UpdateWorkspaceMutationFn = Apollo.MutationFunction<UpdateWorkspaceMutation, UpdateWorkspaceMutationVariables>;
|
||||
|
||||
/**
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { createState } from '@ui/utilities/state/utils/createState';
|
||||
|
||||
import { Workspace } from '~/generated/graphql';
|
||||
import { Role, Workspace } from '~/generated/graphql';
|
||||
|
||||
export type CurrentWorkspace = Pick<
|
||||
Workspace,
|
||||
@ -23,7 +23,9 @@ export type CurrentWorkspace = Pick<
|
||||
| 'customDomain'
|
||||
| 'workspaceUrls'
|
||||
| 'metadataVersion'
|
||||
>;
|
||||
> & {
|
||||
defaultRole?: Omit<Role, 'workspaceMembers'> | null;
|
||||
};
|
||||
|
||||
export const currentWorkspaceState = createState<CurrentWorkspace | null>({
|
||||
key: 'currentWorkspaceState',
|
||||
|
||||
@ -342,6 +342,17 @@ export const responseData = {
|
||||
metadataVersion: 1,
|
||||
currentBillingSubscription: null,
|
||||
workspaceMembersCount: 1,
|
||||
defaultRole: {
|
||||
id: 'default-role-id',
|
||||
label: 'Default Role',
|
||||
description: 'Default Role Description',
|
||||
canUpdateAllSettings: true,
|
||||
isEditable: true,
|
||||
canReadAllObjectRecords: true,
|
||||
canUpdateAllObjectRecords: true,
|
||||
canSoftDeleteAllObjectRecords: true,
|
||||
canDestroyAllObjectRecords: true,
|
||||
}
|
||||
},
|
||||
currentBillingSubscription: null,
|
||||
billingSubscriptions: [],
|
||||
|
||||
@ -21,6 +21,16 @@ import {
|
||||
responseData as findManyObjectMetadataItemsResponseData,
|
||||
} from '../__mocks__/useFindManyObjectMetadataItems';
|
||||
|
||||
jest.mock('@/object-metadata/hooks/useUpdateOneFieldMetadataItem', () => ({
|
||||
useUpdateOneFieldMetadataItem: () => ({
|
||||
updateOneFieldMetadataItem: jest.fn().mockResolvedValue({
|
||||
data: {
|
||||
updateOneField: responseData.default,
|
||||
},
|
||||
}),
|
||||
}),
|
||||
}));
|
||||
|
||||
const fieldMetadataItem: FieldMetadataItem = {
|
||||
id: FIELD_METADATA_ID,
|
||||
createdAt: '',
|
||||
@ -111,17 +121,6 @@ const mocks = [
|
||||
},
|
||||
})),
|
||||
},
|
||||
{
|
||||
request: {
|
||||
query: queries.activateMetadataField,
|
||||
variables: variables.activateMetadataField,
|
||||
},
|
||||
result: jest.fn(() => ({
|
||||
data: {
|
||||
updateOneField: responseData.default,
|
||||
},
|
||||
})),
|
||||
},
|
||||
{
|
||||
request: {
|
||||
query: queries.createMetadataField,
|
||||
@ -133,26 +132,6 @@ const mocks = [
|
||||
},
|
||||
})),
|
||||
},
|
||||
{
|
||||
request: {
|
||||
query: queries.activateMetadataField,
|
||||
variables: variables.deactivateMetadataField,
|
||||
},
|
||||
result: jest.fn(() => ({
|
||||
data: {
|
||||
updateOneField: responseData.default,
|
||||
},
|
||||
})),
|
||||
},
|
||||
{
|
||||
request: {
|
||||
query: queries.getCurrentUser,
|
||||
variables: {},
|
||||
},
|
||||
result: jest.fn(() => ({
|
||||
data: responseData.getCurrentUser,
|
||||
})),
|
||||
},
|
||||
{
|
||||
request: {
|
||||
query: queries.getCurrentUser,
|
||||
|
||||
@ -57,7 +57,10 @@ export const UserProviderEffect = () => {
|
||||
setCurrentUser(queryData.currentUser);
|
||||
|
||||
if (isDefined(queryData.currentUser.currentWorkspace)) {
|
||||
setCurrentWorkspace(queryData.currentUser.currentWorkspace);
|
||||
setCurrentWorkspace({
|
||||
...queryData.currentUser.currentWorkspace,
|
||||
defaultRole: queryData.currentUser.currentWorkspace.defaultRole ?? null,
|
||||
});
|
||||
}
|
||||
|
||||
if (isDefined(queryData.currentUser.currentUserWorkspace)) {
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import { ROLE_FRAGMENT } from '@/settings/roles/graphql/fragments/roleFragment';
|
||||
import { WORKSPACE_MEMBER_QUERY_FRAGMENT } from '@/workspace-member/graphql/fragments/workspaceMemberQueryFragment';
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const USER_QUERY_FRAGMENT = gql`
|
||||
${ROLE_FRAGMENT}
|
||||
fragment UserQueryFragment on User {
|
||||
id
|
||||
firstName
|
||||
@ -64,6 +66,9 @@ export const USER_QUERY_FRAGMENT = gql`
|
||||
status
|
||||
}
|
||||
workspaceMembersCount
|
||||
defaultRole {
|
||||
...RoleFragment
|
||||
}
|
||||
}
|
||||
workspaces {
|
||||
workspace {
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import { ROLE_FRAGMENT } from '@/settings/roles/graphql/fragments/roleFragment';
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const UPDATE_WORKSPACE = gql`
|
||||
${ROLE_FRAGMENT}
|
||||
mutation UpdateWorkspace($input: UpdateWorkspaceInput!) {
|
||||
updateWorkspace(data: $input) {
|
||||
id
|
||||
@ -13,6 +15,9 @@ export const UPDATE_WORKSPACE = gql`
|
||||
isGoogleAuthEnabled
|
||||
isMicrosoftAuthEnabled
|
||||
isPasswordAuthEnabled
|
||||
defaultRole {
|
||||
...RoleFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@ -1,106 +1,19 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import {
|
||||
AppTooltip,
|
||||
Avatar,
|
||||
Button,
|
||||
H2Title,
|
||||
IconChevronRight,
|
||||
IconLock,
|
||||
IconPlus,
|
||||
IconUser,
|
||||
Section,
|
||||
TooltipDelay,
|
||||
} from 'twenty-ui';
|
||||
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
||||
import { Table } from '@/ui/layout/table/components/Table';
|
||||
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
||||
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import React from 'react';
|
||||
import { useGetRolesQuery } from '~/generated/graphql';
|
||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||
import { Roles } from '~/pages/settings/roles/components/Roles';
|
||||
import { RolesDefaultRole } from '~/pages/settings/roles/components/RolesDefaultRole';
|
||||
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
||||
|
||||
const StyledTable = styled(Table)`
|
||||
margin-top: ${({ theme }) => theme.spacing(0.5)};
|
||||
`;
|
||||
|
||||
const StyledTableRow = styled(TableRow)`
|
||||
&:hover {
|
||||
background: ${({ theme }) => theme.background.transparent.light};
|
||||
cursor: pointer;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledNameCell = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledAssignedCell = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
const StyledAvatarGroup = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
margin-right: ${({ theme }) => theme.spacing(1)};
|
||||
|
||||
> * {
|
||||
margin-left: -5px;
|
||||
|
||||
&:first-of-type {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledTableHeaderRow = styled(Table)`
|
||||
margin-bottom: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledBottomSection = styled(Section)`
|
||||
border-top: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
margin-top: ${({ theme }) => theme.spacing(2)};
|
||||
padding-top: ${({ theme }) => theme.spacing(4)};
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
`;
|
||||
|
||||
const StyledIconChevronRight = styled(IconChevronRight)`
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
`;
|
||||
|
||||
const StyledAvatarContainer = styled.div`
|
||||
border: 0px;
|
||||
`;
|
||||
|
||||
const StyledAssignedText = styled.div`
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
font-size: ${({ theme }) => theme.font.size.md};
|
||||
`;
|
||||
|
||||
export const SettingsRoles = () => {
|
||||
const { t } = useLingui();
|
||||
|
||||
const theme = useTheme();
|
||||
const navigateSettings = useNavigateSettings();
|
||||
const { data: rolesData, loading: rolesLoading } = useGetRolesQuery({
|
||||
fetchPolicy: 'network-only',
|
||||
});
|
||||
|
||||
const handleRoleClick = (roleId: string) => {
|
||||
navigateSettings(SettingsPath.RoleDetail, { roleId });
|
||||
};
|
||||
|
||||
return (
|
||||
<SubMenuTopBarContainer
|
||||
title={t`Roles`}
|
||||
@ -113,90 +26,12 @@ export const SettingsRoles = () => {
|
||||
]}
|
||||
>
|
||||
<SettingsPageContainer>
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`All roles`}
|
||||
description={t`Assign roles to specify each member's access permissions`}
|
||||
/>
|
||||
<StyledTable>
|
||||
<StyledTableHeaderRow>
|
||||
<TableRow>
|
||||
<TableHeader>
|
||||
<Trans>Name</Trans>
|
||||
</TableHeader>
|
||||
<TableHeader align={'right'}>
|
||||
<Trans>Assigned to</Trans>
|
||||
</TableHeader>
|
||||
<TableHeader align={'right'}></TableHeader>
|
||||
</TableRow>
|
||||
</StyledTableHeaderRow>
|
||||
{!rolesLoading &&
|
||||
rolesData?.getRoles.map((role) => (
|
||||
<StyledTableRow
|
||||
key={role.id}
|
||||
onClick={() => handleRoleClick(role.id)}
|
||||
>
|
||||
<TableCell>
|
||||
<StyledNameCell>
|
||||
<IconUser size={theme.icon.size.md} />
|
||||
{role.label}
|
||||
{!role.isEditable && (
|
||||
<IconLock size={theme.icon.size.sm} />
|
||||
)}
|
||||
</StyledNameCell>
|
||||
</TableCell>
|
||||
<TableCell align={'right'}>
|
||||
<StyledAssignedCell>
|
||||
<StyledAvatarGroup>
|
||||
{role.workspaceMembers
|
||||
.slice(0, 5)
|
||||
.map((workspaceMember) => (
|
||||
<React.Fragment key={workspaceMember.id}>
|
||||
<StyledAvatarContainer
|
||||
id={`avatar-${workspaceMember.id}`}
|
||||
>
|
||||
<Avatar
|
||||
avatarUrl={workspaceMember.avatarUrl}
|
||||
placeholderColorSeed={workspaceMember.id}
|
||||
placeholder={
|
||||
workspaceMember.name.firstName ?? ''
|
||||
}
|
||||
type="rounded"
|
||||
size="md"
|
||||
/>
|
||||
</StyledAvatarContainer>
|
||||
<AppTooltip
|
||||
anchorSelect={`#avatar-${workspaceMember.id}`}
|
||||
content={`${workspaceMember.name.firstName} ${workspaceMember.name.lastName}`}
|
||||
noArrow
|
||||
place="top"
|
||||
positionStrategy="fixed"
|
||||
delay={TooltipDelay.shortDelay}
|
||||
/>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</StyledAvatarGroup>
|
||||
<StyledAssignedText>
|
||||
{role.workspaceMembers.length}
|
||||
</StyledAssignedText>
|
||||
</StyledAssignedCell>
|
||||
</TableCell>
|
||||
<TableCell align={'right'}>
|
||||
<StyledIconChevronRight size={theme.icon.size.md} />
|
||||
</TableCell>
|
||||
</StyledTableRow>
|
||||
))}
|
||||
</StyledTable>
|
||||
<StyledBottomSection>
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
title={t`Create Role`}
|
||||
variant="secondary"
|
||||
size="small"
|
||||
soon
|
||||
/>
|
||||
</StyledBottomSection>
|
||||
</Section>
|
||||
{!rolesLoading && (
|
||||
<>
|
||||
<Roles roles={rolesData?.getRoles ?? []} />
|
||||
<RolesDefaultRole roles={rolesData?.getRoles ?? []} />
|
||||
</>
|
||||
)}
|
||||
</SettingsPageContainer>
|
||||
</SubMenuTopBarContainer>
|
||||
);
|
||||
|
||||
@ -6,8 +6,8 @@ import { RolePermissionsObjectPermission } from '~/pages/settings/roles/types/Ro
|
||||
|
||||
const StyledIconWrapper = styled.div`
|
||||
align-items: center;
|
||||
background: ${({ theme }) => theme.color.blue10};
|
||||
border: 1px solid ${({ theme }) => theme.color.blue30};
|
||||
background: ${({ theme }) => theme.adaptiveColors.blue1};
|
||||
border: 1px solid ${({ theme }) => theme.adaptiveColors.blue3};
|
||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
display: flex;
|
||||
height: ${({ theme }) => theme.spacing(4)};
|
||||
|
||||
@ -0,0 +1,46 @@
|
||||
import { Table } from '@/ui/layout/table/components/Table';
|
||||
import styled from '@emotion/styled';
|
||||
import { t } from '@lingui/core/macro';
|
||||
|
||||
import { Button, H2Title, IconPlus, Section } from 'twenty-ui';
|
||||
import { Role } from '~/generated-metadata/graphql';
|
||||
import { RolesTableHeader } from '~/pages/settings/roles/components/RolesTableHeader';
|
||||
import { RolesTableRow } from '~/pages/settings/roles/components/RolesTableRow';
|
||||
|
||||
const StyledTable = styled(Table)`
|
||||
margin-top: ${({ theme }) => theme.spacing(0.5)};
|
||||
`;
|
||||
|
||||
const StyledBottomSection = styled(Section)`
|
||||
border-top: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
margin-top: ${({ theme }) => theme.spacing(2)};
|
||||
padding-top: ${({ theme }) => theme.spacing(4)};
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
`;
|
||||
|
||||
export const Roles = ({ roles }: { roles: Role[] }) => {
|
||||
return (
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`All roles`}
|
||||
description={t`Assign roles to specify each member's access permissions`}
|
||||
/>
|
||||
<StyledTable>
|
||||
<RolesTableHeader />
|
||||
{roles.map((role) => (
|
||||
<RolesTableRow key={role.id} role={role} />
|
||||
))}
|
||||
</StyledTable>
|
||||
<StyledBottomSection>
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
title={t`Create Role`}
|
||||
variant="secondary"
|
||||
size="small"
|
||||
soon
|
||||
/>
|
||||
</StyledBottomSection>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,80 @@
|
||||
import {
|
||||
CurrentWorkspace,
|
||||
currentWorkspaceState,
|
||||
} from '@/auth/states/currentWorkspaceState';
|
||||
import { SettingsOptionCardContentSelect } from '@/settings/components/SettingsOptions/SettingsOptionCardContentSelect';
|
||||
import { Select } from '@/ui/input/components/Select';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
import { Card, H2Title, IconUserPin, Section } from 'twenty-ui';
|
||||
import {
|
||||
Role,
|
||||
UpdateWorkspaceMutation,
|
||||
useUpdateWorkspaceMutation,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
export const RolesDefaultRole = ({ roles }: { roles: Role[] }) => {
|
||||
const [updateWorkspace] = useUpdateWorkspaceMutation();
|
||||
|
||||
const [currentWorkspace, setCurrentWorkspace] = useRecoilState(
|
||||
currentWorkspaceState,
|
||||
);
|
||||
|
||||
const defaultRole = currentWorkspace?.defaultRole;
|
||||
|
||||
const updateDefaultRole = (
|
||||
defaultRoleId: string | null,
|
||||
currentWorkspace: CurrentWorkspace,
|
||||
) => {
|
||||
updateWorkspace({
|
||||
variables: {
|
||||
input: {
|
||||
defaultRoleId: isDefined(defaultRoleId) ? defaultRoleId : null,
|
||||
},
|
||||
},
|
||||
onCompleted: (data: UpdateWorkspaceMutation) => {
|
||||
const defaultRole = data.updateWorkspace.defaultRole;
|
||||
|
||||
setCurrentWorkspace({
|
||||
...currentWorkspace,
|
||||
defaultRole: isDefined(defaultRole) ? defaultRole : null,
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
if (!currentWorkspace) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Options`}
|
||||
description={t`Adjust the role-related settings`}
|
||||
/>
|
||||
<Card rounded>
|
||||
<SettingsOptionCardContentSelect
|
||||
Icon={IconUserPin}
|
||||
title="Default Role"
|
||||
description={t`Set a default role for this workspace`}
|
||||
>
|
||||
<Select
|
||||
selectSizeVariant="small"
|
||||
withSearchInput
|
||||
dropdownId="default-role-select"
|
||||
options={roles.map((role) => ({
|
||||
label: role.label,
|
||||
value: role.id,
|
||||
}))}
|
||||
value={defaultRole?.id ?? ''}
|
||||
onChange={(value) =>
|
||||
updateDefaultRole(value as string, currentWorkspace)
|
||||
}
|
||||
/>
|
||||
</SettingsOptionCardContentSelect>
|
||||
</Card>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,25 @@
|
||||
import { Table } from '@/ui/layout/table/components/Table';
|
||||
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import styled from '@emotion/styled';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
|
||||
const StyledTableHeaderRow = styled(Table)`
|
||||
margin-bottom: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
export const RolesTableHeader = () => {
|
||||
return (
|
||||
<StyledTableHeaderRow>
|
||||
<TableRow>
|
||||
<TableHeader>
|
||||
<Trans>Name</Trans>
|
||||
</TableHeader>
|
||||
<TableHeader align={'right'}>
|
||||
<Trans>Assigned to</Trans>
|
||||
</TableHeader>
|
||||
<TableHeader align={'right'}></TableHeader>
|
||||
</TableRow>
|
||||
</StyledTableHeaderRow>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,117 @@
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import React from 'react';
|
||||
import {
|
||||
AppTooltip,
|
||||
Avatar,
|
||||
IconChevronRight,
|
||||
IconLock,
|
||||
IconUser,
|
||||
TooltipDelay,
|
||||
} from 'twenty-ui';
|
||||
import { Role } from '~/generated-metadata/graphql';
|
||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||
|
||||
const StyledIconChevronRight = styled(IconChevronRight)`
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
`;
|
||||
|
||||
const StyledAvatarContainer = styled.div`
|
||||
border: 0px;
|
||||
`;
|
||||
|
||||
const StyledAssignedText = styled.div`
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
font-size: ${({ theme }) => theme.font.size.md};
|
||||
`;
|
||||
|
||||
const StyledNameCell = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledAssignedCell = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
const StyledAvatarGroup = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
margin-right: ${({ theme }) => theme.spacing(1)};
|
||||
|
||||
> * {
|
||||
margin-left: -5px;
|
||||
|
||||
&:first-of-type {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledTableRow = styled(TableRow)`
|
||||
&:hover {
|
||||
background: ${({ theme }) => theme.background.transparent.light};
|
||||
cursor: pointer;
|
||||
}
|
||||
`;
|
||||
|
||||
export const RolesTableRow = ({ role }: { role: Role }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const navigateSettings = useNavigateSettings();
|
||||
|
||||
const handleRoleClick = (roleId: string) => {
|
||||
navigateSettings(SettingsPath.RoleDetail, { roleId });
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledTableRow key={role.id} onClick={() => handleRoleClick(role.id)}>
|
||||
<TableCell>
|
||||
<StyledNameCell>
|
||||
<IconUser size={theme.icon.size.md} />
|
||||
{role.label}
|
||||
{!role.isEditable && <IconLock size={theme.icon.size.sm} />}
|
||||
</StyledNameCell>
|
||||
</TableCell>
|
||||
<TableCell align={'right'}>
|
||||
<StyledAssignedCell>
|
||||
<StyledAvatarGroup>
|
||||
{role.workspaceMembers.slice(0, 5).map((workspaceMember) => (
|
||||
<React.Fragment key={workspaceMember.id}>
|
||||
<StyledAvatarContainer id={`avatar-${workspaceMember.id}`}>
|
||||
<Avatar
|
||||
avatarUrl={workspaceMember.avatarUrl}
|
||||
placeholderColorSeed={workspaceMember.id}
|
||||
placeholder={workspaceMember.name.firstName ?? ''}
|
||||
type="rounded"
|
||||
size="md"
|
||||
/>
|
||||
</StyledAvatarContainer>
|
||||
<AppTooltip
|
||||
anchorSelect={`#avatar-${workspaceMember.id}`}
|
||||
content={`${workspaceMember.name.firstName} ${workspaceMember.name.lastName}`}
|
||||
noArrow
|
||||
place="top"
|
||||
positionStrategy="fixed"
|
||||
delay={TooltipDelay.shortDelay}
|
||||
/>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</StyledAvatarGroup>
|
||||
<StyledAssignedText>
|
||||
{role.workspaceMembers.length}
|
||||
</StyledAssignedText>
|
||||
</StyledAssignedCell>
|
||||
</TableCell>
|
||||
<TableCell align={'right'}>
|
||||
<StyledIconChevronRight size={theme.icon.size.md} />
|
||||
</TableCell>
|
||||
</StyledTableRow>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user