diff --git a/packages/twenty-front/src/pages/settings/roles/SettingsRoles.tsx b/packages/twenty-front/src/pages/settings/roles/SettingsRoles.tsx
index 86aad9830..87b9810c4 100644
--- a/packages/twenty-front/src/pages/settings/roles/SettingsRoles.tsx
+++ b/packages/twenty-front/src/pages/settings/roles/SettingsRoles.tsx
@@ -64,7 +64,7 @@ const StyledAvatarGroup = styled.div`
`;
const StyledTableHeaderRow = styled(Table)`
- margin-bottom: ${({ theme }) => theme.spacing(1.5)};
+ margin-bottom: ${({ theme }) => theme.spacing(2)};
`;
const StyledBottomSection = styled(Section)`
diff --git a/packages/twenty-front/src/pages/settings/roles/components/RoleAssignment.tsx b/packages/twenty-front/src/pages/settings/roles/components/RoleAssignment.tsx
index b7cc51579..2d52713ac 100644
--- a/packages/twenty-front/src/pages/settings/roles/components/RoleAssignment.tsx
+++ b/packages/twenty-front/src/pages/settings/roles/components/RoleAssignment.tsx
@@ -1,3 +1,4 @@
+import { currentWorkspaceMembersState } from '@/auth/states/currentWorkspaceMembersStates';
import { SettingsPath } from '@/types/SettingsPath';
import { TextInput } from '@/ui/input/components/TextInput';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
@@ -6,7 +7,16 @@ import { Table } from '@/ui/layout/table/components/Table';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { useState } from 'react';
-import { Button, H2Title, IconPlus, IconSearch, Section } from 'twenty-ui';
+import { useRecoilValue } from 'recoil';
+import {
+ AppTooltip,
+ Button,
+ H2Title,
+ IconPlus,
+ IconSearch,
+ Section,
+ TooltipDelay,
+} from 'twenty-ui';
import { Role, WorkspaceMember } from '~/generated-metadata/graphql';
import {
GetRolesDocument,
@@ -36,14 +46,6 @@ const StyledBottomSection = styled(Section)<{ hasRows: boolean }>`
justify-content: flex-end;
`;
-const StyledEmptyText = styled.div`
- align-items: center;
- color: ${({ theme }) => theme.font.color.light};
- display: flex;
- justify-content: center;
- margin-top: ${({ theme }) => theme.spacing(4)};
-`;
-
const StyledSearchContainer = styled.div`
margin: ${({ theme }) => theme.spacing(2)} 0;
`;
@@ -80,6 +82,7 @@ export const RoleAssignment = ({ role }: RoleAssignmentProps) => {
const { data: rolesData } = useGetRolesQuery();
const { closeDropdown } = useDropdown('role-member-select');
const [searchFilter, setSearchFilter] = useState('');
+ const currentWorkspaceMembers = useRecoilValue(currentWorkspaceMembersState);
const workspaceMemberRoleMap = new Map<
string,
@@ -154,6 +157,9 @@ export const RoleAssignment = ({ role }: RoleAssignmentProps) => {
setSearchFilter(text);
};
+ const allWorkspaceMembersHaveThisRole =
+ role.workspaceMembers.length === currentWorkspaceMembers.length;
+
return (
<>
@@ -180,13 +186,6 @@ export const RoleAssignment = ({ role }: RoleAssignmentProps) => {
onRemove={() => handleRemoveClick(workspaceMember)}
/>
))}
- {filteredWorkspaceMembers.length === 0 && (
-
- {searchFilter
- ? t`No members matching your search`
- : t`No members assigned to this role yet`}
-
- )}
0}>
@@ -194,12 +193,23 @@ export const RoleAssignment = ({ role }: RoleAssignmentProps) => {
dropdownId="role-member-select"
dropdownHotkeyScope={{ scope: 'roleAssignment' }}
clickableComponent={
-
+ <>
+
+
+
+
+ >
}
dropdownComponents={
theme.spacing(1.5)};
+ margin-bottom: ${({ theme }) => theme.spacing(2)};
`;
type RoleAssignmentTableHeaderProps = {
diff --git a/packages/twenty-front/src/pages/settings/roles/components/RolePermissions.tsx b/packages/twenty-front/src/pages/settings/roles/components/RolePermissions.tsx
index e9d7f1c36..f770d80c1 100644
--- a/packages/twenty-front/src/pages/settings/roles/components/RolePermissions.tsx
+++ b/packages/twenty-front/src/pages/settings/roles/components/RolePermissions.tsx
@@ -1,19 +1,126 @@
+import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
-import { H2Title, Section } from 'twenty-ui';
-import { Role } from '~/generated-metadata/graphql';
+import {
+ H2Title,
+ IconEye,
+ IconPencil,
+ IconTrash,
+ IconTrashX,
+ Section,
+} from 'twenty-ui';
+import { Role, SettingsFeatures } from '~/generated-metadata/graphql';
+import { RolePermissionsObjectsTableHeader } from '~/pages/settings/roles/components/RolePermissionsObjectsTableHeader';
+import { RolePermissionsSettingsTableHeader } from '~/pages/settings/roles/components/RolePermissionsSettingsTableHeader';
+import { RolePermissionsSettingsTableRow } from '~/pages/settings/roles/components/RolePermissionsSettingsTableRow';
+import { RolePermissionsObjectPermission } from '~/pages/settings/roles/types/RolePermissionsObjectPermission';
+import { RolePermissionsObjectsTableRow } from './RolePermissionsObjectsTableRow';
-type RolePermissionsProps = {
- role: Pick;
-};
+const StyledRolePermissionsContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: ${({ theme }) => theme.spacing(8)};
+`;
+
+export const RolePermissions = ({ role }: { role: Role }) => {
+ const objectPermissionsConfig: RolePermissionsObjectPermission[] = [
+ {
+ key: 'seeRecords',
+ label: 'See Records on All Objects',
+ icon: ,
+ value: true,
+ },
+ {
+ key: 'editRecords',
+ label: 'Edit Records on All Objects',
+ icon: ,
+ value: true,
+ },
+ {
+ key: 'deleteRecords',
+ label: 'Delete Records on All Objects',
+ icon: ,
+ value: true,
+ },
+ {
+ key: 'destroyRecords',
+ label: 'Destroy Records on All Objects',
+ icon: ,
+ value: true,
+ },
+ ];
+
+ const settingsPermissionsConfig = [
+ {
+ key: SettingsFeatures.API_KEYS_AND_WEBHOOKS,
+ label: 'API Keys and Webhooks',
+ type: 'Developer',
+ value: role.canUpdateAllSettings,
+ },
+ {
+ key: SettingsFeatures.ROLES,
+ label: 'Roles',
+ type: 'Members',
+ value: role.canUpdateAllSettings,
+ },
+ {
+ key: SettingsFeatures.WORKSPACE_SETTINGS,
+ label: 'Workspace Settings',
+ type: 'General',
+ value: role.canUpdateAllSettings,
+ },
+ {
+ key: SettingsFeatures.WORKSPACE_USERS,
+ label: 'Workspace Users',
+ type: 'Members',
+ value: role.canUpdateAllSettings,
+ },
+ {
+ key: SettingsFeatures.DATA_MODEL,
+ label: 'Data Model',
+ type: 'Data Model',
+ value: role.canUpdateAllSettings,
+ },
+ {
+ key: SettingsFeatures.ADMIN_PANEL,
+ label: 'Admin Panel',
+ type: 'Admin Panel',
+ value: role.canUpdateAllSettings,
+ },
+ {
+ key: SettingsFeatures.SECURITY_SETTINGS,
+ label: 'Security Settings',
+ type: 'Security',
+ value: role.canUpdateAllSettings,
+ },
+ ];
-// eslint-disable-next-line unused-imports/no-unused-vars
-export const RolePermissions = ({ role }: RolePermissionsProps) => {
return (
-
+
+
+
+
+ {objectPermissionsConfig.map((permission) => (
+
+ ))}
+
+
+
+
+ {settingsPermissionsConfig.map((permission) => (
+
+ ))}
+
+
);
};
diff --git a/packages/twenty-front/src/pages/settings/roles/components/RolePermissionsObjectsTableHeader.tsx b/packages/twenty-front/src/pages/settings/roles/components/RolePermissionsObjectsTableHeader.tsx
new file mode 100644
index 000000000..254bdb26f
--- /dev/null
+++ b/packages/twenty-front/src/pages/settings/roles/components/RolePermissionsObjectsTableHeader.tsx
@@ -0,0 +1,44 @@
+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 { t } from '@lingui/core/macro';
+import { Checkbox } from 'twenty-ui';
+
+const StyledTableHeaderRow = styled(TableRow)`
+ display: flex;
+`;
+
+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)};
+`;
+
+const StyledTable = styled(Table)`
+ margin-bottom: ${({ theme }) => theme.spacing(2)};
+`;
+
+type RolePermissionsObjectsTableHeaderProps = {
+ className?: string;
+ allPermissions: boolean;
+};
+
+export const RolePermissionsObjectsTableHeader = ({
+ className,
+ allPermissions,
+}: RolePermissionsObjectsTableHeaderProps) => (
+
+
+ {t`Name`}
+
+
+
+
+
+);
diff --git a/packages/twenty-front/src/pages/settings/roles/components/RolePermissionsObjectsTableRow.tsx b/packages/twenty-front/src/pages/settings/roles/components/RolePermissionsObjectsTableRow.tsx
new file mode 100644
index 000000000..725c5fcb6
--- /dev/null
+++ b/packages/twenty-front/src/pages/settings/roles/components/RolePermissionsObjectsTableRow.tsx
@@ -0,0 +1,69 @@
+import { TableCell } from '@/ui/layout/table/components/TableCell';
+import { TableRow } from '@/ui/layout/table/components/TableRow';
+import styled from '@emotion/styled';
+import { Checkbox } from 'twenty-ui';
+import { RolePermissionsObjectPermission } from '~/pages/settings/roles/types/RolePermissionsObjectPermission';
+
+const StyledIconWrapper = styled.div`
+ align-items: center;
+ background: ${({ theme }) => theme.color.blue10};
+ border: 1px solid ${({ theme }) => theme.color.blue30};
+ border-radius: ${({ theme }) => theme.border.radius.sm};
+ display: flex;
+ height: ${({ theme }) => theme.spacing(4)};
+ justify-content: center;
+ width: ${({ theme }) => theme.spacing(4)};
+`;
+
+const StyledIcon = styled.div`
+ align-items: center;
+ display: flex;
+ color: ${({ theme }) => theme.color.blue};
+ justify-content: center;
+`;
+
+const StyledLabel = styled.span`
+ color: ${({ theme }) => theme.font.color.primary};
+`;
+
+const StyledPermissionCell = styled(TableCell)`
+ align-items: center;
+ display: flex;
+ flex: 1;
+ gap: ${({ theme }) => theme.spacing(2)};
+ padding-left: ${({ theme }) => theme.spacing(2)};
+`;
+
+const StyledCheckboxCell = styled(TableCell)`
+ align-items: center;
+ display: flex;
+ justify-content: flex-end;
+ padding-right: ${({ theme }) => theme.spacing(4)};
+`;
+
+const StyledTableRow = styled(TableRow)`
+ align-items: center;
+ display: flex;
+`;
+
+type RolePermissionsObjectsTableRowProps = {
+ permission: RolePermissionsObjectPermission;
+};
+
+export const RolePermissionsObjectsTableRow = ({
+ permission,
+}: RolePermissionsObjectsTableRowProps) => {
+ return (
+
+
+
+ {permission.icon}
+
+ {permission.label}
+
+
+
+
+
+ );
+};
diff --git a/packages/twenty-front/src/pages/settings/roles/components/RolePermissionsSettingsTableHeader.tsx b/packages/twenty-front/src/pages/settings/roles/components/RolePermissionsSettingsTableHeader.tsx
new file mode 100644
index 000000000..6e950d8bd
--- /dev/null
+++ b/packages/twenty-front/src/pages/settings/roles/components/RolePermissionsSettingsTableHeader.tsx
@@ -0,0 +1,52 @@
+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 { t } from '@lingui/core/macro';
+import { Checkbox } from 'twenty-ui';
+
+const StyledTableHeaderRow = styled(TableRow)`
+ align-items: center;
+ display: flex;
+ height: ${({ theme }) => theme.spacing(8)};
+`;
+
+const StyledNameHeader = styled(TableHeader)`
+ flex: 1;
+ padding-left: ${({ theme }) => theme.spacing(2)};
+`;
+
+const StyledActionsHeader = styled(TableHeader)`
+ align-items: center;
+ display: flex;
+ justify-content: flex-end;
+ padding-right: ${({ theme }) => theme.spacing(4)};
+`;
+
+const StyledTable = styled(Table)`
+ margin-bottom: ${({ theme }) => theme.spacing(2)};
+`;
+
+const StyledTypeHeader = styled(TableHeader)`
+ flex: 1;
+`;
+
+type RolePermissionsSettingsTableHeaderProps = {
+ className?: string;
+ allPermissions: boolean;
+};
+
+export const RolePermissionsSettingsTableHeader = ({
+ className,
+ allPermissions,
+}: RolePermissionsSettingsTableHeaderProps) => (
+
+
+ {t`Name`}
+ {t`Type`}
+
+
+
+
+
+);
diff --git a/packages/twenty-front/src/pages/settings/roles/components/RolePermissionsSettingsTableRow.tsx b/packages/twenty-front/src/pages/settings/roles/components/RolePermissionsSettingsTableRow.tsx
new file mode 100644
index 000000000..5fd9a06d1
--- /dev/null
+++ b/packages/twenty-front/src/pages/settings/roles/components/RolePermissionsSettingsTableRow.tsx
@@ -0,0 +1,55 @@
+import { TableCell } from '@/ui/layout/table/components/TableCell';
+import { TableRow } from '@/ui/layout/table/components/TableRow';
+import styled from '@emotion/styled';
+import { Checkbox } from 'twenty-ui';
+import { RolePermissionsSettingPermission } from '~/pages/settings/roles/types/RolePermissionsSettingPermission';
+
+const StyledLabel = styled.span`
+ color: ${({ theme }) => theme.font.color.primary};
+`;
+
+const StyledType = styled(StyledLabel)`
+ color: ${({ theme }) => theme.font.color.secondary};
+`;
+
+const StyledPermissionCell = styled(TableCell)`
+ align-items: center;
+ display: flex;
+ flex: 1;
+ gap: ${({ theme }) => theme.spacing(2)};
+ padding-left: ${({ theme }) => theme.spacing(2)};
+`;
+
+const StyledCheckboxCell = styled(TableCell)`
+ align-items: center;
+ display: flex;
+ justify-content: flex-end;
+ padding-right: ${({ theme }) => theme.spacing(4)};
+`;
+
+const StyledTableRow = styled(TableRow)`
+ align-items: center;
+ display: flex;
+`;
+
+type RolePermissionsSettingsTableRowProps = {
+ permission: RolePermissionsSettingPermission;
+};
+
+export const RolePermissionsSettingsTableRow = ({
+ permission,
+}: RolePermissionsSettingsTableRowProps) => {
+ return (
+
+
+ {permission.label}
+
+
+ {permission.type}
+
+
+
+
+
+ );
+};
diff --git a/packages/twenty-front/src/pages/settings/roles/components/RoleWorkspaceMemberPickerDropdown.tsx b/packages/twenty-front/src/pages/settings/roles/components/RoleWorkspaceMemberPickerDropdown.tsx
index 83a4e767d..6c05f33ac 100644
--- a/packages/twenty-front/src/pages/settings/roles/components/RoleWorkspaceMemberPickerDropdown.tsx
+++ b/packages/twenty-front/src/pages/settings/roles/components/RoleWorkspaceMemberPickerDropdown.tsx
@@ -1,17 +1,13 @@
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
-import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
+import { useSearchRecords } from '@/object-record/hooks/useSearchRecords';
+import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
-import styled from '@emotion/styled';
+import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { ChangeEvent, useState } from 'react';
import { WorkspaceMember } from '~/generated-metadata/graphql';
import { RoleWorkspaceMemberPickerDropdownContent } from './RoleWorkspaceMemberPickerDropdownContent';
-const StyledWorkspaceMemberSelectContainer = styled.div`
- max-height: ${({ theme }) => theme.spacing(50)};
- overflow-y: auto;
-`;
-
type RoleWorkspaceMemberPickerDropdownProps = {
excludedWorkspaceMemberIds: string[];
onSelect: (workspaceMember: WorkspaceMember) => void;
@@ -23,23 +19,9 @@ export const RoleWorkspaceMemberPickerDropdown = ({
}: RoleWorkspaceMemberPickerDropdownProps) => {
const [searchFilter, setSearchFilter] = useState('');
- const { records: workspaceMembers, loading } = useFindManyRecords({
+ const { loading, records: workspaceMembers } = useSearchRecords({
objectNameSingular: CoreObjectNameSingular.WorkspaceMember,
- filter: searchFilter
- ? {
- or: [
- {
- name: { firstName: { ilike: `%${searchFilter}%` } },
- },
- {
- name: { lastName: { ilike: `%${searchFilter}%` } },
- },
- {
- userEmail: { ilike: `%${searchFilter}%` },
- },
- ],
- }
- : undefined,
+ searchInput: searchFilter,
});
const filteredWorkspaceMembers = (workspaceMembers?.filter(
@@ -52,20 +34,21 @@ export const RoleWorkspaceMemberPickerDropdown = ({
};
return (
-
+
-
+
+
-
-
+
+
);
};
diff --git a/packages/twenty-front/src/pages/settings/roles/components/RoleWorkspaceMemberPickerDropdownContent.tsx b/packages/twenty-front/src/pages/settings/roles/components/RoleWorkspaceMemberPickerDropdownContent.tsx
index 22e15b3a8..3b9a68660 100644
--- a/packages/twenty-front/src/pages/settings/roles/components/RoleWorkspaceMemberPickerDropdownContent.tsx
+++ b/packages/twenty-front/src/pages/settings/roles/components/RoleWorkspaceMemberPickerDropdownContent.tsx
@@ -1,37 +1,7 @@
-import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
-import { Avatar } from 'twenty-ui';
+import { MenuItem, MenuItemAvatar } from 'twenty-ui';
import { WorkspaceMember } from '~/generated-metadata/graphql';
-const StyledEmptyState = styled.div`
- align-items: center;
- color: ${({ theme }) => theme.font.color.light};
- display: flex;
- font-size: ${({ theme }) => theme.font.size.md};
- font-weight: ${({ theme }) => theme.font.weight.regular};
- height: ${({ theme }) => theme.spacing(8)};
- justify-content: flex-start;
- padding: ${({ theme }) => theme.spacing(2)};
-`;
-
-const StyledWorkspaceMemberItem = styled.div`
- align-items: center;
- cursor: pointer;
- display: flex;
- font-size: ${({ theme }) => theme.font.size.md};
- gap: ${({ theme }) => theme.spacing(2)};
- min-width: ${({ theme }) => theme.spacing(45)};
- padding: ${({ theme }) => theme.spacing(2)};
-
- &:hover {
- background: ${({ theme }) => theme.background.tertiary};
- }
-`;
-
-const StyledWorkspaceMemberName = styled.div`
- color: ${({ theme }) => theme.font.color.secondary};
-`;
-
type RoleWorkspaceMemberPickerDropdownContentProps = {
loading: boolean;
searchFilter: string;
@@ -49,34 +19,24 @@ export const RoleWorkspaceMemberPickerDropdownContent = ({
return null;
}
- if (!filteredWorkspaceMembers?.length) {
- return (
-
- {searchFilter
- ? t`No members matching this search`
- : t`No more members to add`}
-
- );
+ if (!filteredWorkspaceMembers?.length && searchFilter?.length > 0) {
+ return ;
}
return (
<>
{filteredWorkspaceMembers.map((workspaceMember) => (
- onSelect(workspaceMember)}
- aria-label={`${workspaceMember.name.firstName} ${workspaceMember.name.lastName}`}
- >
-
-
- {workspaceMember.name.firstName} {workspaceMember.name.lastName}
-
-
+ avatar={{
+ type: 'rounded',
+ size: 'md',
+ placeholder: workspaceMember.name.firstName ?? '',
+ placeholderColorSeed: workspaceMember.id,
+ }}
+ text={`${workspaceMember.name.firstName} ${workspaceMember.name.lastName}`}
+ />
))}
>
);
diff --git a/packages/twenty-front/src/pages/settings/roles/types/RolePermissionsObjectPermission.ts b/packages/twenty-front/src/pages/settings/roles/types/RolePermissionsObjectPermission.ts
new file mode 100644
index 000000000..1638d03e5
--- /dev/null
+++ b/packages/twenty-front/src/pages/settings/roles/types/RolePermissionsObjectPermission.ts
@@ -0,0 +1,6 @@
+export type RolePermissionsObjectPermission = {
+ key: string;
+ label: string;
+ icon: React.ReactNode;
+ value: boolean;
+};
diff --git a/packages/twenty-front/src/pages/settings/roles/types/RolePermissionsSettingPermission.ts b/packages/twenty-front/src/pages/settings/roles/types/RolePermissionsSettingPermission.ts
new file mode 100644
index 000000000..488fa6292
--- /dev/null
+++ b/packages/twenty-front/src/pages/settings/roles/types/RolePermissionsSettingPermission.ts
@@ -0,0 +1,6 @@
+export type RolePermissionsSettingPermission = {
+ key: string;
+ label: string;
+ type: string;
+ value: boolean;
+};
diff --git a/packages/twenty-server/src/database/commands/data-seed-dev-workspace.command.ts b/packages/twenty-server/src/database/commands/data-seed-dev-workspace.command.ts
index dc435894a..b67264efe 100644
--- a/packages/twenty-server/src/database/commands/data-seed-dev-workspace.command.ts
+++ b/packages/twenty-server/src/database/commands/data-seed-dev-workspace.command.ts
@@ -32,7 +32,6 @@ import { TypeORMService } from 'src/database/typeorm/typeorm.service';
import { InjectCacheStorage } from 'src/engine/core-modules/cache-storage/decorators/cache-storage.decorator';
import { CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service';
import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum';
-import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { FieldMetadataService } from 'src/engine/metadata-modules/field-metadata/field-metadata.service';
@@ -43,12 +42,11 @@ import { PETS_METADATA_SEEDS } from 'src/engine/seeder/metadata-seeds/pets-metad
import { SURVEY_RESULTS_METADATA_SEEDS } from 'src/engine/seeder/metadata-seeds/survey-results-metadata-seeds';
import { SeederService } from 'src/engine/seeder/seeder.service';
import { shouldSeedWorkspaceFavorite } from 'src/engine/utils/should-seed-workspace-favorite';
-import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
import { createWorkspaceViews } from 'src/engine/workspace-manager/standard-objects-prefill-data/create-workspace-views';
import { seedViewWithDemoData } from 'src/engine/workspace-manager/standard-objects-prefill-data/seed-view-with-demo-data';
import { opportunitiesTableByStageView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/opportunity-table-by-stage.view';
+import { WorkspaceManagerService } from 'src/engine/workspace-manager/workspace-manager.service';
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
-import { WorkspaceSyncMetadataService } from 'src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.service';
// TODO: implement dry-run
@Command({
@@ -63,14 +61,12 @@ export class DataSeedWorkspaceCommand extends CommandRunner {
constructor(
private readonly dataSourceService: DataSourceService,
private readonly typeORMService: TypeORMService,
- private readonly workspaceSyncMetadataService: WorkspaceSyncMetadataService,
- private readonly workspaceDataSourceService: WorkspaceDataSourceService,
private readonly fieldMetadataService: FieldMetadataService,
private readonly objectMetadataService: ObjectMetadataService,
@InjectCacheStorage(CacheStorageNamespace.EngineWorkspace)
private readonly workspaceSchemaCache: CacheStorageService,
- private readonly featureFlagService: FeatureFlagService,
private readonly seederService: SeederService,
+ private readonly workspaceManagerService: WorkspaceManagerService,
) {
super();
}
@@ -100,21 +96,7 @@ export class DataSeedWorkspaceCommand extends CommandRunner {
await rawDataSource.destroy();
- const schemaName =
- await this.workspaceDataSourceService.createWorkspaceDBSchema(
- workspaceId,
- );
-
- const dataSourceMetadata =
- await this.dataSourceService.createDataSourceMetadata(
- workspaceId,
- schemaName,
- );
-
- await this.workspaceSyncMetadataService.synchronize({
- workspaceId: workspaceId,
- dataSourceId: dataSourceMetadata.id,
- });
+ await this.workspaceManagerService.initDev(workspaceId);
}
async seedWorkspace(workspaceId: string) {
diff --git a/packages/twenty-server/src/database/typeorm-seeds/core/demo/user-workspaces.ts b/packages/twenty-server/src/database/typeorm-seeds/core/demo/user-workspaces.ts
index 5e49424cb..78ed485b5 100644
--- a/packages/twenty-server/src/database/typeorm-seeds/core/demo/user-workspaces.ts
+++ b/packages/twenty-server/src/database/typeorm-seeds/core/demo/user-workspaces.ts
@@ -4,6 +4,12 @@ import { DEMO_SEED_USER_IDS } from 'src/database/typeorm-seeds/core/demo/users';
const tableName = 'userWorkspace';
+export const DEV_SEED_USER_WORKSPACE_IDS = {
+ NOAH: '20202020-9e3b-46d4-a556-88b9ddc2b534',
+ HUGO: '20202020-3957-4908-9c36-2929a23f8457',
+ TIM: '20202020-9e3b-46d4-a556-88b9ddc2b015',
+};
+
export const seedUserWorkspaces = async (
workspaceDataSource: DataSource,
schemaName: string,
@@ -12,18 +18,21 @@ export const seedUserWorkspaces = async (
await workspaceDataSource
.createQueryBuilder()
.insert()
- .into(`${schemaName}.${tableName}`, ['userId', 'workspaceId'])
+ .into(`${schemaName}.${tableName}`, ['id', 'userId', 'workspaceId'])
.orIgnore()
.values([
{
+ id: DEV_SEED_USER_WORKSPACE_IDS.NOAH,
userId: DEMO_SEED_USER_IDS.NOAH,
workspaceId: workspaceId,
},
{
+ id: DEV_SEED_USER_WORKSPACE_IDS.HUGO,
userId: DEMO_SEED_USER_IDS.HUGO,
workspaceId: workspaceId,
},
{
+ id: DEV_SEED_USER_WORKSPACE_IDS.TIM,
userId: DEMO_SEED_USER_IDS.TIM,
workspaceId: workspaceId,
},
diff --git a/packages/twenty-server/src/database/typeorm-seeds/core/index.ts b/packages/twenty-server/src/database/typeorm-seeds/core/index.ts
index 2e686e076..0762b050a 100644
--- a/packages/twenty-server/src/database/typeorm-seeds/core/index.ts
+++ b/packages/twenty-server/src/database/typeorm-seeds/core/index.ts
@@ -1,9 +1,9 @@
import { DataSource } from 'typeorm';
+import { seedFeatureFlags } from 'src/database/typeorm-seeds/core/feature-flags';
+import { seedUserWorkspaces } from 'src/database/typeorm-seeds/core/user-workspaces';
import { seedUsers } from 'src/database/typeorm-seeds/core/users';
import { seedWorkspaces } from 'src/database/typeorm-seeds/core/workspaces';
-import { seedFeatureFlags } from 'src/database/typeorm-seeds/core/feature-flags';
-import { seedUserWorkspaces } from 'src/database/typeorm-seeds/core/userWorkspaces';
export const seedCoreSchema = async (
workspaceDataSource: DataSource,
diff --git a/packages/twenty-server/src/database/typeorm-seeds/core/userWorkspaces.ts b/packages/twenty-server/src/database/typeorm-seeds/core/user-workspaces.ts
similarity index 70%
rename from packages/twenty-server/src/database/typeorm-seeds/core/userWorkspaces.ts
rename to packages/twenty-server/src/database/typeorm-seeds/core/user-workspaces.ts
index b6170d848..b044fd0aa 100644
--- a/packages/twenty-server/src/database/typeorm-seeds/core/userWorkspaces.ts
+++ b/packages/twenty-server/src/database/typeorm-seeds/core/user-workspaces.ts
@@ -1,32 +1,43 @@
import { DataSource } from 'typeorm';
+import { DEV_SEED_USER_IDS } from 'src/database/typeorm-seeds/core/users';
import {
- SEED_APPLE_WORKSPACE_ID,
SEED_ACME_WORKSPACE_ID,
+ SEED_APPLE_WORKSPACE_ID,
} from 'src/database/typeorm-seeds/core/workspaces';
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
-import { DEV_SEED_USER_IDS } from 'src/database/typeorm-seeds/core/users';
const tableName = 'userWorkspace';
+export const DEV_SEED_USER_WORKSPACE_IDS = {
+ TIM: '20202020-9e3b-46d4-a556-88b9ddc2b035',
+ JONY: '20202020-3957-4908-9c36-2929a23f8353',
+ PHIL: '20202020-7169-42cf-bc47-1cfef15264b1',
+ TIM_ACME: '20202020-9e3b-46d4-a556-88b9ddc2b436',
+};
+
export const seedUserWorkspaces = async (
workspaceDataSource: DataSource,
schemaName: string,
workspaceId: string,
) => {
- let userWorkspaces: Pick[] = [];
+ let userWorkspaces: Pick[] =
+ [];
if (workspaceId === SEED_APPLE_WORKSPACE_ID) {
userWorkspaces = [
{
+ id: DEV_SEED_USER_WORKSPACE_IDS.TIM,
userId: DEV_SEED_USER_IDS.TIM,
workspaceId,
},
{
+ id: DEV_SEED_USER_WORKSPACE_IDS.JONY,
userId: DEV_SEED_USER_IDS.JONY,
workspaceId,
},
{
+ id: DEV_SEED_USER_WORKSPACE_IDS.PHIL,
userId: DEV_SEED_USER_IDS.PHIL,
workspaceId,
},
@@ -36,6 +47,7 @@ export const seedUserWorkspaces = async (
if (workspaceId === SEED_ACME_WORKSPACE_ID) {
userWorkspaces = [
{
+ id: DEV_SEED_USER_WORKSPACE_IDS.TIM_ACME,
userId: DEV_SEED_USER_IDS.TIM,
workspaceId,
},
@@ -44,7 +56,7 @@ export const seedUserWorkspaces = async (
await workspaceDataSource
.createQueryBuilder()
.insert()
- .into(`${schemaName}.${tableName}`, ['userId', 'workspaceId'])
+ .into(`${schemaName}.${tableName}`, ['id', 'userId', 'workspaceId'])
.orIgnore()
.values(userWorkspaces)
.execute();
diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-manager.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-manager.service.ts
index 05175f474..4d3ed7159 100644
--- a/packages/twenty-server/src/engine/workspace-manager/workspace-manager.service.ts
+++ b/packages/twenty-server/src/engine/workspace-manager/workspace-manager.service.ts
@@ -108,6 +108,31 @@ export class WorkspaceManagerService {
await this.prefillWorkspaceWithDemoObjects(dataSourceMetadata, workspaceId);
}
+ public async initDev(workspaceId: string): Promise {
+ const schemaName =
+ await this.workspaceDataSourceService.createWorkspaceDBSchema(
+ workspaceId,
+ );
+
+ const dataSourceMetadata =
+ await this.dataSourceService.createDataSourceMetadata(
+ workspaceId,
+ schemaName,
+ );
+
+ await this.workspaceSyncMetadataService.synchronize({
+ workspaceId: workspaceId,
+ dataSourceId: dataSourceMetadata.id,
+ });
+
+ const permissionsEnabled =
+ await this.permissionsService.isPermissionsEnabled();
+
+ if (permissionsEnabled === true) {
+ await this.initPermissions(workspaceId);
+ }
+ }
+
/**
*
* We are prefilling a few standard objects with data to make it easier for the user to get started.
@@ -209,30 +234,25 @@ export class WorkspaceManagerService {
workspaceId,
});
- const userWorkspace = await this.userWorkspaceRepository.find({
+ const userWorkspaces = await this.userWorkspaceRepository.find({
where: {
workspaceId,
},
});
- if (isEmpty(userWorkspace)) {
+ if (isEmpty(userWorkspaces)) {
throw new PermissionsException(
'User workspace not found',
PermissionsExceptionCode.USER_WORKSPACE_NOT_FOUND,
);
}
- if (userWorkspace.length > 1) {
- throw new PermissionsException(
- 'Multiple user workspaces found, cannot tell which one should be admin',
- PermissionsExceptionCode.TOO_MANY_ADMIN_CANDIDATES,
- );
+ for (const userWorkspace of userWorkspaces) {
+ await this.userRoleService.assignRoleToUserWorkspace({
+ workspaceId,
+ userWorkspaceId: userWorkspace.id,
+ roleId: adminRole.id,
+ });
}
-
- await this.userRoleService.assignRoleToUserWorkspace({
- workspaceId,
- userWorkspaceId: userWorkspace[0].id,
- roleId: adminRole.id,
- });
}
}
diff --git a/packages/twenty-ui/src/input/components/Checkbox.tsx b/packages/twenty-ui/src/input/components/Checkbox.tsx
index b981ea027..0c4af7b55 100644
--- a/packages/twenty-ui/src/input/components/Checkbox.tsx
+++ b/packages/twenty-ui/src/input/components/Checkbox.tsx
@@ -100,8 +100,12 @@ const StyledInput = styled.input`
& + label:before {
--size: ${({ checkboxSize }) =>
checkboxSize === CheckboxSize.Large ? '18px' : '12px'};
- background: ${({ theme, indeterminate, isChecked }) =>
- indeterminate || isChecked ? theme.color.blue : 'transparent'};
+ background: ${({ theme, indeterminate, isChecked, disabled }) =>
+ disabled && isChecked
+ ? theme.color.blue30
+ : indeterminate || isChecked
+ ? theme.color.blue
+ : 'transparent'};
border-color: ${({
theme,
indeterminate,
@@ -111,7 +115,7 @@ const StyledInput = styled.input`
}) => {
switch (true) {
case disabled:
- return theme.background.transparent.medium;
+ return isChecked ? theme.color.blue30 : theme.font.color.extraLight;
case indeterminate || isChecked:
return theme.color.blue;
case variant === CheckboxVariant.Primary: