mirror of
https://github.com/lingble/twenty.git
synced 2025-11-03 14:17:58 +00:00
Merge branch 'main' into feat/add-sub-field-filtering
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -9,7 +9,6 @@
|
||||
|
||||
.nx/installation
|
||||
.nx/cache
|
||||
projectStructure.cache.json
|
||||
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
|
||||
@@ -20,4 +20,8 @@ Your turn 👇
|
||||
|
||||
» 19-October-2024 by [Thefool76](https://oss.gg/thefool76) YouTube Link: [YouTube](https://youtu.be/KuAycGuW698?si=q-YxcukbbYuO8BWf)
|
||||
|
||||
» 26-October-2024 by [Khaan25](https://oss.gg/Khaan25) YouTube Link: [YouTube](https://www.youtube.com/watch?v=1ruo4tTWNIg)
|
||||
|
||||
» 28-October-2024 by [sateshcharan](https://oss.gg/sateshcharan) Youtube Link: [Youtube](https://youtu.be/qfZyhrhCeyo)
|
||||
|
||||
---
|
||||
|
||||
@@ -25,3 +25,9 @@ Your turn 👇
|
||||
» 22-October-2024 by [rajeevDewangan](https://oss.gg/rajeevDewangan) blog Link: [blog](https://open.substack.com/pub/rajeevdewangan/p/comprehensive-guide-to-self-hosting?r=4lly3x&utm_campaign=post&utm_medium=web&showWelcomeOnShare=true)
|
||||
|
||||
» 22-October-2024 by [Khaan25](https://oss.gg/Khaan25) blog Link: [blog](https://medium.com/@ziaurzai/twenty-crm-modern-solution-for-modern-problems-a0b65fec9d6c)
|
||||
|
||||
» 27-October-2024 by [Karan0207](https://oss.gg/karan0207) blog Link: [blog](https://medium.com/@karansingh0201k/my-journey-with-twenty-the-open-source-crm-that-really-gets-it-133879af6280)
|
||||
|
||||
» 27-October-2024 by [Vardhaman619](https://oss.gg/vardhaman619) blog Link: [blog](https://dev.to/vardhaman619/my-experience-with-modern-open-source-crm-twenty-crm-2hen)
|
||||
|
||||
» 28-October-2024 by [harshsbhat](https://oss.gg/harshsbhat) blog Link: [blog](https://www.harshbhat.me/blog/twenty-crm)
|
||||
@@ -25,4 +25,7 @@ Your turn 👇
|
||||
|
||||
» 24-October-2024 by [Khaan25](https://oss.gg/Khaan25) blog Link: [blog](https://medium.com/@ziaurzai/detailed-guide-on-self-hosting-twenty-crm-on-your-server-troubleshooting-and-best-practices-1f2ca15cd6eb)
|
||||
|
||||
» 26-October-2024 by [Yash-1511](https://oss.gg/Yash-1511) blog Link: [blog](https://medium.com/@yashp3020/a-comprehensive-guide-to-self-hosting-twenty-crm-with-docker-compose-40ea3fb4afdc)
|
||||
---
|
||||
|
||||
» 28-October-2024 by [harshsbhat](https://oss.gg/harshsbhat) blog Link: [blog](https://www.harshbhat.me/blog/twenty-self-host)
|
||||
@@ -20,4 +20,10 @@ Your turn 👇
|
||||
|
||||
» 24-October-2024 by [Thefool76](https://oss.gg/thefool76) video Link: [video](https://youtube.com/shorts/lC4oqm7UlCI?si=Md-nsfK9F6Shzjkv)
|
||||
|
||||
» 27-October-2024 by [Khaan25](https://oss.gg/Khaan25) video Link: [video](https://x.com/zia_webdev/status/1850409233663115529)
|
||||
|
||||
» 27-October-2024 by [sateshcharan](https://oss.gg/sateshcharan) video link: [video](https://youtube.com/shorts/OK52eaq0pAQ?feature=share)
|
||||
|
||||
» 28-October-2024 by [adityadeshlahre](https://oss.gg/adityadeshlahre) video link: [video](https://youtu.be/65sOHce1gjw)
|
||||
|
||||
---
|
||||
|
||||
@@ -20,5 +20,9 @@ Your turn 👇
|
||||
» 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) Figma Link: [Figma](https://twenty.com/)
|
||||
» 22-October-2024 by [rajeevDewangan](https://oss.gg/rajeevDewangan) Figma Link: [Figma](https://www.figma.com/design/XE21QdkFuy0IJHtmW7TURa/Twenty-(rajeevDewangan)?node-id=0-1&node-type=canvas&t=BYBulCT6hpJu6E8G-0)
|
||||
» 24-October-2024 by [Khaan25](https://oss.gg/Khaan25) Figma Link: [Figma](https://www.figma.com/design/HqYQrzel3e2TjzujwfdCXZ/Twenty-(Copy)---Khaan25?node-id=478-19796&t=QTB8gzKTudbVNeNs-1)
|
||||
» 24-October-2024 by [sateshcharan](https://oss.gg/sateshcharan) Figma Link: [Figma](https://www.figma.com/design/2qlAPS3llwf8jrWKGHEf6O/Twenty-(sateshcharan)?node-id=1633-94880&t=GIceWxqyY0ajWXnZ-1)
|
||||
» 28-October-2024 by [Vanshdeep Singh](https://oss.gg/Vanshdeepsingh-2232) Figma Link:[Figma](https://www.figma.com/design/akgDOb37YLUW9iWLB155EV/Twenty-(Copy)?node-id=478-19796&t=8Gz1yqls2Q3dsN9h-1)
|
||||
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
@@ -19,4 +19,5 @@ Your turn 👇
|
||||
» 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) video Link: [video](https://twenty.com/)
|
||||
|
||||
» 22-October-2024 by [FaheemOnHub](https://oss.gg/FaheemOnHub) video Link: [video](https://drive.google.com/file/d/1bR59Q5gqoqHjzgdrF6K68U2hloexkQYM/view)
|
||||
---
|
||||
|
||||
» 27-October-2024 by [Khaan25](https://oss.gg/Khaan25) video Link: [video](https://drive.google.com/file/d/1-wgzofJaWmnMcFgZZV5uYNNgtbJKJ_1G/view?usp=sharing/)
|
||||
|
||||
@@ -18,4 +18,6 @@ Your turn 👇
|
||||
|
||||
» 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) template Link: [template](https://twenty.com/)
|
||||
|
||||
» 25-October-2024 by [sateshcharan](https://oss.gg/sateshcharan) template Link: [template]()
|
||||
|
||||
---
|
||||
@@ -18,4 +18,6 @@ Your turn 👇
|
||||
|
||||
» 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) guide Link: [guide](https://twenty.com/)
|
||||
|
||||
» 26-October-2024 by [sateshcharan](https://oss.gg/sateshcharan) guide Link: [guide](https://dev.to/sateshcharan/supercharge-your-marketing-with-twentycrm-n8n-1hfd)
|
||||
|
||||
---
|
||||
@@ -19,3 +19,5 @@ Your turn 👇
|
||||
» 21-October-2024 by [sateshcharan](https://oss.gg/sateshcharan)
|
||||
|
||||
» 22-October-2024 by [Khaan25](https://oss.gg/Khaan25)
|
||||
|
||||
» 26-October-2024 by [Naprila](https://oss.gg/Naprila)
|
||||
@@ -49,6 +49,7 @@
|
||||
"@stoplight/elements": "^8.0.5",
|
||||
"@swc/jest": "^0.2.29",
|
||||
"@tabler/icons-react": "^2.44.0",
|
||||
"@tiptap/extension-hard-break": "^2.9.1",
|
||||
"@types/dompurify": "^3.0.5",
|
||||
"@types/facepaint": "^1.2.5",
|
||||
"@types/lodash.camelcase": "^4.3.7",
|
||||
@@ -295,7 +296,7 @@
|
||||
"eslint-plugin-jsx-a11y": "^6.8.0",
|
||||
"eslint-plugin-prefer-arrow": "^1.2.3",
|
||||
"eslint-plugin-prettier": "^5.1.2",
|
||||
"eslint-plugin-project-structure": "^3.7.2",
|
||||
"eslint-plugin-project-structure": "^3.9.1",
|
||||
"eslint-plugin-react": "^7.33.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.4",
|
||||
|
||||
3
packages/twenty-front/.gitignore
vendored
3
packages/twenty-front/.gitignore
vendored
@@ -42,3 +42,6 @@ dist-ssr
|
||||
|
||||
.vite/
|
||||
.nyc_output/
|
||||
|
||||
# eslint-plugin-project-structure
|
||||
projectStructure.cache.json
|
||||
|
||||
@@ -1,57 +1,45 @@
|
||||
{
|
||||
"$schema": "../../node_modules/eslint-plugin-project-structure/folderStructure.schema.json",
|
||||
"projectRoot": "packages/twenty-front",
|
||||
"structureRoot": "src",
|
||||
"regexParameters": {
|
||||
"camelCase": "^[a-z]+[A-Za-z0-9]+"
|
||||
"camelCase": "^[a-z]+([A-Za-z0-9]+)+",
|
||||
"kebab-case": "[a-z][a-z0-9]*(?:-[a-z0-9]+)*"
|
||||
},
|
||||
"structure": [
|
||||
{
|
||||
"name": "packages",
|
||||
"children": [
|
||||
{
|
||||
"name": "twenty-front",
|
||||
"children": [
|
||||
{ "name": "*", "children": [] },
|
||||
{ "name": "*" },
|
||||
{
|
||||
"name": "src",
|
||||
"children": [
|
||||
{ "name": "*", "children": [] },
|
||||
{ "name": "*" },
|
||||
{
|
||||
"name": "modules",
|
||||
"children": [
|
||||
{ "ruleId": "moduleFolderRule" },
|
||||
{ "name": "types", "ruleId": "doNotCheckLeafFolderRule" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
{ "name": "modules", "ruleId": "modulesFolderRule" }
|
||||
],
|
||||
"rules": {
|
||||
"modulesFolderRule": {
|
||||
"children": [
|
||||
{ "ruleId": "moduleFolderRule" },
|
||||
{ "name": "types", "children": [] }
|
||||
]
|
||||
},
|
||||
|
||||
"moduleFolderRule": {
|
||||
"name": "^(?!utils$|hooks$|states$|types$|graphql$|components$|effect-components$|constants$|validation-schemas$|contexts$|scopes$|services$|errors$)[a-z][a-z0-9]**(?:-[a-z0-9]+)**$",
|
||||
"name": "{kebab-case}",
|
||||
"folderRecursionLimit": 6,
|
||||
"children": [
|
||||
{ "ruleId": "moduleFolderRule" },
|
||||
{ "name": "hooks", "ruleId": "hooksLeafFolderRule" },
|
||||
{ "name": "utils", "ruleId": "utilsLeafFolderRule" },
|
||||
{ "name": "states", "ruleId": "doNotCheckLeafFolderRule" },
|
||||
{ "name": "types", "ruleId": "doNotCheckLeafFolderRule" },
|
||||
{ "name": "graphql", "ruleId": "doNotCheckLeafFolderRule" },
|
||||
{ "name": "components", "ruleId": "doNotCheckLeafFolderRule" },
|
||||
{ "name": "effect-components", "ruleId": "doNotCheckLeafFolderRule" },
|
||||
{ "name": "constants", "ruleId": "doNotCheckLeafFolderRule" },
|
||||
{ "name": "validation-schemas", "ruleId": "doNotCheckLeafFolderRule" },
|
||||
{ "name": "contexts", "ruleId": "doNotCheckLeafFolderRule" },
|
||||
{ "name": "scopes", "ruleId": "doNotCheckLeafFolderRule" },
|
||||
{ "name": "services", "ruleId": "doNotCheckLeafFolderRule" },
|
||||
{ "name": "errors", "ruleId": "doNotCheckLeafFolderRule" }
|
||||
{ "name": "states", "children": [] },
|
||||
{ "name": "types", "children": [] },
|
||||
{ "name": "graphql", "children": [] },
|
||||
{ "name": "components", "children": [] },
|
||||
{ "name": "effect-components", "children": [] },
|
||||
{ "name": "constants", "children": [] },
|
||||
{ "name": "validation-schemas", "children": [] },
|
||||
{ "name": "contexts", "children": [] },
|
||||
{ "name": "scopes", "children": [] },
|
||||
{ "name": "services", "children": [] },
|
||||
{ "name": "errors", "children": [] }
|
||||
]
|
||||
},
|
||||
|
||||
"hooksLeafFolderRule": {
|
||||
"folderRecursionLimit": 2,
|
||||
"children": [
|
||||
@@ -63,12 +51,8 @@
|
||||
{ "name": "internal", "ruleId": "hooksLeafFolderRule" }
|
||||
]
|
||||
},
|
||||
"doNotCheckLeafFolderRule": {
|
||||
"folderRecursionLimit": 1,
|
||||
"children": [{ "name": "*" }, { "name": "*", "children": [] }]
|
||||
},
|
||||
|
||||
"utilsLeafFolderRule": {
|
||||
"folderRecursionLimit": 1,
|
||||
"children": [
|
||||
{ "name": "{camelCase}.ts" },
|
||||
{
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
import { jest } from '@storybook/jest';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { graphql, HttpResponse } from 'msw';
|
||||
import { HttpResponse, graphql } from 'msw';
|
||||
import { HelmetProvider } from 'react-helmet-async';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
import { IconsProvider } from 'twenty-ui';
|
||||
|
||||
import { AppErrorBoundary } from '@/error-handler/components/AppErrorBoundary';
|
||||
import indexAppPath from '@/navigation/utils/indexAppPath';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope';
|
||||
import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser';
|
||||
|
||||
import { AppRouter } from '@/app/components/AppRouter';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { IconsProvider } from 'twenty-ui';
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
import { mockedUserData } from '~/testing/mock-data/users';
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -155,6 +155,11 @@ export type ClientConfig = {
|
||||
support: Support;
|
||||
};
|
||||
|
||||
export type ComputeStepOutputSchemaInput = {
|
||||
/** Step JSON format */
|
||||
step: Scalars['JSON'];
|
||||
};
|
||||
|
||||
export type CreateServerlessFunctionInput = {
|
||||
description?: InputMaybe<Scalars['String']>;
|
||||
name: Scalars['String'];
|
||||
@@ -424,6 +429,7 @@ export type Mutation = {
|
||||
authorizeApp: AuthorizeApp;
|
||||
challenge: LoginToken;
|
||||
checkoutSession: SessionEntity;
|
||||
computeStepOutputSchema: Scalars['JSON'];
|
||||
createOIDCIdentityProvider: SetupSsoOutput;
|
||||
createOneAppToken: AppToken;
|
||||
createOneObject: Object;
|
||||
@@ -509,6 +515,11 @@ export type MutationCheckoutSessionArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationComputeStepOutputSchemaArgs = {
|
||||
input: ComputeStepOutputSchemaInput;
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreateOidcIdentityProviderArgs = {
|
||||
input: SetupOidcSsoInput;
|
||||
};
|
||||
@@ -1155,12 +1166,13 @@ export type UpdateObjectPayload = {
|
||||
icon?: InputMaybe<Scalars['String']>;
|
||||
imageIdentifierFieldMetadataId?: InputMaybe<Scalars['String']>;
|
||||
isActive?: InputMaybe<Scalars['Boolean']>;
|
||||
isLabelSyncedWithName?: InputMaybe<Scalars['Boolean']>;
|
||||
labelIdentifierFieldMetadataId?: InputMaybe<Scalars['String']>;
|
||||
labelPlural?: InputMaybe<Scalars['String']>;
|
||||
labelSingular?: InputMaybe<Scalars['String']>;
|
||||
namePlural?: InputMaybe<Scalars['String']>;
|
||||
nameSingular?: InputMaybe<Scalars['String']>;
|
||||
shouldSyncLabelAndName?: InputMaybe<Scalars['Boolean']>;
|
||||
shortcut?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type UpdateOneObjectInput = {
|
||||
@@ -1470,6 +1482,7 @@ export type Object = {
|
||||
indexMetadatas: ObjectIndexMetadatasConnection;
|
||||
isActive: Scalars['Boolean'];
|
||||
isCustom: Scalars['Boolean'];
|
||||
isLabelSyncedWithName: Scalars['Boolean'];
|
||||
isRemote: Scalars['Boolean'];
|
||||
isSystem: Scalars['Boolean'];
|
||||
labelIdentifierFieldMetadataId?: Maybe<Scalars['String']>;
|
||||
@@ -1477,7 +1490,7 @@ export type Object = {
|
||||
labelSingular: Scalars['String'];
|
||||
namePlural: Scalars['String'];
|
||||
nameSingular: Scalars['String'];
|
||||
shouldSyncLabelAndName: Scalars['Boolean'];
|
||||
shortcut?: Maybe<Scalars['String']>;
|
||||
updatedAt: Scalars['DateTime'];
|
||||
};
|
||||
|
||||
@@ -1821,6 +1834,13 @@ export type ActivateWorkflowVersionMutationVariables = Exact<{
|
||||
|
||||
export type ActivateWorkflowVersionMutation = { __typename?: 'Mutation', activateWorkflowVersion: boolean };
|
||||
|
||||
export type ComputeStepOutputSchemaMutationVariables = Exact<{
|
||||
input: ComputeStepOutputSchemaInput;
|
||||
}>;
|
||||
|
||||
|
||||
export type ComputeStepOutputSchemaMutation = { __typename?: 'Mutation', computeStepOutputSchema: any };
|
||||
|
||||
export type DeactivateWorkflowVersionMutationVariables = Exact<{
|
||||
workflowVersionId: Scalars['String'];
|
||||
}>;
|
||||
@@ -3441,6 +3461,37 @@ export function useActivateWorkflowVersionMutation(baseOptions?: Apollo.Mutation
|
||||
export type ActivateWorkflowVersionMutationHookResult = ReturnType<typeof useActivateWorkflowVersionMutation>;
|
||||
export type ActivateWorkflowVersionMutationResult = Apollo.MutationResult<ActivateWorkflowVersionMutation>;
|
||||
export type ActivateWorkflowVersionMutationOptions = Apollo.BaseMutationOptions<ActivateWorkflowVersionMutation, ActivateWorkflowVersionMutationVariables>;
|
||||
export const ComputeStepOutputSchemaDocument = gql`
|
||||
mutation ComputeStepOutputSchema($input: ComputeStepOutputSchemaInput!) {
|
||||
computeStepOutputSchema(input: $input)
|
||||
}
|
||||
`;
|
||||
export type ComputeStepOutputSchemaMutationFn = Apollo.MutationFunction<ComputeStepOutputSchemaMutation, ComputeStepOutputSchemaMutationVariables>;
|
||||
|
||||
/**
|
||||
* __useComputeStepOutputSchemaMutation__
|
||||
*
|
||||
* To run a mutation, you first call `useComputeStepOutputSchemaMutation` within a React component and pass it any options that fit your needs.
|
||||
* When your component renders, `useComputeStepOutputSchemaMutation` 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 [computeStepOutputSchemaMutation, { data, loading, error }] = useComputeStepOutputSchemaMutation({
|
||||
* variables: {
|
||||
* input: // value for 'input'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useComputeStepOutputSchemaMutation(baseOptions?: Apollo.MutationHookOptions<ComputeStepOutputSchemaMutation, ComputeStepOutputSchemaMutationVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useMutation<ComputeStepOutputSchemaMutation, ComputeStepOutputSchemaMutationVariables>(ComputeStepOutputSchemaDocument, options);
|
||||
}
|
||||
export type ComputeStepOutputSchemaMutationHookResult = ReturnType<typeof useComputeStepOutputSchemaMutation>;
|
||||
export type ComputeStepOutputSchemaMutationResult = Apollo.MutationResult<ComputeStepOutputSchemaMutation>;
|
||||
export type ComputeStepOutputSchemaMutationOptions = Apollo.BaseMutationOptions<ComputeStepOutputSchemaMutation, ComputeStepOutputSchemaMutationVariables>;
|
||||
export const DeactivateWorkflowVersionDocument = gql`
|
||||
mutation DeactivateWorkflowVersion($workflowVersionId: String!) {
|
||||
deactivateWorkflowVersion(workflowVersionId: $workflowVersionId)
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useIsLogged } from '@/auth/hooks/useIsLogged';
|
||||
import { useDefaultHomePagePath } from '@/navigation/hooks/useDefaultHomePagePath';
|
||||
import { useOnboardingStatus } from '@/onboarding/hooks/useOnboardingStatus';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
|
||||
import { useSubscriptionStatus } from '@/workspace/hooks/useSubscriptionStatus';
|
||||
import { OnboardingStatus, SubscriptionStatus } from '~/generated/graphql';
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { useIsMatchingLocation } from '~/hooks/useIsMatchingLocation';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { apiKeyTokenState } from '@/settings/developers/states/generatedApiKeyTokenState';
|
||||
import { useRecoilValue, useResetRecoilState } from 'recoil';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { useRecoilValue, useResetRecoilState } from 'recoil';
|
||||
import { useIsMatchingLocation } from '~/hooks/useIsMatchingLocation';
|
||||
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const useCleanRecoilState = () => {
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
AnimatedPlaceholderEmptyTitle,
|
||||
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
|
||||
H3Title,
|
||||
Section,
|
||||
} from 'twenty-ui';
|
||||
|
||||
import { CalendarMonthCard } from '@/activities/calendar/components/CalendarMonthCard';
|
||||
@@ -21,7 +22,6 @@ import { SkeletonLoader } from '@/activities/components/SkeletonLoader';
|
||||
import { useCustomResolver } from '@/activities/hooks/useCustomResolver';
|
||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { Section } from '@/ui/layout/section/components/Section';
|
||||
import { TimelineCalendarEventsWithTotal } from '~/generated/graphql';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
EMPTY_PLACEHOLDER_TRANSITION_PROPS,
|
||||
H1Title,
|
||||
H1TitleFontColor,
|
||||
Section,
|
||||
} from 'twenty-ui';
|
||||
|
||||
import { ActivityList } from '@/activities/components/ActivityList';
|
||||
@@ -20,7 +21,6 @@ import { getTimelineThreadsFromPersonId } from '@/activities/emails/graphql/quer
|
||||
import { useCustomResolver } from '@/activities/hooks/useCustomResolver';
|
||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { Section } from '@/ui/layout/section/components/Section';
|
||||
import { TimelineThread, TimelineThreadsWithTotal } from '~/generated/graphql';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { IconCalendar, OverflowingTextWithTooltip } from 'twenty-ui';
|
||||
import {
|
||||
Checkbox,
|
||||
CheckboxShape,
|
||||
IconCalendar,
|
||||
OverflowingTextWithTooltip,
|
||||
} from 'twenty-ui';
|
||||
|
||||
import { useOpenActivityRightDrawer } from '@/activities/hooks/useOpenActivityRightDrawer';
|
||||
import { ActivityTargetsInlineCell } from '@/activities/inline-cell/components/ActivityTargetsInlineCell';
|
||||
import { getActivitySummary } from '@/activities/utils/getActivitySummary';
|
||||
import { Checkbox, CheckboxShape } from '@/ui/input/components/Checkbox';
|
||||
import { beautifyExactDate, hasDatePassed } from '~/utils/date-utils';
|
||||
|
||||
import { ActivityRow } from '@/activities/components/ActivityRow';
|
||||
|
||||
@@ -10,12 +10,12 @@ import { previousUrlState } from '@/auth/states/previousUrlState';
|
||||
import { tokenPairState } from '@/auth/states/tokenPairState';
|
||||
import { workspacesState } from '@/auth/states/workspaces';
|
||||
import { isDebugModeState } from '@/client-config/states/isDebugModeState';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
||||
import { useIsMatchingLocation } from '~/hooks/useIsMatchingLocation';
|
||||
import { useUpdateEffect } from '~/hooks/useUpdateEffect';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { ApolloFactory, Options } from '../services/apollo.factory';
|
||||
|
||||
export const useApolloFactory = (options: Partial<Options<any>> = {}) => {
|
||||
|
||||
@@ -27,11 +27,17 @@ export const GotoHotkeysEffectsProvider = () => {
|
||||
),
|
||||
});
|
||||
|
||||
return nonSystemActiveObjectMetadataItems.map((objectMetadataItem) => (
|
||||
return nonSystemActiveObjectMetadataItems.map((objectMetadataItem) => {
|
||||
if (!objectMetadataItem.shortcut) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<GoToHotkeyItemEffect
|
||||
key={`go-to-hokey-item-${objectMetadataItem.id}`}
|
||||
hotkey={objectMetadataItem.namePlural[0]}
|
||||
hotkey={objectMetadataItem.shortcut}
|
||||
pathToNavigateTo={`/objects/${objectMetadataItem.namePlural}`}
|
||||
/>
|
||||
));
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { IconCheckbox } from 'twenty-ui';
|
||||
|
||||
import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
|
||||
import {
|
||||
@@ -21,6 +20,7 @@ import { AppPath } from '@/types/AppPath';
|
||||
import { PageHotkeyScope } from '@/types/PageHotkeyScope';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
||||
import { IconCheckbox } from 'twenty-ui';
|
||||
import { useCleanRecoilState } from '~/hooks/useCleanRecoilState';
|
||||
import { useIsMatchingLocation } from '~/hooks/useIsMatchingLocation';
|
||||
import { usePageChangeEffectNavigateLocation } from '~/hooks/usePageChangeEffectNavigateLocation';
|
||||
@@ -115,7 +115,7 @@ export const PageChangeEffect = () => {
|
||||
break;
|
||||
}
|
||||
case isMatchingLocation(AppPath.CreateWorkspace): {
|
||||
setHotkeyScope(PageHotkeyScope.CreateWokspace);
|
||||
setHotkeyScope(PageHotkeyScope.CreateWorkspace);
|
||||
break;
|
||||
}
|
||||
case isMatchingLocation(AppPath.SyncEmails): {
|
||||
|
||||
@@ -2,7 +2,10 @@ import { FooterNote } from '@/auth/sign-in-up/components/FooterNote';
|
||||
import { HorizontalSeparator } from '@/auth/sign-in-up/components/HorizontalSeparator';
|
||||
import { useHandleResetPassword } from '@/auth/sign-in-up/hooks/useHandleResetPassword';
|
||||
import { SignInUpMode, useSignInUp } from '@/auth/sign-in-up/hooks/useSignInUp';
|
||||
import { useSignInUpForm } from '@/auth/sign-in-up/hooks/useSignInUpForm';
|
||||
import {
|
||||
useSignInUpForm,
|
||||
validationSchema,
|
||||
} from '@/auth/sign-in-up/hooks/useSignInUpForm';
|
||||
import { useSignInWithGoogle } from '@/auth/sign-in-up/hooks/useSignInWithGoogle';
|
||||
import { useSignInWithMicrosoft } from '@/auth/sign-in-up/hooks/useSignInWithMicrosoft';
|
||||
import { SignInUpStep } from '@/auth/states/signInUpStepState';
|
||||
@@ -24,6 +27,7 @@ import {
|
||||
IconMicrosoft,
|
||||
Loader,
|
||||
MainButton,
|
||||
StyledText,
|
||||
} from 'twenty-ui';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
@@ -127,7 +131,8 @@ export const SignInUpForm = () => {
|
||||
|
||||
const isEmailStepSubmitButtonDisabledCondition =
|
||||
signInUpStep === SignInUpStep.Email &&
|
||||
(form.watch('email')?.length === 0 || shouldWaitForCaptchaToken);
|
||||
(!validationSchema.shape.email.safeParse(form.watch('email')).success ||
|
||||
shouldWaitForCaptchaToken);
|
||||
|
||||
// TODO: isValid is actually a proxy function. If it is not rendered the first time, react might not trigger re-renders
|
||||
// We make the isValid check synchronous and update a reactState to make sure this does not happen
|
||||
@@ -183,7 +188,9 @@ export const SignInUpForm = () => {
|
||||
</>
|
||||
)}
|
||||
|
||||
<HorizontalSeparator visible={true} />
|
||||
{(authProviders.google ||
|
||||
authProviders.microsoft ||
|
||||
authProviders.sso) && <HorizontalSeparator visible />}
|
||||
|
||||
{authProviders.password &&
|
||||
(signInUpStep === SignInUpStep.Password ||
|
||||
@@ -263,6 +270,12 @@ export const SignInUpForm = () => {
|
||||
disableHotkeys
|
||||
onKeyDown={handleKeyDown}
|
||||
/>
|
||||
{signInUpMode === SignInUpMode.SignUp && (
|
||||
<StyledText
|
||||
text={'At least 8 characters long.'}
|
||||
color={theme.font.color.secondary}
|
||||
/>
|
||||
)}
|
||||
</StyledInputContainer>
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -5,20 +5,20 @@ import { useParams, useSearchParams } from 'react-router-dom';
|
||||
import { Form } from '@/auth/sign-in-up/hooks/useSignInUpForm';
|
||||
import { useReadCaptchaToken } from '@/captcha/hooks/useReadCaptchaToken';
|
||||
import { useRequestFreshCaptchaToken } from '@/captcha/hooks/useRequestFreshCaptchaToken';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { useIsMatchingLocation } from '~/hooks/useIsMatchingLocation';
|
||||
import { useRecoilState, useSetRecoilState } from 'recoil';
|
||||
import { useIsMatchingLocation } from '~/hooks/useIsMatchingLocation';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
import { useAuth } from '../../hooks/useAuth';
|
||||
import { useSSO } from '@/auth/sign-in-up/hooks/useSSO';
|
||||
import { availableSSOIdentityProvidersState } from '@/auth/states/availableWorkspacesForSSO';
|
||||
import {
|
||||
SignInUpStep,
|
||||
signInUpStepState,
|
||||
} from '@/auth/states/signInUpStepState';
|
||||
import { useSSO } from '@/auth/sign-in-up/hooks/useSSO';
|
||||
import { availableSSOIdentityProvidersState } from '@/auth/states/availableWorkspacesForSSO';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { useAuth } from '../../hooks/useAuth';
|
||||
|
||||
export enum SignInUpMode {
|
||||
SignIn = 'sign-in',
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useEffect } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { PASSWORD_REGEX } from '@/auth/utils/passwordRegex';
|
||||
import { isSignInPrefilledState } from '@/client-config/states/isSignInPrefilledState';
|
||||
|
||||
const validationSchema = z
|
||||
export const validationSchema = z
|
||||
.object({
|
||||
exist: z.boolean(),
|
||||
email: z.string().trim().email('Email must be a valid email'),
|
||||
|
||||
@@ -3,11 +3,12 @@ import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
|
||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { isDefaultLayoutAuthModalVisibleState } from '@/ui/layout/states/isDefaultLayoutAuthModalVisibleState';
|
||||
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { useGetWorkspaceFromInviteHashQuery } from '~/generated/graphql';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
|
||||
@@ -124,7 +124,8 @@ describe('useCommandMenu', () => {
|
||||
namePlural: 'tasks',
|
||||
labelSingular: 'Task',
|
||||
labelPlural: 'Tasks',
|
||||
shouldSyncLabelAndName: true,
|
||||
isLabelSyncedWithName: true,
|
||||
shortcut: 'T',
|
||||
description: 'A task',
|
||||
icon: 'IconCheckbox',
|
||||
isCustom: false,
|
||||
|
||||
@@ -83,8 +83,8 @@ export const useCommandMenu = () => {
|
||||
to: `/objects/${item.namePlural}`,
|
||||
label: `Go to ${item.labelPlural}`,
|
||||
type: CommandType.Navigate,
|
||||
firstHotKey: 'G',
|
||||
secondHotKey: item.labelPlural[0],
|
||||
firstHotKey: item.shortcut ? 'G' : undefined,
|
||||
secondHotKey: item.shortcut,
|
||||
Icon: ALL_ICONS[
|
||||
(item?.icon as keyof typeof ALL_ICONS) ?? 'IconArrowUpRight'
|
||||
],
|
||||
|
||||
@@ -3,13 +3,18 @@ import { findAvailableTimeZoneOption } from '@/localization/utils/findAvailableT
|
||||
describe('findAvailableTimeZoneOption', () => {
|
||||
it('should find the matching available IANA time zone select option from a given IANA time zone', () => {
|
||||
const ianaTimeZone = 'Europe/Paris';
|
||||
const expectedOption = {
|
||||
label: '(GMT+02:00) Central European Summer Time - Paris',
|
||||
value: 'Europe/Paris',
|
||||
};
|
||||
const expectedValue = 'Europe/Paris';
|
||||
const expectedLabelWinter =
|
||||
'(GMT+01:00) Central European Standard Time - Paris';
|
||||
const expectedLabelSummer =
|
||||
'(GMT+02:00) Central European Summer Time - Paris';
|
||||
|
||||
const option = findAvailableTimeZoneOption(ianaTimeZone);
|
||||
|
||||
expect(option).toEqual(expectedOption);
|
||||
expect(option.value).toEqual(expectedValue);
|
||||
expect(
|
||||
expectedLabelWinter === option.label ||
|
||||
expectedLabelSummer === option.label,
|
||||
).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,11 +3,17 @@ import { formatTimeZoneLabel } from '@/localization/utils/formatTimeZoneLabel';
|
||||
describe('formatTimeZoneLabel', () => {
|
||||
it('should format the time zone label correctly when location is included in the label', () => {
|
||||
const ianaTimeZone = 'Europe/Paris';
|
||||
const expectedLabel = '(GMT+02:00) Central European Summer Time - Paris';
|
||||
const expectedLabelSummer =
|
||||
'(GMT+02:00) Central European Summer Time - Paris';
|
||||
const expectedLabelWinter =
|
||||
'(GMT+01:00) Central European Standard Time - Paris';
|
||||
|
||||
const formattedLabel = formatTimeZoneLabel(ianaTimeZone);
|
||||
|
||||
expect(formattedLabel).toEqual(expectedLabel);
|
||||
expect(
|
||||
expectedLabelSummer === formattedLabel ||
|
||||
expectedLabelWinter === formattedLabel,
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should format the time zone label correctly when location is not included in the label', () => {
|
||||
|
||||
@@ -4,13 +4,12 @@ import { MemoryRouter } from 'react-router-dom';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
|
||||
import { currentMobileNavigationDrawerState } from '@/navigation/states/currentMobileNavigationDrawerState';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
|
||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||
import { IconsProviderDecorator } from '~/testing/decorators/IconsProviderDecorator';
|
||||
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
|
||||
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
|
||||
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
|
||||
import {
|
||||
AppNavigationDrawer,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
|
||||
import indexAppPath from '../indexAppPath';
|
||||
|
||||
describe('getIndexAppPath', () => {
|
||||
|
||||
@@ -43,8 +43,7 @@ export const NavigationDrawerItemForObjectMetadataItem = ({
|
||||
const shouldSubItemsBeDisplayed = isActive && objectMetadataViews.length > 1;
|
||||
|
||||
const sortedObjectMetadataViews = [...objectMetadataViews].sort(
|
||||
(viewA, viewB) =>
|
||||
viewA.key === 'INDEX' ? -1 : viewA.position - viewB.position,
|
||||
(viewA, viewB) => viewA.position - viewB.position,
|
||||
);
|
||||
|
||||
const selectedSubItemIndex = sortedObjectMetadataViews.findIndex(
|
||||
|
||||
@@ -24,7 +24,8 @@ export const FIND_MANY_OBJECT_METADATA_ITEMS = gql`
|
||||
updatedAt
|
||||
labelIdentifierFieldMetadataId
|
||||
imageIdentifierFieldMetadataId
|
||||
shouldSyncLabelAndName
|
||||
shortcut
|
||||
isLabelSyncedWithName
|
||||
indexMetadatas(paging: { first: 100 }) {
|
||||
edges {
|
||||
node {
|
||||
|
||||
@@ -11,5 +11,5 @@ export const query = gql`
|
||||
export const variables = { idToDelete: 'idToDelete' };
|
||||
|
||||
export const responseData = {
|
||||
id: 'idToDelete'
|
||||
id: 'idToDelete',
|
||||
};
|
||||
|
||||
@@ -2,7 +2,8 @@ import { gql } from '@apollo/client';
|
||||
import { FieldMetadataType } from '~/generated/graphql';
|
||||
|
||||
export const FIELD_METADATA_ID = '2c43466a-fe9e-4005-8d08-c5836067aa6c';
|
||||
export const FIELD_RELATION_METADATA_ID = '4da0302d-358a-45cd-9973-9f92723ed3c1';
|
||||
export const FIELD_RELATION_METADATA_ID =
|
||||
'4da0302d-358a-45cd-9973-9f92723ed3c1';
|
||||
export const RELATION_METADATA_ID = 'f81d4fae-7dec-11d0-a765-00a0c91e6bf6';
|
||||
|
||||
const baseFields = `
|
||||
@@ -34,7 +35,7 @@ export const queries = {
|
||||
id
|
||||
}
|
||||
}
|
||||
`,
|
||||
`,
|
||||
activateMetadataField: gql`
|
||||
mutation UpdateOneFieldMetadataItem(
|
||||
$idToUpdate: UUID!
|
||||
@@ -94,7 +95,7 @@ export const variables = {
|
||||
deactivateMetadataField: {
|
||||
idToUpdate: FIELD_METADATA_ID,
|
||||
updatePayload: { isActive: false, label: undefined },
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const defaultResponseData = {
|
||||
@@ -127,4 +128,3 @@ export const responseData = {
|
||||
options: [],
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ export const query = gql`
|
||||
updatedAt
|
||||
labelIdentifierFieldMetadataId
|
||||
imageIdentifierFieldMetadataId
|
||||
shortcut
|
||||
isLabelSyncedWithName
|
||||
fields(paging: { first: 1000 }, filter: $fieldFilter) {
|
||||
edges {
|
||||
node {
|
||||
|
||||
@@ -30,7 +30,7 @@ describe('objectMetadataItemSchema', () => {
|
||||
namePlural: 'notCamelCase',
|
||||
nameSingular: 'notCamelCase',
|
||||
updatedAt: 'invalid date',
|
||||
shouldSyncLabelAndName: 'not a boolean',
|
||||
isLabelSyncedWithName: 'not a boolean',
|
||||
};
|
||||
|
||||
// When
|
||||
|
||||
@@ -26,5 +26,6 @@ export const objectMetadataItemSchema = z.object({
|
||||
namePlural: camelCaseStringSchema,
|
||||
nameSingular: camelCaseStringSchema,
|
||||
updatedAt: z.string().datetime(),
|
||||
shouldSyncLabelAndName: z.boolean(),
|
||||
shortcut: z.string().nullable().optional(),
|
||||
isLabelSyncedWithName: z.boolean(),
|
||||
}) satisfies z.ZodType<ObjectMetadataItem>;
|
||||
|
||||
@@ -45,7 +45,7 @@ export const PERSON_FRAGMENT_WITH_DEPTH_ZERO_RELATIONS = `
|
||||
primaryLinkLabel
|
||||
secondaryLinks
|
||||
}
|
||||
`
|
||||
`;
|
||||
|
||||
export const PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS = `
|
||||
__typename
|
||||
@@ -324,4 +324,4 @@ export const PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS = `
|
||||
primaryLinkLabel
|
||||
secondaryLinks
|
||||
}
|
||||
`
|
||||
`;
|
||||
|
||||
@@ -3,10 +3,19 @@ import { gql } from '@apollo/client';
|
||||
|
||||
import { peopleQueryResult } from '~/testing/mock-data/people';
|
||||
|
||||
|
||||
export const query = gql`
|
||||
query FindManyPeople($filter: PersonFilterInput, $orderBy: [PersonOrderByInput], $lastCursor: String, $limit: Int) {
|
||||
people(filter: $filter, orderBy: $orderBy, first: $limit, after: $lastCursor){
|
||||
query FindManyPeople(
|
||||
$filter: PersonFilterInput
|
||||
$orderBy: [PersonOrderByInput]
|
||||
$lastCursor: String
|
||||
$limit: Int
|
||||
) {
|
||||
people(
|
||||
filter: $filter
|
||||
orderBy: $orderBy
|
||||
first: $limit
|
||||
after: $lastCursor
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
__typename
|
||||
@@ -27,38 +36,51 @@ export const query = gql`
|
||||
|
||||
export const mockPageSize = 2;
|
||||
|
||||
export const peopleMockWithIdsOnly: RecordGqlConnection = { ...peopleQueryResult.people,edges: peopleQueryResult.people.edges.map((edge) => ({ ...edge, node: { __typename: 'Person', id: edge.node.id } })) };
|
||||
export const peopleMockWithIdsOnly: RecordGqlConnection = {
|
||||
...peopleQueryResult.people,
|
||||
edges: peopleQueryResult.people.edges.map((edge) => ({
|
||||
...edge,
|
||||
node: { __typename: 'Person', id: edge.node.id },
|
||||
})),
|
||||
};
|
||||
|
||||
export const firstRequestLastCursor = peopleMockWithIdsOnly.edges[mockPageSize].cursor;
|
||||
export const secondRequestLastCursor = peopleMockWithIdsOnly.edges[mockPageSize * 2].cursor;
|
||||
export const thirdRequestLastCursor = peopleMockWithIdsOnly.edges[mockPageSize * 3].cursor;
|
||||
export const firstRequestLastCursor =
|
||||
peopleMockWithIdsOnly.edges[mockPageSize].cursor;
|
||||
export const secondRequestLastCursor =
|
||||
peopleMockWithIdsOnly.edges[mockPageSize * 2].cursor;
|
||||
export const thirdRequestLastCursor =
|
||||
peopleMockWithIdsOnly.edges[mockPageSize * 3].cursor;
|
||||
|
||||
export const variablesFirstRequest = {
|
||||
filter: undefined,
|
||||
limit: mockPageSize,
|
||||
orderBy: undefined
|
||||
orderBy: undefined,
|
||||
};
|
||||
|
||||
export const variablesSecondRequest = {
|
||||
filter: undefined,
|
||||
limit: mockPageSize,
|
||||
orderBy: undefined,
|
||||
lastCursor: firstRequestLastCursor
|
||||
lastCursor: firstRequestLastCursor,
|
||||
};
|
||||
|
||||
export const variablesThirdRequest = {
|
||||
filter: undefined,
|
||||
limit: mockPageSize,
|
||||
orderBy: undefined,
|
||||
lastCursor: secondRequestLastCursor
|
||||
}
|
||||
lastCursor: secondRequestLastCursor,
|
||||
};
|
||||
|
||||
const paginateRequestResponse = (response: RecordGqlConnection, start: number, end: number, hasNextPage: boolean, totalCount: number) => {
|
||||
const paginateRequestResponse = (
|
||||
response: RecordGqlConnection,
|
||||
start: number,
|
||||
end: number,
|
||||
hasNextPage: boolean,
|
||||
totalCount: number,
|
||||
) => {
|
||||
return {
|
||||
...response,
|
||||
edges: [
|
||||
...response.edges.slice(start, end)
|
||||
],
|
||||
edges: [...response.edges.slice(start, end)],
|
||||
pageInfo: {
|
||||
...response.pageInfo,
|
||||
startCursor: response.edges[start].cursor,
|
||||
@@ -66,17 +88,35 @@ const paginateRequestResponse = (response: RecordGqlConnection, start: number, e
|
||||
hasNextPage,
|
||||
} satisfies RecordGqlConnection['pageInfo'],
|
||||
totalCount,
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const responseFirstRequest = {
|
||||
people: paginateRequestResponse(peopleMockWithIdsOnly, 0, mockPageSize, true, 6),
|
||||
people: paginateRequestResponse(
|
||||
peopleMockWithIdsOnly,
|
||||
0,
|
||||
mockPageSize,
|
||||
true,
|
||||
6,
|
||||
),
|
||||
};
|
||||
|
||||
export const responseSecondRequest = {
|
||||
people: paginateRequestResponse(peopleMockWithIdsOnly, mockPageSize, mockPageSize * 2, true, 6),
|
||||
people: paginateRequestResponse(
|
||||
peopleMockWithIdsOnly,
|
||||
mockPageSize,
|
||||
mockPageSize * 2,
|
||||
true,
|
||||
6,
|
||||
),
|
||||
};
|
||||
|
||||
export const responseThirdRequest = {
|
||||
people: paginateRequestResponse(peopleMockWithIdsOnly, mockPageSize * 2, mockPageSize * 3, false, 6),
|
||||
people: paginateRequestResponse(
|
||||
peopleMockWithIdsOnly,
|
||||
mockPageSize * 2,
|
||||
mockPageSize * 3,
|
||||
false,
|
||||
6,
|
||||
),
|
||||
};
|
||||
|
||||
@@ -25,7 +25,7 @@ const objectMetadataItem: ObjectMetadataItem = {
|
||||
isRemote: false,
|
||||
labelPlural: 'object1s',
|
||||
labelSingular: 'object1',
|
||||
shouldSyncLabelAndName: true,
|
||||
isLabelSyncedWithName: true,
|
||||
};
|
||||
|
||||
describe('turnSortsIntoOrderBy', () => {
|
||||
|
||||
@@ -31,7 +31,7 @@ const StyledContainer = styled.div`
|
||||
|
||||
const StyledColumnContainer = styled.div`
|
||||
display: flex;
|
||||
& > *:not(:first-child) {
|
||||
& > *:not(:first-of-type) {
|
||||
border-left: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -18,7 +18,7 @@ const StyledHeaderContainer = styled.div`
|
||||
top: 0;
|
||||
}
|
||||
|
||||
& > *:not(:first-child) {
|
||||
& > *:not(:first-of-type) {
|
||||
border-left: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -17,7 +17,6 @@ import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/
|
||||
import { RecordValueSetterEffect } from '@/object-record/record-store/components/RecordValueSetterEffect';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { Checkbox, CheckboxVariant } from '@/ui/input/components/Checkbox';
|
||||
import { TextInput } from '@/ui/input/components/TextInput';
|
||||
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
|
||||
import { RecordBoardScrollWrapperContext } from '@/ui/utilities/scroll/contexts/ScrollWrapperContexts';
|
||||
@@ -29,6 +28,8 @@ import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
import {
|
||||
AnimatedEaseInOut,
|
||||
AvatarChipVariant,
|
||||
Checkbox,
|
||||
CheckboxVariant,
|
||||
ChipSize,
|
||||
IconEye,
|
||||
IconEyeOff,
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
FieldFullNameMetadata,
|
||||
FieldRatingMetadata,
|
||||
FieldSelectMetadata,
|
||||
FieldTextMetadata
|
||||
FieldTextMetadata,
|
||||
} from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
|
||||
@@ -29,7 +29,6 @@ if (!mockedPersonObjectMetadataItem) {
|
||||
throw new Error('Person object metadata item not found');
|
||||
}
|
||||
|
||||
|
||||
const relationFieldMetadataItem = mockedPersonObjectMetadataItem?.fields?.find(
|
||||
({ name }) => name === 'company',
|
||||
);
|
||||
|
||||
@@ -10,6 +10,7 @@ import { FieldRelationMetadata } from '@/object-record/record-field/types/FieldM
|
||||
import { MultiRecordSelect } from '@/object-record/relation-picker/components/MultiRecordSelect';
|
||||
import { useAddNewRecordAndOpenRightDrawer } from '@/object-record/relation-picker/hooks/useAddNewRecordAndOpenRightDrawer';
|
||||
import { RelationPickerScope } from '@/object-record/relation-picker/scopes/RelationPickerScope';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
|
||||
type RelationFromManyFieldInputProps = {
|
||||
onSubmit?: FieldInputEvent;
|
||||
@@ -50,6 +51,8 @@ export const RelationFromManyFieldInput = ({
|
||||
recordId,
|
||||
});
|
||||
|
||||
const { dropdownPlacement } = useDropdown(relationPickerScopeId);
|
||||
|
||||
return (
|
||||
<>
|
||||
<RelationPickerScope relationPickerScopeId={relationPickerScopeId}>
|
||||
@@ -58,6 +61,7 @@ export const RelationFromManyFieldInput = ({
|
||||
onSubmit={handleSubmit}
|
||||
onChange={updateRelation}
|
||||
onCreate={createNewRecordAndOpenRightDrawer}
|
||||
dropdownPlacement={dropdownPlacement}
|
||||
/>
|
||||
</RelationPickerScope>
|
||||
</>
|
||||
|
||||
@@ -32,7 +32,7 @@ export const RecordIdentifierChip = ({
|
||||
name={recordChipData.name}
|
||||
avatarType={recordChipData.avatarType}
|
||||
avatarUrl={recordChipData.avatarUrl ?? ''}
|
||||
to={indexIdentifierUrl(record.id)}
|
||||
to={indexIdentifierUrl ? indexIdentifierUrl(record.id) : undefined}
|
||||
variant={variant}
|
||||
LeftIcon={LeftIcon}
|
||||
LeftIconColor={LeftIconColor}
|
||||
|
||||
@@ -82,7 +82,8 @@ export const RecordDetailRelationSection = ({
|
||||
|
||||
const dropdownId = `record-field-card-relation-picker-${fieldDefinition.label}-${recordId}`;
|
||||
|
||||
const { closeDropdown, isDropdownOpen } = useDropdown(dropdownId);
|
||||
const { closeDropdown, isDropdownOpen, dropdownPlacement } =
|
||||
useDropdown(dropdownId);
|
||||
|
||||
const { setRelationPickerSearchFilter } = useRelationPicker({
|
||||
relationPickerScopeId: dropdownId,
|
||||
@@ -183,7 +184,7 @@ export const RecordDetailRelationSection = ({
|
||||
<DropdownScope dropdownScopeId={dropdownId}>
|
||||
<StyledAddDropdown
|
||||
dropdownId={dropdownId}
|
||||
dropdownPlacement="right-start"
|
||||
dropdownPlacement="left-start"
|
||||
onClose={handleCloseRelationPickerDropdown}
|
||||
clickableComponent={
|
||||
<LightIconButton
|
||||
@@ -204,6 +205,7 @@ export const RecordDetailRelationSection = ({
|
||||
}
|
||||
relationPickerScopeId={dropdownId}
|
||||
onCreate={createNewRecordAndOpenRightDrawer}
|
||||
dropdownPlacement={dropdownPlacement}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
@@ -212,6 +214,7 @@ export const RecordDetailRelationSection = ({
|
||||
onCreate={createNewRecordAndOpenRightDrawer}
|
||||
onChange={updateRelation}
|
||||
onSubmit={closeDropdown}
|
||||
dropdownPlacement={dropdownPlacement}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { Section } from '@/ui/layout/section/components/Section';
|
||||
import { Section } from 'twenty-ui';
|
||||
|
||||
const StyledRecordDetailSection = styled(Section)`
|
||||
border-top: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useCallback, useContext } from 'react';
|
||||
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
|
||||
import { RecordTableTd } from '@/object-record/record-table/record-table-cell/components/RecordTableTd';
|
||||
import { useSetCurrentRowSelected } from '@/object-record/record-table/record-table-row/hooks/useSetCurrentRowSelected';
|
||||
import { Checkbox } from '@/ui/input/components/Checkbox';
|
||||
import { Checkbox } from 'twenty-ui';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
align-items: center;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useContext } from 'react';
|
||||
|
||||
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
|
||||
import { RecordTableTd } from '@/object-record/record-table/record-table-cell/components/RecordTableTd';
|
||||
import { IconListViewGrip } from '@/ui/input/components/IconListViewGrip';
|
||||
import { IconListViewGrip } from 'twenty-ui';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
cursor: grab;
|
||||
|
||||
@@ -15,7 +15,7 @@ export const recordTableRow: RecordTableRowContextProps = {
|
||||
isPendingRow: false,
|
||||
};
|
||||
|
||||
export const recordTableCell:RecordTableCellContextProps= {
|
||||
export const recordTableCell: RecordTableCellContextProps = {
|
||||
columnIndex: 3,
|
||||
columnDefinition: {
|
||||
size: 1,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||
import { useIsFieldReadOnly } from '@/object-record/record-field/hooks/useIsFieldReadOnly';
|
||||
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
|
||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { CellHotkeyScopeContext } from '@/object-record/record-table/contexts/CellHotkeyScopeContext';
|
||||
@@ -34,6 +35,8 @@ export const useOpenRecordTableCellFromCell = () => {
|
||||
const { isReadOnly, pathToShowPage, objectNameSingular } = useContext(
|
||||
RecordTableRowContext,
|
||||
);
|
||||
const isFieldReadOnly = useIsFieldReadOnly();
|
||||
const cellIsReadOnly = isReadOnly || isFieldReadOnly;
|
||||
|
||||
const openTableCell = (
|
||||
initialValue?: string,
|
||||
@@ -44,7 +47,7 @@ export const useOpenRecordTableCellFromCell = () => {
|
||||
customCellHotkeyScope,
|
||||
recordId,
|
||||
fieldDefinition,
|
||||
isReadOnly,
|
||||
isReadOnly: cellIsReadOnly,
|
||||
pathToShowPage,
|
||||
objectNameSingular,
|
||||
initialValue,
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
|
||||
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
|
||||
import { Checkbox } from '@/ui/input/components/Checkbox';
|
||||
import { Checkbox } from 'twenty-ui';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
align-items: center;
|
||||
|
||||
@@ -5,6 +5,7 @@ import { MULTI_OBJECT_RECORD_SELECT_SELECTABLE_LIST_ID } from '@/object-record/r
|
||||
import { useRelationPickerScopedStates } from '@/object-record/relation-picker/hooks/internal/useRelationPickerScopedStates';
|
||||
import { RelationPickerScopeInternalContext } from '@/object-record/relation-picker/scopes/scope-internal-context/RelationPickerScopeInternalContext';
|
||||
import { CreateNewButton } from '@/ui/input/relation-picker/components/CreateNewButton';
|
||||
import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem';
|
||||
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
|
||||
@@ -18,11 +19,12 @@ import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
||||
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
|
||||
import styled from '@emotion/styled';
|
||||
import { Placement } from '@floating-ui/react';
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
import { Key } from 'ts-key-enum';
|
||||
import { IconPlus, isDefined } from 'twenty-ui';
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||
|
||||
export const StyledSelectableItem = styled(SelectableItem)`
|
||||
height: 100%;
|
||||
@@ -33,10 +35,12 @@ export const MultiRecordSelect = ({
|
||||
onChange,
|
||||
onSubmit,
|
||||
onCreate,
|
||||
dropdownPlacement,
|
||||
}: {
|
||||
onChange?: (changedRecordForSelectId: string) => void;
|
||||
onSubmit?: () => void;
|
||||
onCreate?: ((searchInput?: string) => void) | (() => void);
|
||||
dropdownPlacement?: Placement | null;
|
||||
}) => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const setHotkeyScope = useSetHotkeyScope();
|
||||
@@ -55,6 +59,7 @@ export const MultiRecordSelect = ({
|
||||
const recordMultiSelectIsLoading = useRecoilValue(
|
||||
recordMultiSelectIsLoadingState,
|
||||
);
|
||||
|
||||
const objectRecordsIdsMultiSelect = useRecoilValue(
|
||||
objectRecordsIdsMultiSelectState,
|
||||
);
|
||||
@@ -67,9 +72,6 @@ export const MultiRecordSelect = ({
|
||||
const relationPickerSearchFilter = useRecoilValue(
|
||||
relationPickerSearchFilterState,
|
||||
);
|
||||
const debouncedSetSearchFilter = useDebouncedCallback(setSearchFilter, 100, {
|
||||
leading: true,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setHotkeyScope(relationPickerScopedId);
|
||||
@@ -86,38 +88,15 @@ export const MultiRecordSelect = ({
|
||||
[onSubmit, goBackToPreviousHotkeyScope, resetSelectedItem],
|
||||
);
|
||||
|
||||
const debouncedOnCreate = useDebouncedCallback(
|
||||
() => onCreate?.(relationPickerSearchFilter),
|
||||
500,
|
||||
);
|
||||
|
||||
const handleFilterChange = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
debouncedSetSearchFilter(event.currentTarget.value);
|
||||
setSearchFilter(event.currentTarget.value);
|
||||
},
|
||||
[debouncedSetSearchFilter],
|
||||
[setSearchFilter],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<MultipleObjectRecordOnClickOutsideEffect
|
||||
containerRef={containerRef}
|
||||
onClickOutside={() => {
|
||||
onSubmit?.();
|
||||
}}
|
||||
/>
|
||||
<DropdownMenu ref={containerRef} data-select-disable>
|
||||
<DropdownMenuSearchInput
|
||||
value={relationPickerSearchFilter}
|
||||
onChange={handleFilterChange}
|
||||
autoFocus
|
||||
/>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer hasMaxHeight hasMinHeight>
|
||||
{recordMultiSelectIsLoading ? (
|
||||
<MenuItem text="Loading..." />
|
||||
) : (
|
||||
<>
|
||||
const results = (
|
||||
<DropdownMenuItemsContainer hasMaxHeight>
|
||||
<SelectableList
|
||||
selectableListId={MULTI_OBJECT_RECORD_SELECT_SELECTABLE_LIST_ID}
|
||||
selectableItemIdArray={objectRecordsIdsMultiSelect}
|
||||
@@ -140,23 +119,58 @@ export const MultiRecordSelect = ({
|
||||
);
|
||||
})}
|
||||
</SelectableList>
|
||||
{objectRecordsIdsMultiSelect?.length === 0 && (
|
||||
<MenuItem text="No result" />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{objectRecordsIdsMultiSelect?.length === 0 &&
|
||||
!recordMultiSelectIsLoading && <MenuItem text="No result" />}
|
||||
</DropdownMenuItemsContainer>
|
||||
{isDefined(onCreate) && (
|
||||
);
|
||||
|
||||
const createNewButton = isDefined(onCreate) && (
|
||||
<>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer>
|
||||
<CreateNewButton
|
||||
onClick={debouncedOnCreate}
|
||||
onClick={() => onCreate?.(relationPickerSearchFilter)}
|
||||
LeftIcon={IconPlus}
|
||||
text="Add New"
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<MultipleObjectRecordOnClickOutsideEffect
|
||||
containerRef={containerRef}
|
||||
onClickOutside={() => {
|
||||
onSubmit?.();
|
||||
}}
|
||||
/>
|
||||
<DropdownMenu ref={containerRef} data-select-disable>
|
||||
{dropdownPlacement?.includes('end') && (
|
||||
<>
|
||||
{createNewButton}
|
||||
{results}
|
||||
{recordMultiSelectIsLoading && !relationPickerSearchFilter && (
|
||||
<DropdownMenuSkeletonItem />
|
||||
)}
|
||||
<DropdownMenuSeparator />
|
||||
</>
|
||||
)}
|
||||
<DropdownMenuSearchInput
|
||||
value={relationPickerSearchFilter}
|
||||
onChange={handleFilterChange}
|
||||
autoFocus
|
||||
/>
|
||||
{(dropdownPlacement?.includes('start') ||
|
||||
isUndefinedOrNull(dropdownPlacement)) && (
|
||||
<>
|
||||
<DropdownMenuSeparator />
|
||||
{recordMultiSelectIsLoading && !relationPickerSearchFilter && (
|
||||
<DropdownMenuSkeletonItem />
|
||||
)}
|
||||
{results}
|
||||
{createNewButton}
|
||||
</>
|
||||
)}
|
||||
</DropdownMenu>
|
||||
</>
|
||||
|
||||
@@ -36,6 +36,7 @@ export type SingleEntitySelectMenuItemsProps = {
|
||||
isAllEntitySelectShown?: boolean;
|
||||
onAllEntitySelected?: () => void;
|
||||
hotkeyScope?: string;
|
||||
isFiltered: boolean;
|
||||
};
|
||||
|
||||
export const SingleEntitySelectMenuItems = ({
|
||||
@@ -54,6 +55,7 @@ export const SingleEntitySelectMenuItems = ({
|
||||
isAllEntitySelectShown,
|
||||
onAllEntitySelected,
|
||||
hotkeyScope = RelationPickerHotkeyScope.RelationPicker,
|
||||
isFiltered,
|
||||
}: SingleEntitySelectMenuItemsProps) => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
@@ -139,9 +141,11 @@ export const SingleEntitySelectMenuItems = ({
|
||||
}}
|
||||
>
|
||||
<DropdownMenuItemsContainer hasMaxHeight>
|
||||
{loading ? (
|
||||
{loading && !isFiltered ? (
|
||||
<DropdownMenuSkeletonItem />
|
||||
) : entitiesInDropdown.length === 0 && !isAllEntitySelectShown ? (
|
||||
) : entitiesInDropdown.length === 0 &&
|
||||
!isAllEntitySelectShown &&
|
||||
!loading ? (
|
||||
<>
|
||||
<MenuItem text="No result" />
|
||||
{entitiesToSelect.length > 0 && <DropdownMenuSeparator />}
|
||||
|
||||
@@ -6,7 +6,9 @@ import { useEntitySelectSearch } from '@/object-record/relation-picker/hooks/use
|
||||
import { useRelationPickerEntitiesOptions } from '@/object-record/relation-picker/hooks/useRelationPickerEntitiesOptions';
|
||||
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { Placement } from '@floating-ui/react';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||
|
||||
export type SingleEntitySelectMenuItemsWithSearchProps = {
|
||||
excludedRelationRecordIds?: string[];
|
||||
@@ -14,6 +16,7 @@ export type SingleEntitySelectMenuItemsWithSearchProps = {
|
||||
relationObjectNameSingular: string;
|
||||
relationPickerScopeId?: string;
|
||||
selectedRelationRecordIds: string[];
|
||||
dropdownPlacement?: Placement | null;
|
||||
} & Pick<
|
||||
SingleEntitySelectMenuItemsProps,
|
||||
| 'EmptyIcon'
|
||||
@@ -34,6 +37,7 @@ export const SingleEntitySelectMenuItemsWithSearch = ({
|
||||
relationPickerScopeId = 'relation-picker',
|
||||
selectedEntity,
|
||||
selectedRelationRecordIds,
|
||||
dropdownPlacement,
|
||||
}: SingleEntitySelectMenuItemsWithSearchProps) => {
|
||||
const { handleSearchFilterChange } = useEntitySelectSearch({
|
||||
relationPickerScopeId,
|
||||
@@ -62,10 +66,7 @@ export const SingleEntitySelectMenuItemsWithSearch = ({
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenuSearchInput onChange={handleSearchFilterChange} autoFocus />
|
||||
<DropdownMenuSeparator />
|
||||
const results = (
|
||||
<SingleEntitySelectMenuItems
|
||||
entitiesToSelect={entities.entitiesToSelect}
|
||||
loading={entities.loading}
|
||||
@@ -77,6 +78,7 @@ export const SingleEntitySelectMenuItemsWithSearch = ({
|
||||
}
|
||||
hotkeyScope={relationPickerScopeId}
|
||||
onCreate={onCreateWithInput}
|
||||
isFiltered={!!relationPickerSearchFilter}
|
||||
{...{
|
||||
EmptyIcon,
|
||||
emptyLabel,
|
||||
@@ -85,6 +87,24 @@ export const SingleEntitySelectMenuItemsWithSearch = ({
|
||||
showCreateButton,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{dropdownPlacement?.includes('end') && (
|
||||
<>
|
||||
{results}
|
||||
<DropdownMenuSeparator />
|
||||
</>
|
||||
)}
|
||||
<DropdownMenuSearchInput onChange={handleSearchFilterChange} autoFocus />
|
||||
{(dropdownPlacement?.includes('start') ||
|
||||
isUndefinedOrNull(dropdownPlacement)) && (
|
||||
<>
|
||||
<DropdownMenuSeparator />
|
||||
{results}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -26,7 +26,7 @@ describe('useLimitPerMetadataItem', () => {
|
||||
namePlural: 'namePlural',
|
||||
nameSingular: 'nameSingular',
|
||||
updatedAt: 'updatedAt',
|
||||
shouldSyncLabelAndName: false,
|
||||
isLabelSyncedWithName: false,
|
||||
fields: [],
|
||||
indexMetadatas: [],
|
||||
},
|
||||
|
||||
@@ -34,7 +34,7 @@ const objectData: ObjectMetadataItem[] = [
|
||||
labelSingular: 'labelSingular',
|
||||
namePlural: 'namePlural',
|
||||
nameSingular: 'nameSingular',
|
||||
shouldSyncLabelAndName: false,
|
||||
isLabelSyncedWithName: false,
|
||||
updatedAt: 'updatedAt',
|
||||
fields: [
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { H2Title } from 'twenty-ui';
|
||||
import { H2Title, Section } from 'twenty-ui';
|
||||
|
||||
import { BlocklistItem } from '@/accounts/types/BlocklistItem';
|
||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||
@@ -9,7 +9,6 @@ import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
|
||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||
import { SettingsAccountsBlocklistInput } from '@/settings/accounts/components/SettingsAccountsBlocklistInput';
|
||||
import { SettingsAccountsBlocklistTable } from '@/settings/accounts/components/SettingsAccountsBlocklistTable';
|
||||
import { Section } from '@/ui/layout/section/components/Section';
|
||||
|
||||
export const SettingsAccountsBlocklistSection = () => {
|
||||
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
||||
|
||||
@@ -2,7 +2,7 @@ import { ConnectedAccount } from '@/accounts/types/ConnectedAccount';
|
||||
import { SettingsAccountsRowDropdownMenu } from '@/settings/accounts/components/SettingsAccountsRowDropdownMenu';
|
||||
import { SyncStatus } from '@/settings/accounts/constants/SyncStatus';
|
||||
import { computeSyncStatus } from '@/settings/accounts/utils/computeSyncStatus';
|
||||
import { Status } from '@/ui/display/status/components/Status';
|
||||
import { Status } from 'twenty-ui';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const StyledRowRightContainer = styled.div`
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { H2Title, Toggle, Card } from 'twenty-ui';
|
||||
import { Card, H2Title, Section, Toggle } from 'twenty-ui';
|
||||
|
||||
import {
|
||||
MessageChannel,
|
||||
@@ -10,7 +10,6 @@ import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||
import { SettingsAccountsMessageAutoCreationCard } from '@/settings/accounts/components/SettingsAccountsMessageAutoCreationCard';
|
||||
import { SettingsAccountsMessageVisibilityCard } from '@/settings/accounts/components/SettingsAccountsMessageVisibilityCard';
|
||||
import { SettingsOptionCardContent } from '@/settings/components/SettingsOptionCardContent';
|
||||
import { Section } from '@/ui/layout/section/components/Section';
|
||||
import { MessageChannelVisibility } from '~/generated-metadata/graphql';
|
||||
|
||||
type SettingsAccountsMessageChannelDetailsProps = {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
import { Radio } from '@/ui/input/components/Radio';
|
||||
import { Card, CardContent } from 'twenty-ui';
|
||||
import { Card, CardContent, Radio } from 'twenty-ui';
|
||||
|
||||
type SettingsAccountsRadioSettingsCardProps<Option extends { value: string }> =
|
||||
{
|
||||
|
||||
@@ -4,13 +4,13 @@ import {
|
||||
IconCalendarEvent,
|
||||
IconMailCog,
|
||||
MOBILE_VIEWPORT,
|
||||
Section,
|
||||
UndecoratedLink,
|
||||
} from 'twenty-ui';
|
||||
|
||||
import { SettingsCard } from '@/settings/components/SettingsCard';
|
||||
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { Section } from '@/ui/layout/section/components/Section';
|
||||
import { useTheme } from '@emotion/react';
|
||||
|
||||
const StyledCardsContainer = styled.div`
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { H2Title } from 'twenty-ui';
|
||||
import { H2Title, Section } from 'twenty-ui';
|
||||
|
||||
import { SettingsAccountsListEmptyStateCard } from '@/settings/accounts/components/SettingsAccountsListEmptyStateCard';
|
||||
import { Section } from '@/ui/layout/section/components/Section';
|
||||
|
||||
export const SettingsNewAccountSection = () => {
|
||||
return (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
||||
import {
|
||||
CalendarChannelVisibility,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { Radio } from '@/ui/input/components/Radio';
|
||||
import { IconComponent, CardContent } from 'twenty-ui';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { CardContent, IconComponent, Radio } from 'twenty-ui';
|
||||
|
||||
const StyledRadioCardContent = styled(CardContent)`
|
||||
display: flex;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import {
|
||||
// eslint-disable-next-line prettier/prettier
|
||||
SettingsDataModelFieldPreview,
|
||||
SettingsDataModelFieldPreviewProps,
|
||||
} from '@/settings/data-model/fields/preview/components/SettingsDataModelFieldPreview';
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useIcons } from 'twenty-ui';
|
||||
import { Checkbox, useIcons } from 'twenty-ui';
|
||||
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { Checkbox } from '@/ui/input/components/Checkbox';
|
||||
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { H2Title } from 'twenty-ui';
|
||||
import { H2Title, Section } from 'twenty-ui';
|
||||
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { Section } from '@/ui/layout/section/components/Section';
|
||||
import { Table } from '@/ui/layout/table/components/Table';
|
||||
import { TableBody } from '@/ui/layout/table/components/TableBody';
|
||||
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
||||
|
||||
@@ -36,7 +36,7 @@ export const settingsDataModelObjectAboutFormSchema = objectMetadataItemSchema
|
||||
.pick({
|
||||
nameSingular: true,
|
||||
namePlural: true,
|
||||
shouldSyncLabelAndName: true,
|
||||
isLabelSyncedWithName: true,
|
||||
})
|
||||
.partial(),
|
||||
);
|
||||
@@ -102,6 +102,8 @@ const StyledLabel = styled.span`
|
||||
|
||||
const infoCircleElementId = 'info-circle-id';
|
||||
|
||||
export const IS_LABEL_SYNCED_WITH_NAME_LABEL = 'isLabelSyncedWithName';
|
||||
|
||||
export const SettingsDataModelObjectAboutForm = ({
|
||||
disabled,
|
||||
disableNameEdit,
|
||||
@@ -115,10 +117,10 @@ export const SettingsDataModelObjectAboutForm = ({
|
||||
isAdvancedModeEnabled,
|
||||
);
|
||||
|
||||
const shouldSyncLabelAndName = watch('shouldSyncLabelAndName');
|
||||
const isLabelSyncedWithName = watch(IS_LABEL_SYNCED_WITH_NAME_LABEL);
|
||||
const labelSingular = watch('labelSingular');
|
||||
const labelPlural = watch('labelPlural');
|
||||
const apiNameTooltipText = shouldSyncLabelAndName
|
||||
const apiNameTooltipText = isLabelSyncedWithName
|
||||
? 'Deactivate "Synchronize Objects Labels and API Names" to set a custom API name'
|
||||
: 'Input must be in camel case and cannot start with a number';
|
||||
|
||||
@@ -129,7 +131,7 @@ export const SettingsDataModelObjectAboutForm = ({
|
||||
setValue('labelPlural', newLabelPluralValue, {
|
||||
shouldDirty: isDefined(labelSingular) ? true : false,
|
||||
});
|
||||
if (shouldSyncLabelAndName === true) {
|
||||
if (isLabelSyncedWithName === true) {
|
||||
fillNamePluralFromLabelPlural(newLabelPluralValue);
|
||||
}
|
||||
};
|
||||
@@ -182,7 +184,7 @@ export const SettingsDataModelObjectAboutForm = ({
|
||||
onChange={(value) => {
|
||||
onChange(value);
|
||||
fillLabelPlural(value);
|
||||
if (shouldSyncLabelAndName === true) {
|
||||
if (isLabelSyncedWithName === true) {
|
||||
fillNameSingularFromLabelSingular(value);
|
||||
}
|
||||
}}
|
||||
@@ -204,7 +206,7 @@ export const SettingsDataModelObjectAboutForm = ({
|
||||
value={value}
|
||||
onChange={(value) => {
|
||||
onChange(value);
|
||||
if (shouldSyncLabelAndName === true) {
|
||||
if (isLabelSyncedWithName === true) {
|
||||
fillNamePluralFromLabelPlural(value);
|
||||
}
|
||||
}}
|
||||
@@ -251,7 +253,7 @@ export const SettingsDataModelObjectAboutForm = ({
|
||||
placeholder: 'listing',
|
||||
defaultValue: objectMetadataItem?.nameSingular,
|
||||
disabled:
|
||||
disabled || disableNameEdit || shouldSyncLabelAndName,
|
||||
disabled || disableNameEdit || isLabelSyncedWithName,
|
||||
tooltip: apiNameTooltipText,
|
||||
},
|
||||
{
|
||||
@@ -260,7 +262,7 @@ export const SettingsDataModelObjectAboutForm = ({
|
||||
placeholder: 'listings',
|
||||
defaultValue: objectMetadataItem?.namePlural,
|
||||
disabled:
|
||||
disabled || disableNameEdit || shouldSyncLabelAndName,
|
||||
disabled || disableNameEdit || isLabelSyncedWithName,
|
||||
tooltip: apiNameTooltipText,
|
||||
},
|
||||
].map(
|
||||
@@ -318,10 +320,10 @@ export const SettingsDataModelObjectAboutForm = ({
|
||||
),
|
||||
)}
|
||||
<Controller
|
||||
name="shouldSyncLabelAndName"
|
||||
name={IS_LABEL_SYNCED_WITH_NAME_LABEL}
|
||||
control={control}
|
||||
defaultValue={
|
||||
objectMetadataItem?.shouldSyncLabelAndName ?? true
|
||||
objectMetadataItem?.isLabelSyncedWithName ?? true
|
||||
}
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<SyncObjectLabelAndNameToggle
|
||||
|
||||
@@ -14,7 +14,7 @@ describe('settingsCreateObjectInputSchema', () => {
|
||||
labelSingular: 'Label ',
|
||||
namePlural: 'namePlural',
|
||||
nameSingular: 'nameSingular',
|
||||
shouldSyncLabelAndName: false,
|
||||
isLabelSyncedWithName: false,
|
||||
};
|
||||
|
||||
// When
|
||||
@@ -29,7 +29,7 @@ describe('settingsCreateObjectInputSchema', () => {
|
||||
labelSingular: 'Label',
|
||||
namePlural: 'namePlural',
|
||||
nameSingular: 'nameSingular',
|
||||
shouldSyncLabelAndName: false,
|
||||
isLabelSyncedWithName: false,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -12,6 +12,6 @@ export const settingsCreateObjectInputSchema =
|
||||
namePlural:
|
||||
values.namePlural ??
|
||||
computeMetadataNameFromLabelOrThrow(values.labelPlural),
|
||||
shouldSyncLabelAndName: values.shouldSyncLabelAndName ?? true,
|
||||
isLabelSyncedWithName: values.isLabelSyncedWithName ?? true,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
import { css } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Button, IconArrowUpRight, IconBolt, IconPlus, Pill } from 'twenty-ui';
|
||||
import {
|
||||
Button,
|
||||
IconArrowUpRight,
|
||||
IconBolt,
|
||||
IconPlus,
|
||||
Pill,
|
||||
Status,
|
||||
} from 'twenty-ui';
|
||||
|
||||
import { SettingsIntegration } from '@/settings/integrations/types/SettingsIntegration';
|
||||
import { Status } from '@/ui/display/status/components/Status';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
interface SettingsIntegrationComponentProps {
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
import styled from '@emotion/styled';
|
||||
import { H2Title } from 'twenty-ui';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { H2Title, Section } from 'twenty-ui';
|
||||
|
||||
import { SettingsIntegrationComponent } from '@/settings/integrations/components/SettingsIntegrationComponent';
|
||||
import { SettingsIntegrationCategory } from '@/settings/integrations/types/SettingsIntegrationCategory';
|
||||
import { Section } from '@/ui/layout/section/components/Section';
|
||||
|
||||
interface SettingsIntegrationGroupProps {
|
||||
integrationGroup: SettingsIntegrationCategory;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useGetDatabaseConnectionTables } from '@/databases/hooks/useGetDatabaseConnectionTables';
|
||||
import { Status } from '@/ui/display/status/components/Status';
|
||||
import { Status } from 'twenty-ui';
|
||||
import { RemoteTableStatus } from '~/generated-metadata/graphql';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
import { SettingsIntegration } from '@/settings/integrations/types/SettingsIntegration';
|
||||
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { Info } from '@/ui/display/info/components/Info';
|
||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
@@ -18,7 +17,7 @@ import { Section } from '@react-email/components';
|
||||
import pick from 'lodash.pick';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Breadcrumb, H2Title } from 'twenty-ui';
|
||||
import { Breadcrumb, H2Title, Info } from 'twenty-ui';
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
RemoteServer,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { WatchQueryFetchPolicy } from '@apollo/client';
|
||||
import { useEffect } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { WatchQueryFetchPolicy } from '@apollo/client';
|
||||
|
||||
import { useGetDatabaseConnection } from '@/databases/hooks/useGetDatabaseConnection';
|
||||
import { useGetDatabaseConnectionTables } from '@/databases/hooks/useGetDatabaseConnectionTables';
|
||||
|
||||
@@ -6,11 +6,10 @@ import { SettingsSSOOIDCForm } from '@/settings/security/components/SettingsSSOO
|
||||
import { SettingsSSOSAMLForm } from '@/settings/security/components/SettingsSSOSAMLForm';
|
||||
import { SettingSecurityNewSSOIdentityFormValues } from '@/settings/security/types/SSOIdentityProvider';
|
||||
import { TextInput } from '@/ui/input/components/TextInput';
|
||||
import { Section } from '@/ui/layout/section/components/Section';
|
||||
import styled from '@emotion/styled';
|
||||
import { ReactElement } from 'react';
|
||||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
import { H2Title, IconComponent, IconKey } from 'twenty-ui';
|
||||
import { H2Title, IconComponent, IconKey, Section } from 'twenty-ui';
|
||||
import { IdpType } from '~/generated/graphql';
|
||||
|
||||
const StyledInputsContainer = styled.div`
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { SettingsSecuritySSORowDropdownMenu } from '@/settings/security/components/SettingsSecuritySSORowDropdownMenu';
|
||||
import { SSOIdentitiesProvidersState } from '@/settings/security/states/SSOIdentitiesProviders.state';
|
||||
import { getColorBySSOIdentityProviderStatus } from '@/settings/security/utils/getColorBySSOIdentityProviderStatus';
|
||||
import { Status } from '@/ui/display/status/components/Status';
|
||||
import { Status } from 'twenty-ui';
|
||||
import styled from '@emotion/styled';
|
||||
import { UnwrapRecoilValue } from 'recoil';
|
||||
|
||||
|
||||
@@ -3,11 +3,10 @@
|
||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { TextInput } from '@/ui/input/components/TextInput';
|
||||
import { Section } from '@/ui/layout/section/components/Section';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
import { Button, H2Title, IconCopy } from 'twenty-ui';
|
||||
import { Button, H2Title, IconCopy, Section } from 'twenty-ui';
|
||||
|
||||
const StyledInputsContainer = styled.div`
|
||||
display: flex;
|
||||
|
||||
@@ -5,7 +5,6 @@ import { parseSAMLMetadataFromXMLFile } from '@/settings/security/utils/parseSAM
|
||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { TextInput } from '@/ui/input/components/TextInput';
|
||||
import { Section } from '@/ui/layout/section/components/Section';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { ChangeEvent, useRef } from 'react';
|
||||
@@ -17,6 +16,7 @@ import {
|
||||
IconCopy,
|
||||
IconDownload,
|
||||
IconUpload,
|
||||
Section,
|
||||
} from 'twenty-ui';
|
||||
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { SettingsServerlessFunctionCodeEditorContainer } from '@/settings/serverless-functions/components/SettingsServerlessFunctionCodeEditorContainer';
|
||||
import { useGetAvailablePackages } from '@/settings/serverless-functions/hooks/useGetAvailablePackages';
|
||||
import { CodeEditor } from '@/ui/input/code-editor/components/CodeEditor';
|
||||
import { EditorProps, Monaco } from '@monaco-editor/react';
|
||||
import dotenv from 'dotenv';
|
||||
import { editor, MarkerSeverity } from 'monaco-editor';
|
||||
import { AutoTypings } from 'monaco-editor-auto-typings';
|
||||
import { CodeEditor } from 'twenty-ui';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export type File = {
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { ServerlessFunctionNewFormValues } from '@/settings/serverless-functions/hooks/useServerlessFunctionUpdateFormState';
|
||||
import { TextArea } from '@/ui/input/components/TextArea';
|
||||
import { TextInput } from '@/ui/input/components/TextInput';
|
||||
import { Section } from '@/ui/layout/section/components/Section';
|
||||
import styled from '@emotion/styled';
|
||||
import { H2Title } from 'twenty-ui';
|
||||
import { H2Title, Section } from 'twenty-ui';
|
||||
|
||||
const StyledInputsContainer = styled.div`
|
||||
display: flex;
|
||||
|
||||
@@ -6,8 +6,6 @@ import { SETTINGS_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID } from '@/settings/s
|
||||
import { SettingsServerlessFunctionHotkeyScope } from '@/settings/serverless-functions/types/SettingsServerlessFunctionHotKeyScope';
|
||||
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { CoreEditorHeader } from '@/ui/input/code-editor/components/CodeEditorHeader';
|
||||
import { Section } from '@/ui/layout/section/components/Section';
|
||||
import { TabList } from '@/ui/layout/tab/components/TabList';
|
||||
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
@@ -17,10 +15,12 @@ import { useRecoilValue } from 'recoil';
|
||||
import { Key } from 'ts-key-enum';
|
||||
import {
|
||||
Button,
|
||||
CoreEditorHeader,
|
||||
H2Title,
|
||||
IconGitCommit,
|
||||
IconPlayerPlay,
|
||||
IconRestore,
|
||||
Section,
|
||||
} from 'twenty-ui';
|
||||
import { useHotkeyScopeOnMount } from '~/hooks/useHotkeyScopeOnMount';
|
||||
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
import { SettingsServerlessFunctionNewForm } from '@/settings/serverless-functions/components/SettingsServerlessFunctionNewForm';
|
||||
import { SettingsServerlessFunctionTabEnvironmentVariablesSection } from '@/settings/serverless-functions/components/tabs/SettingsServerlessFunctionTabEnvironmentVariablesSection';
|
||||
import { useDeleteOneServerlessFunction } from '@/settings/serverless-functions/hooks/useDeleteOneServerlessFunction';
|
||||
import { ServerlessFunctionFormValues } from '@/settings/serverless-functions/hooks/useServerlessFunctionUpdateFormState';
|
||||
import { SettingsServerlessFunctionHotkeyScope } from '@/settings/serverless-functions/types/SettingsServerlessFunctionHotKeyScope';
|
||||
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
|
||||
import { Section } from '@/ui/layout/section/components/Section';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Key } from 'ts-key-enum';
|
||||
import { Button, H2Title } from 'twenty-ui';
|
||||
import { Button, H2Title, Section } from 'twenty-ui';
|
||||
import { useHotkeyScopeOnMount } from '~/hooks/useHotkeyScopeOnMount';
|
||||
import { SettingsServerlessFunctionTabEnvironmentVariablesSection } from '@/settings/serverless-functions/components/tabs/SettingsServerlessFunctionTabEnvironmentVariablesSection';
|
||||
|
||||
export const SettingsServerlessFunctionSettingsTab = ({
|
||||
formValues,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { SettingsServerlessFunctionTabEnvironmentVariableTableRow } from '@/settings/serverless-functions/components/tabs/SettingsServerlessFunctionTabEnvironmentVariableTableRow';
|
||||
import { ServerlessFunctionFormValues } from '@/settings/serverless-functions/hooks/useServerlessFunctionUpdateFormState';
|
||||
import { TextInput } from '@/ui/input/components/TextInput';
|
||||
import { Section } from '@/ui/layout/section/components/Section';
|
||||
import { Table } from '@/ui/layout/table/components/Table';
|
||||
import { TableBody } from '@/ui/layout/table/components/TableBody';
|
||||
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
||||
@@ -15,6 +14,7 @@ import {
|
||||
IconPlus,
|
||||
IconSearch,
|
||||
MOBILE_VIEWPORT,
|
||||
Section,
|
||||
} from 'twenty-ui';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { Section } from '@/ui/layout/section/components/Section';
|
||||
import { Button, H2Title, IconPlayerPlay } from 'twenty-ui';
|
||||
import {
|
||||
Button,
|
||||
CodeEditor,
|
||||
CoreEditorHeader,
|
||||
H2Title,
|
||||
IconPlayerPlay,
|
||||
Section,
|
||||
} from 'twenty-ui';
|
||||
|
||||
import { LightCopyIconButton } from '@/object-record/record-field/components/LightCopyIconButton';
|
||||
import { SettingsServerlessFunctionCodeEditorContainer } from '@/settings/serverless-functions/components/SettingsServerlessFunctionCodeEditorContainer';
|
||||
@@ -10,8 +16,6 @@ import { settingsServerlessFunctionOutputState } from '@/settings/serverless-fun
|
||||
import { SettingsServerlessFunctionHotkeyScope } from '@/settings/serverless-functions/types/SettingsServerlessFunctionHotKeyScope';
|
||||
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { CodeEditor } from '@/ui/input/code-editor/components/CodeEditor';
|
||||
import { CoreEditorHeader } from '@/ui/input/code-editor/components/CodeEditorHeader';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import styled from '@emotion/styled';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { Column, FormatterProps, useRowSelection } from 'react-data-grid';
|
||||
|
||||
import { ImportedRow } from '@/spreadsheet-import/types';
|
||||
import { Radio } from '@/ui/input/components/Radio';
|
||||
import { Radio } from 'twenty-ui';
|
||||
|
||||
const SELECT_COLUMN_KEY = 'select-row';
|
||||
|
||||
|
||||
@@ -8,8 +8,7 @@ import { SpreadsheetImportStep } from '@/spreadsheet-import/steps/types/Spreadsh
|
||||
import { SpreadsheetImportStepType } from '@/spreadsheet-import/steps/types/SpreadsheetImportStepType';
|
||||
import { exceedsMaxRecords } from '@/spreadsheet-import/utils/exceedsMaxRecords';
|
||||
import { mapWorkbook } from '@/spreadsheet-import/utils/mapWorkbook';
|
||||
import { Radio } from '@/ui/input/components/Radio';
|
||||
import { RadioGroup } from '@/ui/input/components/RadioGroup';
|
||||
import { Radio, RadioGroup } from 'twenty-ui';
|
||||
|
||||
import { Modal } from '@/ui/layout/modal/components/Modal';
|
||||
import { WorkBook } from 'xlsx-ugnis';
|
||||
|
||||
@@ -2,11 +2,10 @@ import styled from '@emotion/styled';
|
||||
// @ts-expect-error // Todo: remove usage of react-data-grid
|
||||
import { Column, useRowSelection } from 'react-data-grid';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { AppTooltip, Toggle } from 'twenty-ui';
|
||||
import { AppTooltip, Checkbox, CheckboxVariant, Toggle } from 'twenty-ui';
|
||||
|
||||
import { MatchColumnSelect } from '@/spreadsheet-import/components/MatchColumnSelect';
|
||||
import { Fields, ImportedStructuredRow } from '@/spreadsheet-import/types';
|
||||
import { Checkbox, CheckboxVariant } from '@/ui/input/components/Checkbox';
|
||||
import { TextInput } from '@/ui/input/components/TextInput';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export enum PageHotkeyScope {
|
||||
Settings = 'settings',
|
||||
CreateWokspace = 'create-workspace',
|
||||
CreateWorkspace = 'create-workspace',
|
||||
SignInUp = 'sign-in-up',
|
||||
CreateProfile = 'create-profile',
|
||||
InviteTeam = 'invite-team',
|
||||
|
||||
@@ -40,7 +40,7 @@ export const CurrencyDisplay = ({ currencyValue }: CurrencyDisplayProps) => {
|
||||
|
||||
return (
|
||||
<StyledEllipsisDisplay>
|
||||
{isDefined(CurrencyIcon) && (
|
||||
{isDefined(CurrencyIcon) && amountToDisplay !== null && (
|
||||
<>
|
||||
<CurrencyIcon
|
||||
color={theme.font.color.primary}
|
||||
|
||||
@@ -19,13 +19,13 @@ const StyledAddressContainer = styled.div`
|
||||
|
||||
padding: 4px 8px;
|
||||
|
||||
width: 100%;
|
||||
min-width: 260px;
|
||||
width: 344px;
|
||||
> div {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
@media (max-width: ${MOBILE_VIEWPORT}px) {
|
||||
width: auto;
|
||||
min-width: 100px;
|
||||
max-width: 200px;
|
||||
overflow: hidden;
|
||||
@@ -36,7 +36,8 @@ const StyledAddressContainer = styled.div`
|
||||
`;
|
||||
|
||||
const StyledHalfRowContainer = styled.div`
|
||||
display: flex;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 8px;
|
||||
|
||||
@media (max-width: ${MOBILE_VIEWPORT}px) {
|
||||
|
||||
@@ -31,6 +31,7 @@ export type SelectProps<Value extends string | number | null> = {
|
||||
disableBlur?: boolean;
|
||||
dropdownId: string;
|
||||
dropdownWidth?: `${string}px` | 'auto' | number;
|
||||
dropdownWidthAuto?: boolean;
|
||||
emptyOption?: SelectOption<Value>;
|
||||
fullWidth?: boolean;
|
||||
label?: string;
|
||||
@@ -60,6 +61,7 @@ export const Select = <Value extends string | number | null>({
|
||||
disableBlur = false,
|
||||
dropdownId,
|
||||
dropdownWidth = 176,
|
||||
dropdownWidthAuto = false,
|
||||
emptyOption,
|
||||
fullWidth,
|
||||
label,
|
||||
@@ -94,6 +96,11 @@ export const Select = <Value extends string | number | null>({
|
||||
|
||||
const { closeDropdown } = useDropdown(dropdownId);
|
||||
|
||||
const dropDownMenuWidth =
|
||||
dropdownWidthAuto && selectContainerRef.current?.clientWidth
|
||||
? selectContainerRef.current?.clientWidth
|
||||
: dropdownWidth;
|
||||
|
||||
return (
|
||||
<StyledContainer
|
||||
className={className}
|
||||
@@ -111,7 +118,7 @@ export const Select = <Value extends string | number | null>({
|
||||
) : (
|
||||
<Dropdown
|
||||
dropdownId={dropdownId}
|
||||
dropdownMenuWidth={dropdownWidth}
|
||||
dropdownMenuWidth={dropDownMenuWidth}
|
||||
dropdownPlacement="bottom-start"
|
||||
clickableComponent={
|
||||
<SelectControl
|
||||
|
||||
@@ -1,28 +1,31 @@
|
||||
import { SelectOption } from '@/ui/input/components/Select';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { IconChevronDown } from 'twenty-ui';
|
||||
import {
|
||||
IconChevronDown,
|
||||
isDefined,
|
||||
OverflowingTextWithTooltip,
|
||||
} from 'twenty-ui';
|
||||
|
||||
const StyledControlContainer = styled.div<{ disabled?: boolean }>`
|
||||
const StyledControlContainer = styled.div<{
|
||||
disabled?: boolean;
|
||||
hasIcon: boolean;
|
||||
}>`
|
||||
display: grid;
|
||||
grid-template-columns: ${({ hasIcon }) =>
|
||||
hasIcon ? 'auto 1fr auto' : '1fr auto'};
|
||||
align-items: center;
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
box-sizing: border-box;
|
||||
height: ${({ theme }) => theme.spacing(8)};
|
||||
max-width: 100%;
|
||||
padding: 0 ${({ theme }) => theme.spacing(2)};
|
||||
background-color: ${({ theme }) => theme.background.transparent.lighter};
|
||||
border: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
box-sizing: border-box;
|
||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
color: ${({ disabled, theme }) =>
|
||||
disabled ? theme.font.color.tertiary : theme.font.color.primary};
|
||||
cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')};
|
||||
display: flex;
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
height: ${({ theme }) => theme.spacing(8)};
|
||||
justify-content: space-between;
|
||||
padding: 0 ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledControlLabel = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
const StyledIconChevronDown = styled(IconChevronDown)<{
|
||||
@@ -44,19 +47,18 @@ export const SelectControl = ({
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<StyledControlContainer disabled={isDisabled}>
|
||||
<StyledControlLabel>
|
||||
{!!selectedOption?.Icon && (
|
||||
<StyledControlContainer
|
||||
disabled={isDisabled}
|
||||
hasIcon={isDefined(selectedOption.Icon)}
|
||||
>
|
||||
{isDefined(selectedOption.Icon) ? (
|
||||
<selectedOption.Icon
|
||||
color={
|
||||
isDisabled ? theme.font.color.light : theme.font.color.primary
|
||||
}
|
||||
color={isDisabled ? theme.font.color.light : theme.font.color.primary}
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
/>
|
||||
)}
|
||||
{selectedOption?.label}
|
||||
</StyledControlLabel>
|
||||
) : null}
|
||||
<OverflowingTextWithTooltip text={selectedOption.label} />
|
||||
<StyledIconChevronDown disabled={isDisabled} size={theme.icon.size.md} />
|
||||
</StyledControlContainer>
|
||||
);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user