diff --git a/package.json b/package.json index a3069e9ca63..b2b6cde2534 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "googleapis": "105", "hex-rgb": "^5.0.0", "immer": "^10.1.1", + "jotai": "^2.17.1", "libphonenumber-js": "^1.10.26", "lodash.camelcase": "^4.3.0", "lodash.chunk": "^4.2.0", diff --git a/packages/twenty-front/package.json b/packages/twenty-front/package.json index 854ff50417d..6972ec882d8 100644 --- a/packages/twenty-front/package.json +++ b/packages/twenty-front/package.json @@ -84,6 +84,7 @@ "graphql": "16.8.1", "graphql-sse": "^2.5.4", "input-otp": "^1.4.2", + "jotai": "^2.17.1", "js-cookie": "^3.0.5", "json-2-csv": "^5.4.0", "json-logic-js": "^2.0.5", diff --git a/packages/twenty-front/src/localization/states/dateLocaleStateV2.ts b/packages/twenty-front/src/localization/states/dateLocaleStateV2.ts new file mode 100644 index 00000000000..778c14e4b32 --- /dev/null +++ b/packages/twenty-front/src/localization/states/dateLocaleStateV2.ts @@ -0,0 +1,18 @@ +import { type Locale } from 'date-fns'; +import { enUS } from 'date-fns/locale'; +import { type APP_LOCALES } from 'twenty-shared/translations'; + +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; + +type DateLocaleState = { + locale?: keyof typeof APP_LOCALES; + localeCatalog: Locale; +}; + +export const dateLocaleStateV2 = createStateV2({ + key: 'dateLocaleStateV2', + defaultValue: { + locale: undefined, + localeCatalog: enUS, + }, +}); diff --git a/packages/twenty-front/src/modules/app/components/App.tsx b/packages/twenty-front/src/modules/app/components/App.tsx index 2974724317f..f43239740a9 100644 --- a/packages/twenty-front/src/modules/app/components/App.tsx +++ b/packages/twenty-front/src/modules/app/components/App.tsx @@ -6,8 +6,10 @@ import { AppRootErrorFallback } from '@/error-handler/components/AppRootErrorFal import { ExceptionHandlerProvider } from '@/error-handler/components/ExceptionHandlerProvider'; import { SnackBarComponentInstanceContext } from '@/ui/feedback/snack-bar-manager/contexts/SnackBarComponentInstanceContext'; import { ClickOutsideListenerContext } from '@/ui/utilities/pointer-event/contexts/ClickOutsideListenerContext'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { i18n } from '@lingui/core'; import { I18nProvider } from '@lingui/react'; +import { Provider as JotaiProvider } from 'jotai'; import { HelmetProvider } from 'react-helmet-async'; import { RecoilRoot } from 'recoil'; import { IconsProvider } from 'twenty-ui/display'; @@ -17,31 +19,33 @@ initialI18nActivate(); export const App = () => { return ( - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + ); }; diff --git a/packages/twenty-front/src/modules/client-config/hooks/useClientConfig.ts b/packages/twenty-front/src/modules/client-config/hooks/useClientConfig.ts index 65f01bc83ba..d188baebde7 100644 --- a/packages/twenty-front/src/modules/client-config/hooks/useClientConfig.ts +++ b/packages/twenty-front/src/modules/client-config/hooks/useClientConfig.ts @@ -26,8 +26,10 @@ import { sentryConfigState } from '@/client-config/states/sentryConfigState'; import { supportChatState } from '@/client-config/states/supportChatState'; import { type ClientConfig } from '@/client-config/types/ClientConfig'; import { domainConfigurationState } from '@/domain-manager/states/domainConfigurationState'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { useCallback } from 'react'; import { useRecoilState, useSetRecoilState } from 'recoil'; +import { isAttachmentPreviewEnabledStateV2 } from '@/client-config/states/isAttachmentPreviewEnabledStateV2'; import { clientConfigApiStatusState } from '@/client-config/states/clientConfigApiStatusState'; import { getClientConfig } from '@/client-config/utils/getClientConfig'; import { allowRequestsToTwentyIconsState } from '@/client-config/states/allowRequestsToTwentyIcons'; @@ -188,6 +190,10 @@ export const useClientConfig = (): UseClientConfigResult => { setGoogleMessagingEnabled(clientConfig?.isGoogleMessagingEnabled); setGoogleCalendarEnabled(clientConfig?.isGoogleCalendarEnabled); setIsAttachmentPreviewEnabled(clientConfig?.isAttachmentPreviewEnabled); + jotaiStore.set( + isAttachmentPreviewEnabledStateV2.atom, + clientConfig?.isAttachmentPreviewEnabled, + ); setIsConfigVariablesInDbEnabled( clientConfig?.isConfigVariablesInDbEnabled, ); diff --git a/packages/twenty-front/src/modules/client-config/states/isAttachmentPreviewEnabledStateV2.ts b/packages/twenty-front/src/modules/client-config/states/isAttachmentPreviewEnabledStateV2.ts new file mode 100644 index 00000000000..9432c3f41fc --- /dev/null +++ b/packages/twenty-front/src/modules/client-config/states/isAttachmentPreviewEnabledStateV2.ts @@ -0,0 +1,6 @@ +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; + +export const isAttachmentPreviewEnabledStateV2 = createStateV2({ + key: 'isAttachmentPreviewEnabledStateV2', + defaultValue: false, +}); diff --git a/packages/twenty-front/src/modules/object-record/components/RecordChip.tsx b/packages/twenty-front/src/modules/object-record/components/RecordChip.tsx index f571fd83eb5..9c40ba12938 100644 --- a/packages/twenty-front/src/modules/object-record/components/RecordChip.tsx +++ b/packages/twenty-front/src/modules/object-record/components/RecordChip.tsx @@ -2,13 +2,13 @@ import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordIn import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { getLinkToShowPage } from '@/object-metadata/utils/getLinkToShowPage'; import { useRecordChipData } from '@/object-record/hooks/useRecordChipData'; -import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState'; +import { recordIndexOpenRecordInStateV2 } from '@/object-record/record-index/states/recordIndexOpenRecordInStateV2'; import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; import { canOpenObjectInSidePanel } from '@/object-record/utils/canOpenObjectInSidePanel'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType'; import { t } from '@lingui/core/macro'; import { type MouseEvent } from 'react'; -import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { AvatarChip, @@ -55,7 +55,9 @@ export const RecordChip = ({ const { openRecordInCommandMenu } = useOpenRecordInCommandMenu(); - const recordIndexOpenRecordIn = useRecoilValue(recordIndexOpenRecordInState); + const recordIndexOpenRecordIn = useRecoilValueV2( + recordIndexOpenRecordInStateV2, + ); const canOpenInSidePanel = canOpenObjectInSidePanel(objectNameSingular); const isSidePanelViewOpenRecordInType = diff --git a/packages/twenty-front/src/modules/object-record/object-options-dropdown/hooks/useUpdateObjectViewOptions.ts b/packages/twenty-front/src/modules/object-record/object-options-dropdown/hooks/useUpdateObjectViewOptions.ts index 5c0d5fa4f06..0bd4575a821 100644 --- a/packages/twenty-front/src/modules/object-record/object-options-dropdown/hooks/useUpdateObjectViewOptions.ts +++ b/packages/twenty-front/src/modules/object-record/object-options-dropdown/hooks/useUpdateObjectViewOptions.ts @@ -1,5 +1,7 @@ import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState'; +import { recordIndexOpenRecordInStateV2 } from '@/object-record/record-index/states/recordIndexOpenRecordInStateV2'; import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { useUpdateCurrentView } from '@/views/hooks/useUpdateCurrentView'; import { type GraphQLView } from '@/views/types/GraphQLView'; import { type ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType'; @@ -27,6 +29,7 @@ export const useUpdateObjectViewOptions = () => { (openRecordIn: ViewOpenRecordInType, view: GraphQLView | undefined) => { if (!view) return; setRecordIndexOpenRecordIn(openRecordIn); + jotaiStore.set(recordIndexOpenRecordInStateV2.atom, openRecordIn); updateCurrentView({ openRecordIn, }); diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/FilesFieldDisplay.tsx b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/FilesFieldDisplay.tsx index 634bd4a75b4..dfc1d0e8dda 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/FilesFieldDisplay.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/FilesFieldDisplay.tsx @@ -1,20 +1,18 @@ import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext'; import { useFilesFieldDisplay } from '@/object-record/record-field/ui/meta-types/hooks/useFilesFieldDisplay'; -import { filesFieldUploadState } from '@/object-record/record-field/ui/states/filesFieldUploadState'; +import { filesFieldUploadStateV2 } from '@/object-record/record-field/ui/states/filesFieldUploadStateV2'; +import { useFamilyRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilyRecoilValueV2'; import { FilesDisplay } from '@/ui/field/display/components/FilesDisplay'; import { useContext } from 'react'; -import { useRecoilValue } from 'recoil'; export const FilesFieldDisplay = () => { const { recordId, fieldDefinition } = useContext(FieldContext); const { fieldValue, disableChipClick } = useFilesFieldDisplay(); - const uploadState = useRecoilValue( - filesFieldUploadState({ - recordId, - fieldName: fieldDefinition.metadata.fieldName, - }), - ); + const uploadState = useFamilyRecoilValueV2(filesFieldUploadStateV2, { + recordId, + fieldName: fieldDefinition.metadata.fieldName, + }); const isUploadWindowOpen = uploadState === 'UPLOAD_WINDOW_OPEN'; const isFileUploading = uploadState === 'UPLOADING_FILE'; diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useActorFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useActorFieldDisplay.ts index 508327f3b2f..0b112e967bb 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useActorFieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useActorFieldDisplay.ts @@ -3,7 +3,7 @@ import { useContext } from 'react'; import { type FieldActorValue } from '@/object-record/record-field/ui/types/FieldMetadata'; import { AuthContext } from '@/auth/contexts/AuthContext'; -import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue'; +import { useRecordFieldValueV2 } from '@/object-record/record-store/hooks/useRecordFieldValueV2'; import { isDefined } from 'twenty-shared/utils'; import { type WorkspaceMember } from '~/generated-metadata/graphql'; import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext'; @@ -21,7 +21,7 @@ export const useActorFieldDisplay = (): ActorFieldDisplayValue | undefined => { const fieldName = fieldDefinition.metadata.fieldName; - const fieldValue = useRecordFieldValue( + const fieldValue = useRecordFieldValueV2( recordId, fieldName, fieldDefinition, diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useAddressFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useAddressFieldDisplay.ts index 81414bfc98d..27522a3d4f4 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useAddressFieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useAddressFieldDisplay.ts @@ -1,6 +1,6 @@ import { useContext } from 'react'; -import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue'; +import { useRecordFieldValueV2 } from '@/object-record/record-store/hooks/useRecordFieldValueV2'; import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext'; import { type FieldAddressValue } from '@/object-record/record-field/ui/types/FieldMetadata'; @@ -9,7 +9,7 @@ export const useAddressFieldDisplay = () => { const fieldName = fieldDefinition.metadata.fieldName; - const fieldValue = useRecordFieldValue( + const fieldValue = useRecordFieldValueV2( recordId, fieldName, fieldDefinition, diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useArrayFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useArrayFieldDisplay.ts index 0121ffa06cc..b51d8efe277 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useArrayFieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useArrayFieldDisplay.ts @@ -4,7 +4,7 @@ import { type FieldArrayMetadata, type FieldArrayValue, } from '@/object-record/record-field/ui/types/FieldMetadata'; -import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue'; +import { useRecordFieldValueV2 } from '@/object-record/record-store/hooks/useRecordFieldValueV2'; import { useContext } from 'react'; @@ -13,7 +13,7 @@ export const useArrayFieldDisplay = () => { const { fieldName } = fieldDefinition.metadata; - const fieldValue = useRecordFieldValue( + const fieldValue = useRecordFieldValueV2( recordId, fieldName, fieldDefinition, diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useBooleanFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useBooleanFieldDisplay.ts index bdf218a2875..07738a23278 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useBooleanFieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useBooleanFieldDisplay.ts @@ -1,6 +1,6 @@ import { useContext } from 'react'; -import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue'; +import { useRecordFieldValueV2 } from '@/object-record/record-store/hooks/useRecordFieldValueV2'; import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext'; export const useBooleanFieldDisplay = () => { @@ -8,7 +8,7 @@ export const useBooleanFieldDisplay = () => { const fieldName = fieldDefinition.metadata.fieldName; - const fieldValue = useRecordFieldValue( + const fieldValue = useRecordFieldValueV2( recordId, fieldName, fieldDefinition, diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useChipFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useChipFieldDisplay.ts index fd910a23896..9c96fd4d7e5 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useChipFieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useChipFieldDisplay.ts @@ -5,11 +5,11 @@ import { isFieldText } from '@/object-record/record-field/ui/types/guards/isFiel import { isFieldUuid } from '@/object-record/record-field/ui/types/guards/isFieldUuid'; import { isNonEmptyString } from '@sniptt/guards'; import { useContext } from 'react'; -import { useRecoilValue } from 'recoil'; import { isFieldActor } from '@/object-record/record-field/ui/types/guards/isFieldActor'; import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext'; -import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; +import { recordStoreFamilyStateV2 } from '@/object-record/record-store/states/recordStoreFamilyStateV2'; +import { useFamilyRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilyRecoilValueV2'; import { isDefined } from 'twenty-shared/utils'; import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext'; @@ -50,7 +50,10 @@ export const useChipFieldDisplay = () => { ? fieldDefinition.metadata.objectMetadataNameSingular : undefined; - const recordValue = useRecoilValue(recordStoreFamilyState(recordId)); + const recordValue = useFamilyRecoilValueV2( + recordStoreFamilyStateV2, + recordId, + ); if (!isNonEmptyString(objectNameSingular)) { throw new Error('Object metadata name singular is not a non-empty string'); diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useCurrencyFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useCurrencyFieldDisplay.ts index 628f4d5aef9..e542b45135c 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useCurrencyFieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useCurrencyFieldDisplay.ts @@ -2,7 +2,7 @@ import { useContext } from 'react'; import { assertFieldMetadata } from '@/object-record/record-field/ui/types/guards/assertFieldMetadata'; import { isFieldCurrency } from '@/object-record/record-field/ui/types/guards/isFieldCurrency'; -import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue'; +import { useRecordFieldValueV2 } from '@/object-record/record-store/hooks/useRecordFieldValueV2'; import { FieldMetadataType } from 'twenty-shared/types'; import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext'; import { type FieldCurrencyValue } from '@/object-record/record-field/ui/types/FieldMetadata'; @@ -18,7 +18,7 @@ export const useCurrencyFieldDisplay = () => { const fieldName = fieldDefinition.metadata.fieldName; - const fieldValue = useRecordFieldValue( + const fieldValue = useRecordFieldValueV2( recordId, fieldName, fieldDefinition, diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useDateFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useDateFieldDisplay.ts index 39d1b67a51c..19025034513 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useDateFieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useDateFieldDisplay.ts @@ -2,7 +2,7 @@ import { useContext } from 'react'; import { type FieldDefinition } from '@/object-record/record-field/ui/types/FieldDefinition'; import { type FieldDateMetadata } from '@/object-record/record-field/ui/types/FieldMetadata'; -import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue'; +import { useRecordFieldValueV2 } from '@/object-record/record-store/hooks/useRecordFieldValueV2'; import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext'; export const useDateFieldDisplay = () => { @@ -10,7 +10,7 @@ export const useDateFieldDisplay = () => { const fieldName = fieldDefinition.metadata.fieldName; - const fieldValue = useRecordFieldValue( + const fieldValue = useRecordFieldValueV2( recordId, fieldName, fieldDefinition, diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useDateTimeFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useDateTimeFieldDisplay.ts index 227f0452d82..8b822410489 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useDateTimeFieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useDateTimeFieldDisplay.ts @@ -2,7 +2,7 @@ import { useContext } from 'react'; import { type FieldDefinition } from '@/object-record/record-field/ui/types/FieldDefinition'; import { type FieldDateTimeMetadata } from '@/object-record/record-field/ui/types/FieldMetadata'; -import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue'; +import { useRecordFieldValueV2 } from '@/object-record/record-store/hooks/useRecordFieldValueV2'; import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext'; export const useDateTimeFieldDisplay = () => { @@ -10,7 +10,7 @@ export const useDateTimeFieldDisplay = () => { const fieldName = fieldDefinition.metadata.fieldName; - const fieldValue = useRecordFieldValue( + const fieldValue = useRecordFieldValueV2( recordId, fieldName, fieldDefinition, diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useEmailsFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useEmailsFieldDisplay.ts index ed711e91e0c..6efb39062ce 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useEmailsFieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useEmailsFieldDisplay.ts @@ -3,7 +3,7 @@ import { useContext } from 'react'; import { type FieldEmailsValue } from '@/object-record/record-field/ui/types/FieldMetadata'; import { assertFieldMetadata } from '@/object-record/record-field/ui/types/guards/assertFieldMetadata'; import { isFieldEmails } from '@/object-record/record-field/ui/types/guards/isFieldEmails'; -import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue'; +import { useRecordFieldValueV2 } from '@/object-record/record-store/hooks/useRecordFieldValueV2'; import { FieldMetadataType } from 'twenty-shared/types'; import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext'; @@ -14,7 +14,7 @@ export const useEmailsFieldDisplay = () => { const fieldName = fieldDefinition.metadata.fieldName; - const fieldValue = useRecordFieldValue( + const fieldValue = useRecordFieldValueV2( recordId, fieldName, fieldDefinition, diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useFilesFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useFilesFieldDisplay.ts index fac1d57a408..0cd3dacb480 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useFilesFieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useFilesFieldDisplay.ts @@ -4,7 +4,7 @@ import { type FieldFilesMetadata, type FieldFilesValue, } from '@/object-record/record-field/ui/types/FieldMetadata'; -import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue'; +import { useRecordFieldValueV2 } from '@/object-record/record-store/hooks/useRecordFieldValueV2'; import { useContext } from 'react'; @@ -14,7 +14,7 @@ export const useFilesFieldDisplay = () => { const { fieldName } = fieldDefinition.metadata; - const fieldValue = useRecordFieldValue( + const fieldValue = useRecordFieldValueV2( recordId, fieldName, fieldDefinition, diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useFullNameFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useFullNameFieldDisplay.ts index 75cef5e1f75..fd488a4915f 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useFullNameFieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useFullNameFieldDisplay.ts @@ -2,7 +2,7 @@ import { useContext } from 'react'; import { type FieldFullNameValue } from '@/object-record/record-field/ui/types/FieldMetadata'; -import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue'; +import { useRecordFieldValueV2 } from '@/object-record/record-store/hooks/useRecordFieldValueV2'; import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext'; export const useFullNameFieldDisplay = () => { @@ -10,7 +10,7 @@ export const useFullNameFieldDisplay = () => { const fieldName = fieldDefinition.metadata.fieldName; - const fieldValue = useRecordFieldValue( + const fieldValue = useRecordFieldValueV2( recordId, fieldName, fieldDefinition, diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useJsonFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useJsonFieldDisplay.ts index f571c4df263..6c9d41c6ceb 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useJsonFieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useJsonFieldDisplay.ts @@ -3,7 +3,7 @@ import { useContext } from 'react'; import { type FieldJsonValue } from '@/object-record/record-field/ui/types/FieldMetadata'; import { useFormattedJsonFieldValue } from '@/object-record/record-field/ui/meta-types/hooks/useFormattedJsonFieldValue'; -import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue'; +import { useRecordFieldValueV2 } from '@/object-record/record-store/hooks/useRecordFieldValueV2'; import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext'; export const useJsonFieldDisplay = () => { @@ -12,7 +12,7 @@ export const useJsonFieldDisplay = () => { const fieldName = fieldDefinition.metadata.fieldName; - const fieldValue = useRecordFieldValue( + const fieldValue = useRecordFieldValueV2( recordId, fieldName, fieldDefinition, diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useLinksFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useLinksFieldDisplay.ts index 2aec9dd92d1..12d68e2edfd 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useLinksFieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useLinksFieldDisplay.ts @@ -4,7 +4,7 @@ import { type FieldLinksValue } from '@/object-record/record-field/ui/types/Fiel import { assertFieldMetadata } from '@/object-record/record-field/ui/types/guards/assertFieldMetadata'; import { isFieldLinks } from '@/object-record/record-field/ui/types/guards/isFieldLinks'; -import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue'; +import { useRecordFieldValueV2 } from '@/object-record/record-store/hooks/useRecordFieldValueV2'; import { FieldMetadataType } from 'twenty-shared/types'; import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext'; @@ -15,7 +15,7 @@ export const useLinksFieldDisplay = () => { const fieldName = fieldDefinition.metadata.fieldName; - const fieldValue = useRecordFieldValue( + const fieldValue = useRecordFieldValueV2( recordId, fieldName, fieldDefinition, diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useMorphRelationFromManyFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useMorphRelationFromManyFieldDisplay.ts index fe375b943b6..c241a558836 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useMorphRelationFromManyFieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useMorphRelationFromManyFieldDisplay.ts @@ -7,7 +7,7 @@ import { FieldMetadataType } from '~/generated-metadata/graphql'; import { isFieldMorphRelation } from '@/object-record/record-field/ui/types/guards/isFieldMorphRelation'; -import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue'; +import { useRecordFieldValueV2 } from '@/object-record/record-store/hooks/useRecordFieldValueV2'; import { type ObjectRecord } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext'; @@ -32,7 +32,7 @@ export const useMorphRelationFromManyFieldDisplay = () => { const button = fieldDefinition.editButtonIcon; - const morphValuesWithObjectNameSingular = useRecordFieldValue< + const morphValuesWithObjectNameSingular = useRecordFieldValueV2< { objectNameSingular: string; value: ObjectRecord; diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useMorphRelationToOneFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useMorphRelationToOneFieldDisplay.ts index 99134159eb1..98cad5be40f 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useMorphRelationToOneFieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useMorphRelationToOneFieldDisplay.ts @@ -11,7 +11,7 @@ import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldCont import { assertFieldMetadata } from '@/object-record/record-field/ui/types/guards/assertFieldMetadata'; import { isFieldMorphRelation } from '@/object-record/record-field/ui/types/guards/isFieldMorphRelation'; -import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue'; +import { useRecordFieldValueV2 } from '@/object-record/record-store/hooks/useRecordFieldValueV2'; import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; import { isDefined } from 'twenty-shared/utils'; @@ -34,7 +34,7 @@ export const useMorphRelationToOneFieldDisplay = () => { const button = fieldDefinition.editButtonIcon; - const morphFieldValueWithObjectName = useRecordFieldValue<{ + const morphFieldValueWithObjectName = useRecordFieldValueV2<{ objectNameSingular: string; value: ObjectRecord; }>(recordId, fieldDefinition.metadata.fieldName, fieldDefinition); diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useMultiSelectFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useMultiSelectFieldDisplay.ts index 6fc10d044ca..c1115e7ac99 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useMultiSelectFieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useMultiSelectFieldDisplay.ts @@ -6,14 +6,14 @@ import { type FieldMultiSelectMetadata, type FieldMultiSelectValue, } from '@/object-record/record-field/ui/types/FieldMetadata'; -import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue'; +import { useRecordFieldValueV2 } from '@/object-record/record-store/hooks/useRecordFieldValueV2'; export const useMultiSelectFieldDisplay = () => { const { recordId, fieldDefinition } = useContext(FieldContext); const { fieldName } = fieldDefinition.metadata; - const fieldValue = useRecordFieldValue( + const fieldValue = useRecordFieldValueV2( recordId, fieldName, fieldDefinition, diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useNumberFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useNumberFieldDisplay.ts index b2b3f62c022..55adaf116bc 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useNumberFieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useNumberFieldDisplay.ts @@ -2,7 +2,7 @@ import { useContext } from 'react'; import { FieldMetadataType } from '~/generated-metadata/graphql'; -import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue'; +import { useRecordFieldValueV2 } from '@/object-record/record-store/hooks/useRecordFieldValueV2'; import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext'; import { assertFieldMetadata } from '@/object-record/record-field/ui/types/guards/assertFieldMetadata'; import { isFieldNumber } from '@/object-record/record-field/ui/types/guards/isFieldNumber'; @@ -13,7 +13,7 @@ export const useNumberFieldDisplay = () => { assertFieldMetadata(FieldMetadataType.NUMBER, isFieldNumber, fieldDefinition); const fieldName = fieldDefinition.metadata.fieldName; - const fieldValue = useRecordFieldValue( + const fieldValue = useRecordFieldValueV2( recordId, fieldName, fieldDefinition, diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/usePhonesFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/usePhonesFieldDisplay.ts index b54acfe6906..ecea4effb68 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/usePhonesFieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/usePhonesFieldDisplay.ts @@ -4,7 +4,7 @@ import { type FieldPhonesValue } from '@/object-record/record-field/ui/types/Fie import { assertFieldMetadata } from '@/object-record/record-field/ui/types/guards/assertFieldMetadata'; import { isFieldPhones } from '@/object-record/record-field/ui/types/guards/isFieldPhones'; -import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue'; +import { useRecordFieldValueV2 } from '@/object-record/record-store/hooks/useRecordFieldValueV2'; import { FieldMetadataType } from 'twenty-shared/types'; import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext'; @@ -15,7 +15,7 @@ export const usePhonesFieldDisplay = () => { const fieldName = fieldDefinition.metadata.fieldName; - const fieldValue = useRecordFieldValue( + const fieldValue = useRecordFieldValueV2( recordId, fieldName, fieldDefinition, diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useRatingFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useRatingFieldDisplay.ts index d79be589a5a..a64b44f5a34 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useRatingFieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useRatingFieldDisplay.ts @@ -1,6 +1,6 @@ import { useContext } from 'react'; -import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue'; +import { useRecordFieldValueV2 } from '@/object-record/record-store/hooks/useRecordFieldValueV2'; import { type FieldRatingValue } from 'twenty-shared/types'; import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext'; @@ -9,7 +9,7 @@ export const useRatingFieldDisplay = () => { const fieldName = fieldDefinition.metadata.fieldName; - const fieldValue = useRecordFieldValue( + const fieldValue = useRecordFieldValueV2( recordId, fieldName, fieldDefinition, diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useRelationFromManyFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useRelationFromManyFieldDisplay.ts index d37178c46b3..c3b9dd84bf5 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useRelationFromManyFieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useRelationFromManyFieldDisplay.ts @@ -8,7 +8,7 @@ import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; import { FIELD_EDIT_BUTTON_WIDTH } from '@/ui/field/display/constants/FieldEditButtonWidth'; import { FieldMetadataType } from '~/generated-metadata/graphql'; -import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue'; +import { useRecordFieldValueV2 } from '@/object-record/record-store/hooks/useRecordFieldValueV2'; import { isDefined } from 'twenty-shared/utils'; import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext'; import { assertFieldMetadata } from '@/object-record/record-field/ui/types/guards/assertFieldMetadata'; @@ -35,7 +35,7 @@ export const useRelationFromManyFieldDisplay = () => { const fieldName = fieldDefinition.metadata.fieldName; - const fieldValue = useRecordFieldValue( + const fieldValue = useRecordFieldValueV2( recordId, fieldName, fieldDefinition, diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useRelationToOneFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useRelationToOneFieldDisplay.ts index 7956827c0d7..4959d741f19 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useRelationToOneFieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useRelationToOneFieldDisplay.ts @@ -12,7 +12,7 @@ import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldCont import { assertFieldMetadata } from '@/object-record/record-field/ui/types/guards/assertFieldMetadata'; import { isFieldRelation } from '@/object-record/record-field/ui/types/guards/isFieldRelation'; import { getJoinColumnNameOrThrow } from '@/object-record/record-field/ui/utils/junction/getJoinColumnNameOrThrow'; -import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue'; +import { useRecordFieldValueV2 } from '@/object-record/record-store/hooks/useRecordFieldValueV2'; import { isDefined } from 'twenty-shared/utils'; export const useRelationToOneFieldDisplay = () => { @@ -36,7 +36,7 @@ export const useRelationToOneFieldDisplay = () => { const fieldName = fieldDefinition.metadata.fieldName; - const fieldValue = useRecordFieldValue( + const fieldValue = useRecordFieldValueV2( recordId, fieldName, fieldDefinition, @@ -46,7 +46,7 @@ export const useRelationToOneFieldDisplay = () => { fieldDefinition.metadata.settings, ); - const foreignKeyFieldValue = useRecordFieldValue( + const foreignKeyFieldValue = useRecordFieldValueV2( recordId, joinColumnName, { type: FieldMetadataType.UUID, metadata: { fieldName: joinColumnName } }, diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useRichTextFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useRichTextFieldDisplay.ts index 7a8064f0b96..b6db1472312 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useRichTextFieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useRichTextFieldDisplay.ts @@ -3,7 +3,7 @@ import { useContext } from 'react'; import { type FieldRichTextValue } from '@/object-record/record-field/ui/types/FieldMetadata'; import { assertFieldMetadata } from '@/object-record/record-field/ui/types/guards/assertFieldMetadata'; import { isFieldRichText } from '@/object-record/record-field/ui/types/guards/isFieldRichText'; -import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue'; +import { useRecordFieldValueV2 } from '@/object-record/record-store/hooks/useRecordFieldValueV2'; import type { PartialBlock } from '@blocknote/core'; import { isDefined, parseJson } from 'twenty-shared/utils'; import { FieldMetadataType } from '~/generated-metadata/graphql'; @@ -20,7 +20,7 @@ export const useRichTextFieldDisplay = () => { const fieldName = fieldDefinition.metadata.fieldName; - const fieldValue = useRecordFieldValue( + const fieldValue = useRecordFieldValueV2( recordId, fieldName, fieldDefinition, diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useRichTextV2FieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useRichTextV2FieldDisplay.ts index ef36cde0e74..5e017107bae 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useRichTextV2FieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useRichTextV2FieldDisplay.ts @@ -3,7 +3,7 @@ import { useContext } from 'react'; import { type FieldRichTextV2Value } from '@/object-record/record-field/ui/types/FieldMetadata'; import { assertFieldMetadata } from '@/object-record/record-field/ui/types/guards/assertFieldMetadata'; import { isFieldRichTextV2 } from '@/object-record/record-field/ui/types/guards/isFieldRichTextV2'; -import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue'; +import { useRecordFieldValueV2 } from '@/object-record/record-store/hooks/useRecordFieldValueV2'; import { FieldMetadataType } from '~/generated-metadata/graphql'; import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext'; @@ -18,7 +18,7 @@ export const useRichTextV2FieldDisplay = () => { const fieldName = fieldDefinition.metadata.fieldName; - const fieldValue = useRecordFieldValue( + const fieldValue = useRecordFieldValueV2( recordId, fieldName, fieldDefinition, diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useSelectFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useSelectFieldDisplay.ts index 14146fd9199..86b4daa383e 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useSelectFieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useSelectFieldDisplay.ts @@ -2,7 +2,7 @@ import { useContext } from 'react'; import { type FieldDefinition } from '@/object-record/record-field/ui/types/FieldDefinition'; -import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue'; +import { useRecordFieldValueV2 } from '@/object-record/record-store/hooks/useRecordFieldValueV2'; import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext'; import { type FieldSelectMetadata, @@ -14,7 +14,7 @@ export const useSelectFieldDisplay = () => { const { fieldName } = fieldDefinition.metadata; - const fieldValue = useRecordFieldValue( + const fieldValue = useRecordFieldValueV2( recordId, fieldName, fieldDefinition, diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useTextFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useTextFieldDisplay.ts index 4deb57269a7..c9c89b17eef 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useTextFieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useTextFieldDisplay.ts @@ -1,6 +1,6 @@ import { useContext } from 'react'; -import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue'; +import { useRecordFieldValueV2 } from '@/object-record/record-store/hooks/useRecordFieldValueV2'; import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext'; export const useTextFieldDisplay = () => { @@ -10,7 +10,7 @@ export const useTextFieldDisplay = () => { const fieldName = fieldDefinition.metadata.fieldName; const fieldValue = - useRecordFieldValue( + useRecordFieldValueV2( recordId, fieldName, fieldDefinition, diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useUuidField.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useUuidField.ts index 28bc29485ba..32b0990e3e5 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useUuidField.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useUuidField.ts @@ -1,12 +1,12 @@ import { useContext } from 'react'; -import { useRecoilState } from 'recoil'; import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext'; import { type FieldUUidValue } from '@/object-record/record-field/ui/types/FieldMetadata'; import { assertFieldMetadata } from '@/object-record/record-field/ui/types/guards/assertFieldMetadata'; import { isFieldTextValue } from '@/object-record/record-field/ui/types/guards/isFieldTextValue'; import { isFieldUuid } from '@/object-record/record-field/ui/types/guards/isFieldUuid'; -import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector'; +import { recordStoreFamilySelectorV2 } from '@/object-record/record-store/states/selectors/recordStoreFamilySelectorV2'; +import { useFamilySelectorStateV2 } from '@/ui/utilities/state/jotai/hooks/useFamilySelectorStateV2'; import { FieldMetadataType } from '~/generated-metadata/graphql'; export const useUuidField = () => { @@ -16,13 +16,14 @@ export const useUuidField = () => { const fieldName = fieldDefinition.metadata.fieldName; - const [fieldValue, setFieldValue] = useRecoilState( - recordStoreFamilySelector({ - recordId, - fieldName: fieldName, - }), + const [fieldValue, setFieldValue] = useFamilySelectorStateV2( + recordStoreFamilySelectorV2, + { recordId, fieldName }, ); - const fieldTextValue = isFieldTextValue(fieldValue) ? fieldValue : ''; + + const fieldTextValue = isFieldTextValue(fieldValue as FieldUUidValue) + ? (fieldValue as FieldUUidValue) + : ''; return { fieldDefinition, diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/components/FilesFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/components/FilesFieldInput.tsx index 25325b2d4c2..1e5c5dfe06a 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/components/FilesFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/components/FilesFieldInput.tsx @@ -1,4 +1,4 @@ -import { isAttachmentPreviewEnabledState } from '@/client-config/states/isAttachmentPreviewEnabledState'; +import { isAttachmentPreviewEnabledStateV2 } from '@/client-config/states/isAttachmentPreviewEnabledStateV2'; import { useFileUpload } from '@/file-upload/hooks/useFileUpload'; import { FieldInputEventContext } from '@/object-record/record-field/ui/contexts/FieldInputEventContext'; import { useFilesField } from '@/object-record/record-field/ui/meta-types/hooks/useFilesField'; @@ -11,11 +11,12 @@ import { recordFieldInputIsFieldInErrorComponentState } from '@/object-record/re import { type FieldFilesValue } from '@/object-record/record-field/ui/types/FieldMetadata'; import { filesSchema } from '@/object-record/record-field/ui/types/guards/isFieldFilesValue'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; -import { filePreviewState } from '@/ui/field/display/states/filePreviewState'; +import { filePreviewStateV2 } from '@/ui/field/display/states/filePreviewStateV2'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; +import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState'; import { useLingui } from '@lingui/react/macro'; import { useCallback, useContext, useMemo, useState } from 'react'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; import { MULTI_ITEM_FIELD_DEFAULT_MAX_VALUES } from 'twenty-shared/constants'; import { isDefined } from 'twenty-shared/utils'; import { FieldMetadataType } from '~/generated-metadata/graphql'; @@ -27,9 +28,9 @@ export const FilesFieldInput = () => { const { t } = useLingui(); const [isUploading, setIsUploading] = useState(false); const { enqueueErrorSnackBar } = useSnackBar(); - const setFilePreview = useSetRecoilState(filePreviewState); - const isAttachmentPreviewEnabled = useRecoilValue( - isAttachmentPreviewEnabledState, + const setFilePreview = useSetRecoilStateV2(filePreviewStateV2); + const isAttachmentPreviewEnabled = useRecoilValueV2( + isAttachmentPreviewEnabledStateV2, ); const { onEscape, onClickOutside, onEnter } = useContext( diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/hooks/useOpenFilesFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/hooks/useOpenFilesFieldInput.tsx index 24ca4a7938a..324f772c8f9 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/hooks/useOpenFilesFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/hooks/useOpenFilesFieldInput.tsx @@ -2,8 +2,10 @@ import { useFileUpload } from '@/file-upload/hooks/useFileUpload'; import { useUploadFilesFieldFile } from '@/object-record/record-field/ui/meta-types/hooks/useUploadFilesFieldFile'; import { uploadMultipleFiles } from '@/object-record/record-field/ui/meta-types/utils/uploadMultipleFiles'; import { filesFieldUploadState } from '@/object-record/record-field/ui/states/filesFieldUploadState'; +import { filesFieldUploadStateV2 } from '@/object-record/record-field/ui/states/filesFieldUploadStateV2'; import { type FieldFilesValue } from '@/object-record/record-field/ui/types/FieldMetadata'; import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { RECORD_TABLE_CELL_INPUT_ID_PREFIX } from '@/object-record/record-table/constants/RecordTableCellInputIdPrefix'; import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext'; import { recordTableCellEditModePositionComponentState } from '@/object-record/record-table/states/recordTableCellEditModePositionComponentState'; @@ -99,6 +101,10 @@ export const useOpenFilesFieldInput = () => { filesFieldUploadState({ recordId, fieldName }), 'UPLOAD_WINDOW_OPEN', ); + jotaiStore.set( + filesFieldUploadStateV2.atomFamily({ recordId, fieldName }), + 'UPLOAD_WINDOW_OPEN', + ); openFileUpload({ multiple: true, @@ -109,6 +115,10 @@ export const useOpenFilesFieldInput = () => { }); set(filesFieldUploadState({ recordId, fieldName }), null); + jotaiStore.set( + filesFieldUploadStateV2.atomFamily({ recordId, fieldName }), + null, + ); if (isTableContext && isDefined(recordTableId)) { set( @@ -131,6 +141,10 @@ export const useOpenFilesFieldInput = () => { filesFieldUploadState({ recordId, fieldName }), 'UPLOADING_FILE', ); + jotaiStore.set( + filesFieldUploadStateV2.atomFamily({ recordId, fieldName }), + 'UPLOADING_FILE', + ); try { const uploadedFiles = await uploadMultipleFiles( @@ -146,6 +160,10 @@ export const useOpenFilesFieldInput = () => { } } finally { set(filesFieldUploadState({ recordId, fieldName }), null); + jotaiStore.set( + filesFieldUploadStateV2.atomFamily({ recordId, fieldName }), + null, + ); if (isTableContext && isDefined(recordTableId)) { set( @@ -165,6 +183,10 @@ export const useOpenFilesFieldInput = () => { }, onCancel: () => { set(filesFieldUploadState({ recordId, fieldName }), null); + jotaiStore.set( + filesFieldUploadStateV2.atomFamily({ recordId, fieldName }), + null, + ); if (isTableContext && isDefined(recordTableId)) { set( diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/states/filesFieldUploadStateV2.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/states/filesFieldUploadStateV2.ts new file mode 100644 index 00000000000..a09679574eb --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/states/filesFieldUploadStateV2.ts @@ -0,0 +1,16 @@ +import { createFamilyStateV2 } from '@/ui/utilities/state/jotai/utils/createFamilyStateV2'; + +type FilesFieldUploadStateKey = { + recordId: string; + fieldName: string; +}; + +type FilesFieldUploadState = 'UPLOAD_WINDOW_OPEN' | 'UPLOADING_FILE' | null; + +export const filesFieldUploadStateV2 = createFamilyStateV2< + FilesFieldUploadState, + FilesFieldUploadStateKey +>({ + key: 'filesFieldUploadStateV2', + defaultValue: null, +}); diff --git a/packages/twenty-front/src/modules/object-record/record-index/hooks/useLoadRecordIndexStates.ts b/packages/twenty-front/src/modules/object-record/record-index/hooks/useLoadRecordIndexStates.ts index 1f94114019b..d5b71f14b99 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/hooks/useLoadRecordIndexStates.ts +++ b/packages/twenty-front/src/modules/object-record/record-index/hooks/useLoadRecordIndexStates.ts @@ -13,6 +13,8 @@ import { recordIndexFieldDefinitionsState } from '@/object-record/record-index/s import { recordIndexGroupAggregateFieldMetadataItemComponentState } from '@/object-record/record-index/states/recordIndexGroupAggregateFieldMetadataItemComponentState'; import { recordIndexGroupAggregateOperationComponentState } from '@/object-record/record-index/states/recordIndexGroupAggregateOperationComponentState'; import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState'; +import { recordIndexOpenRecordInStateV2 } from '@/object-record/record-index/states/recordIndexOpenRecordInStateV2'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { recordIndexShouldHideEmptyRecordGroupsComponentState } from '@/object-record/record-index/states/recordIndexShouldHideEmptyRecordGroupsComponentState'; import { recordIndexViewTypeState } from '@/object-record/record-index/states/recordIndexViewTypeState'; import { viewFieldAggregateOperationState } from '@/object-record/record-table/record-table-footer/states/viewFieldAggregateOperationState'; @@ -200,6 +202,7 @@ export const useLoadRecordIndexStates = () => { setRecordIndexViewType(view.type); setRecordIndexOpenRecordIn(view.openRecordIn); + jotaiStore.set(recordIndexOpenRecordInStateV2.atom, view.openRecordIn); setRecordIndexCalendarFieldMetadataIdState( view.calendarFieldMetadataId ?? null, diff --git a/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexOpenRecordInStateV2.ts b/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexOpenRecordInStateV2.ts new file mode 100644 index 00000000000..c2660367863 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-index/states/recordIndexOpenRecordInStateV2.ts @@ -0,0 +1,8 @@ +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; +import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType'; + +export const recordIndexOpenRecordInStateV2 = + createStateV2({ + key: 'recordIndexOpenRecordInStateV2', + defaultValue: ViewOpenRecordInType.SIDE_PANEL, + }); diff --git a/packages/twenty-front/src/modules/object-record/record-show/components/RecordShowEffect.tsx b/packages/twenty-front/src/modules/object-record/record-show/components/RecordShowEffect.tsx index 5dd48d7f524..3c46c802e63 100644 --- a/packages/twenty-front/src/modules/object-record/record-show/components/RecordShowEffect.tsx +++ b/packages/twenty-front/src/modules/object-record/record-show/components/RecordShowEffect.tsx @@ -3,7 +3,9 @@ import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadat import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord'; import { buildFindOneRecordForShowPageOperationSignature } from '@/object-record/record-show/graphql/operations/factories/findOneRecordForShowPageOperationSignatureFactory'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; +import { recordStoreFamilyStateV2 } from '@/object-record/record-store/states/recordStoreFamilyStateV2'; import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { useEffect } from 'react'; import { useRecoilCallback } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; @@ -42,6 +44,10 @@ export const RecordShowEffect = ({ if (JSON.stringify(previousRecordValue) !== JSON.stringify(newRecord)) { set(recordStoreFamilyState(recordId), newRecord); + jotaiStore.set( + recordStoreFamilyStateV2.atomFamily(recordId), + newRecord, + ); } }, [recordId], diff --git a/packages/twenty-front/src/modules/object-record/record-store/hooks/useRecordFieldValueV2.ts b/packages/twenty-front/src/modules/object-record/record-store/hooks/useRecordFieldValueV2.ts new file mode 100644 index 00000000000..371ad7f9942 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-store/hooks/useRecordFieldValueV2.ts @@ -0,0 +1,22 @@ +import { useAtomValue } from 'jotai'; + +import { type FieldDefinition } from '@/object-record/record-field/ui/types/FieldDefinition'; +import { type FieldMetadata } from '@/object-record/record-field/ui/types/FieldMetadata'; +import { recordStoreFieldValueSelectorV2 } from '@/object-record/record-store/states/selectors/recordStoreFieldValueSelectorV2'; + +export const useRecordFieldValueV2 = ( + recordId: string, + fieldName: string, + fieldDefinition: Pick, 'type' | 'metadata'>, +) => { + const fieldValueAtom = recordStoreFieldValueSelectorV2({ + recordId, + fieldName, + fieldDefinition: { + type: fieldDefinition.type, + metadata: fieldDefinition.metadata, + }, + }); + + return useAtomValue(fieldValueAtom) as T | undefined; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-store/hooks/useUpsertRecordsInStore.ts b/packages/twenty-front/src/modules/object-record/record-store/hooks/useUpsertRecordsInStore.ts index 245b2fccc58..e0c5bfaad72 100644 --- a/packages/twenty-front/src/modules/object-record/record-store/hooks/useUpsertRecordsInStore.ts +++ b/packages/twenty-front/src/modules/object-record/record-store/hooks/useUpsertRecordsInStore.ts @@ -3,7 +3,9 @@ import { useRecoilCallback } from 'recoil'; import { filterRecordOnGqlFields } from '@/object-record/cache/utils/filterRecordOnGqlFields'; import { type RecordGqlFields } from '@/object-record/graphql/record-gql-fields/types/RecordGqlFields'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; +import { recordStoreFamilyStateV2 } from '@/object-record/record-store/states/recordStoreFamilyStateV2'; import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { isDefined } from 'twenty-shared/utils'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; @@ -29,11 +31,16 @@ export const useUpsertRecordsInStore = () => { : partialRecord; if (!isDefined(currentRecord)) { - set(recordStoreFamilyState(partialRecord.id), { + const newRecord = { id: partialRecord.id, __typename: partialRecord.__typename, ...filteredPartialRecord, - }); + }; + set(recordStoreFamilyState(partialRecord.id), newRecord); + jotaiStore.set( + recordStoreFamilyStateV2.atomFamily(partialRecord.id), + newRecord, + ); continue; } @@ -45,10 +52,15 @@ export const useUpsertRecordsInStore = () => { : currentRecord; if (!isDeeplyEqual(filteredCurrentRecord, filteredPartialRecord)) { - set(recordStoreFamilyState(partialRecord.id), { + const updatedRecord = { ...currentRecord, ...filteredPartialRecord, - }); + }; + set(recordStoreFamilyState(partialRecord.id), updatedRecord); + jotaiStore.set( + recordStoreFamilyStateV2.atomFamily(partialRecord.id), + updatedRecord, + ); } } }, diff --git a/packages/twenty-front/src/modules/object-record/record-store/states/recordStoreFamilyStateV2.ts b/packages/twenty-front/src/modules/object-record/record-store/states/recordStoreFamilyStateV2.ts new file mode 100644 index 00000000000..cfc4debafc4 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-store/states/recordStoreFamilyStateV2.ts @@ -0,0 +1,10 @@ +import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; +import { createFamilyStateV2 } from '@/ui/utilities/state/jotai/utils/createFamilyStateV2'; + +export const recordStoreFamilyStateV2 = createFamilyStateV2< + ObjectRecord | null | undefined, + string +>({ + key: 'recordStoreFamilyStateV2', + defaultValue: null, +}); diff --git a/packages/twenty-front/src/modules/object-record/record-store/states/selectors/recordStoreFamilySelectorV2.ts b/packages/twenty-front/src/modules/object-record/record-store/states/selectors/recordStoreFamilySelectorV2.ts new file mode 100644 index 00000000000..aa539cce532 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-store/states/selectors/recordStoreFamilySelectorV2.ts @@ -0,0 +1,23 @@ +import { recordStoreFamilyStateV2 } from '@/object-record/record-store/states/recordStoreFamilyStateV2'; +import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; +import { createWritableFamilySelectorV2 } from '@/ui/utilities/state/jotai/utils/createWritableFamilySelectorV2'; + +export const recordStoreFamilySelectorV2 = createWritableFamilySelectorV2< + unknown, + { recordId: string; fieldName: string } +>({ + key: 'recordStoreFamilySelectorV2', + get: + ({ recordId, fieldName }) => + ({ get }) => + get(recordStoreFamilyStateV2, recordId)?.[fieldName], + set: + ({ recordId, fieldName }) => + ({ set }, newValue) => { + set(recordStoreFamilyStateV2, recordId, (prev) => + prev + ? { ...prev, [fieldName]: newValue } + : ({ [fieldName]: newValue } as ObjectRecord), + ); + }, +}); diff --git a/packages/twenty-front/src/modules/object-record/record-store/states/selectors/recordStoreFieldValueSelectorV2.ts b/packages/twenty-front/src/modules/object-record/record-store/states/selectors/recordStoreFieldValueSelectorV2.ts new file mode 100644 index 00000000000..c846f8346e8 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-store/states/selectors/recordStoreFieldValueSelectorV2.ts @@ -0,0 +1,117 @@ +import { atom, type Atom } from 'jotai'; + +import { type FieldDefinition } from '@/object-record/record-field/ui/types/FieldDefinition'; +import { type FieldMetadata } from '@/object-record/record-field/ui/types/FieldMetadata'; +import { isFieldMorphRelation } from '@/object-record/record-field/ui/types/guards/isFieldMorphRelation'; +import { recordStoreFamilyStateV2 } from '@/object-record/record-store/states/recordStoreFamilyStateV2'; +import { createFamilySelectorV2 } from '@/ui/utilities/state/jotai/utils/createFamilySelectorV2'; +import { RelationType, type ObjectRecord } from 'twenty-shared/types'; +import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils'; + +const simpleFieldValueSelector = createFamilySelectorV2< + unknown, + { recordId: string; fieldName: string } +>({ + key: 'recordStoreSimpleFieldValue', + get: + ({ recordId, fieldName }) => + ({ get }) => + get(recordStoreFamilyStateV2, recordId)?.[fieldName], +}); + +const morphAtomCache = new Map>(); + +const getMorphRelationFieldValueAtom = ( + recordId: string, + fieldName: string, + fieldDefinition: Pick, 'type' | 'metadata'>, +): Atom => { + const cacheKey = `morph__${recordId}__${fieldName}`; + const existing = morphAtomCache.get(cacheKey); + + if (existing !== undefined) { + return existing; + } + + const morphRelations = + ( + fieldDefinition.metadata as { + morphRelations?: Array<{ + type: string; + sourceFieldMetadata: { name: string }; + targetObjectMetadata: { + nameSingular: string; + namePlural: string; + }; + }>; + } + ).morphRelations ?? []; + + const derivedAtom = atom((get) => { + const record = get(recordStoreFamilyStateV2.atomFamily(recordId)); + + const morphValuesWithObjectName = morphRelations.map((morphRelation) => { + const computedFieldName = computeMorphRelationFieldName({ + fieldName: morphRelation.sourceFieldMetadata.name, + relationType: morphRelation.type as RelationType, + targetObjectMetadataNameSingular: + morphRelation.targetObjectMetadata.nameSingular, + targetObjectMetadataNamePlural: + morphRelation.targetObjectMetadata.namePlural, + }); + + return { + objectNameSingular: morphRelation.targetObjectMetadata.nameSingular, + value: record?.[computedFieldName], + }; + }); + + const relationType = morphRelations[0]?.type; + + if (relationType === RelationType.ONE_TO_MANY) { + return morphValuesWithObjectName.map((morphValue) => ({ + ...morphValue, + value: morphValue.value ? morphValue.value : [], + })) as { + objectNameSingular: string; + value: ObjectRecord[]; + }[]; + } + + if (relationType === RelationType.MANY_TO_ONE) { + const morphValueFiltered = morphValuesWithObjectName.filter( + (morphValue) => isDefined(morphValue.value), + ); + + return morphValueFiltered.length > 0 + ? (morphValueFiltered[0] as { + objectNameSingular: string; + value: ObjectRecord; + }) + : null; + } + + return null; + }); + + derivedAtom.debugLabel = `recordMorphFieldValue__${cacheKey}`; + morphAtomCache.set(cacheKey, derivedAtom); + + return derivedAtom; +}; + +export const recordStoreFieldValueSelectorV2 = ({ + recordId, + fieldName, + fieldDefinition, +}: { + recordId: string; + fieldName: string; + fieldDefinition: Pick, 'type' | 'metadata'>; +}): Atom => { + if (isFieldMorphRelation(fieldDefinition)) { + return getMorphRelationFieldValueAtom(recordId, fieldName, fieldDefinition); + } + + return simpleFieldValueSelector.selectorFamily({ recordId, fieldName }); +}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useSetRecordTableData.ts b/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useSetRecordTableData.ts index 4949eb1e812..9fc84153896 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useSetRecordTableData.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useSetRecordTableData.ts @@ -4,6 +4,7 @@ import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; +import { recordStoreFamilyStateV2 } from '@/object-record/record-store/states/recordStoreFamilyStateV2'; import { useFocusedRecordTableRow } from '@/object-record/record-table/hooks/useFocusedRecordTableRow'; import { useUnfocusRecordTableCell } from '@/object-record/record-table/record-table-cell/hooks/useUnfocusRecordTableCell'; import { hasUserSelectedAllRowsComponentState } from '@/object-record/record-table/record-table-row/states/hasUserSelectedAllRowsFamilyState'; @@ -16,6 +17,7 @@ import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState'; import { useRecoilComponentFamilyCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyCallbackState'; import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; import { isDefined } from 'twenty-shared/utils'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; @@ -90,6 +92,10 @@ export const useSetRecordTableData = ({ }; set(recordStoreFamilyState(record.id), newRecord); + jotaiStore.set( + recordStoreFamilyStateV2.atomFamily(record.id), + newRecord, + ); } } diff --git a/packages/twenty-front/src/modules/page-layout/widgets/field/components/__stories__/FieldWidget.stories.tsx b/packages/twenty-front/src/modules/page-layout/widgets/field/components/__stories__/FieldWidget.stories.tsx index c9a813f43e3..c8709499c53 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/field/components/__stories__/FieldWidget.stories.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/field/components/__stories__/FieldWidget.stories.tsx @@ -13,6 +13,7 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadat import { shouldAppBeLoadingState } from '@/object-metadata/states/shouldAppBeLoadingState'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; +import { recordStoreFamilyStateV2 } from '@/object-record/record-store/states/recordStoreFamilyStateV2'; import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; import { PageLayoutContentProvider } from '@/page-layout/contexts/PageLayoutContentContext'; import { @@ -25,6 +26,7 @@ import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget'; import { FieldWidget } from '@/page-layout/widgets/field/components/FieldWidget'; import { WidgetComponentInstanceContext } from '@/page-layout/widgets/states/contexts/WidgetComponentInstanceContext'; import { LayoutRenderingProvider } from '@/ui/layout/contexts/LayoutRenderingContext'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { ComponentDecorator } from 'twenty-ui/testing'; import { PageLayoutTabLayoutMode, @@ -256,6 +258,16 @@ const mockCompanyRecord: ObjectRecord = { }, }; +// Sets a record in both Recoil and Jotai stores so field display hooks can read it +const setRecordInStores = ( + snapshot: MutableSnapshot, + recordId: string, + record: ObjectRecord, +) => { + snapshot.set(recordStoreFamilyState(recordId), record); + jotaiStore.set(recordStoreFamilyStateV2.atomFamily(recordId), record); +}; + const JestMetadataAndApolloMocksWrapper = getJestMetadataAndApolloMocksWrapper({ apolloMocks: [], }); @@ -369,7 +381,7 @@ export const TextFieldWidget: Story = { }), pageLayoutData, ); - snapshot.set(recordStoreFamilyState(TEST_RECORD_ID), mockCompanyRecord); + setRecordInStores(snapshot, TEST_RECORD_ID, mockCompanyRecord); }; return ( @@ -461,7 +473,7 @@ export const AddressFieldWidget: Story = { }), pageLayoutData, ); - snapshot.set(recordStoreFamilyState(TEST_RECORD_ID), mockCompanyRecord); + setRecordInStores(snapshot, TEST_RECORD_ID, mockCompanyRecord); }; return ( @@ -556,7 +568,7 @@ export const NumberFieldWidget: Story = { }), pageLayoutData, ); - snapshot.set(recordStoreFamilyState(TEST_RECORD_ID), mockCompanyRecord); + setRecordInStores(snapshot, TEST_RECORD_ID, mockCompanyRecord); }; return ( @@ -648,7 +660,7 @@ export const LinkFieldWidget: Story = { }), pageLayoutData, ); - snapshot.set(recordStoreFamilyState(TEST_RECORD_ID), mockCompanyRecord); + setRecordInStores(snapshot, TEST_RECORD_ID, mockCompanyRecord); }; return ( @@ -740,14 +752,15 @@ export const ManyToOneRelationFieldWidget: Story = { }), pageLayoutData, ); - snapshot.set(recordStoreFamilyState(TEST_RECORD_ID), mockCompanyRecord); + setRecordInStores(snapshot, TEST_RECORD_ID, mockCompanyRecord); // Set the related WorkspaceMember record for relation field display if ( mockCompanyRecord.accountOwner !== null && mockCompanyRecord.accountOwner !== undefined ) { - snapshot.set( - recordStoreFamilyState(mockCompanyRecord.accountOwner.id), + setRecordInStores( + snapshot, + mockCompanyRecord.accountOwner.id, mockCompanyRecord.accountOwner, ); } @@ -842,12 +855,9 @@ export const OneToManyRelationFieldWidget: Story = { }), pageLayoutData, ); - snapshot.set(recordStoreFamilyState(TEST_RECORD_ID), mockCompanyRecord); + setRecordInStores(snapshot, TEST_RECORD_ID, mockCompanyRecord); // Set the related Person record for ONE_TO_MANY relation display - snapshot.set( - recordStoreFamilyState(TEST_PERSON_RECORD_ID), - mockPersonRecord, - ); + setRecordInStores(snapshot, TEST_PERSON_RECORD_ID, mockPersonRecord); }; return ( @@ -939,7 +949,7 @@ export const BooleanFieldWidget: Story = { }), pageLayoutData, ); - snapshot.set(recordStoreFamilyState(TEST_RECORD_ID), mockCompanyRecord); + setRecordInStores(snapshot, TEST_RECORD_ID, mockCompanyRecord); }; return ( @@ -1030,7 +1040,7 @@ export const CurrencyFieldWidget: Story = { }), pageLayoutData, ); - snapshot.set(recordStoreFamilyState(TEST_RECORD_ID), mockCompanyRecord); + setRecordInStores(snapshot, TEST_RECORD_ID, mockCompanyRecord); }; return ( @@ -1121,10 +1131,7 @@ export const EmailsFieldWidget: Story = { }), pageLayoutData, ); - snapshot.set( - recordStoreFamilyState(TEST_PERSON_RECORD_ID), - mockPersonRecord, - ); + setRecordInStores(snapshot, TEST_PERSON_RECORD_ID, mockPersonRecord); }; return ( @@ -1216,10 +1223,7 @@ export const PhonesFieldWidget: Story = { }), pageLayoutData, ); - snapshot.set( - recordStoreFamilyState(TEST_PERSON_RECORD_ID), - mockPersonRecord, - ); + setRecordInStores(snapshot, TEST_PERSON_RECORD_ID, mockPersonRecord); }; return ( @@ -1311,8 +1315,9 @@ export const SelectFieldWidget: Story = { }), pageLayoutData, ); - snapshot.set( - recordStoreFamilyState(TEST_OPPORTUNITY_RECORD_ID), + setRecordInStores( + snapshot, + TEST_OPPORTUNITY_RECORD_ID, mockOpportunityRecord, ); }; @@ -1407,7 +1412,7 @@ export const MultiSelectFieldWidget: Story = { }), pageLayoutData, ); - snapshot.set(recordStoreFamilyState(TEST_RECORD_ID), mockCompanyRecord); + setRecordInStores(snapshot, TEST_RECORD_ID, mockCompanyRecord); }; return ( @@ -1503,13 +1508,15 @@ export const TimelineActivityRelationFieldWidget: Story = { }), pageLayoutData, ); - snapshot.set( - recordStoreFamilyState(TEST_TIMELINE_ACTIVITY_RECORD_ID), + setRecordInStores( + snapshot, + TEST_TIMELINE_ACTIVITY_RECORD_ID, mockTimelineActivityRecord, ); // Set the related WorkspaceMember record for TimelineActivity relation display - snapshot.set( - recordStoreFamilyState('test-workspace-member-xyz'), + setRecordInStores( + snapshot, + 'test-workspace-member-xyz', mockWorkspaceMemberRecord, ); }; @@ -1603,13 +1610,14 @@ export const ManyToOneRelationCardWidget: Story = { }), pageLayoutData, ); - snapshot.set(recordStoreFamilyState(TEST_RECORD_ID), mockCompanyRecord); + setRecordInStores(snapshot, TEST_RECORD_ID, mockCompanyRecord); if ( mockCompanyRecord.accountOwner !== null && mockCompanyRecord.accountOwner !== undefined ) { - snapshot.set( - recordStoreFamilyState(mockCompanyRecord.accountOwner.id), + setRecordInStores( + snapshot, + mockCompanyRecord.accountOwner.id, mockCompanyRecord.accountOwner, ); } @@ -1713,11 +1721,8 @@ export const OneToManyRelationCardWidget: Story = { }), pageLayoutData, ); - snapshot.set(recordStoreFamilyState(TEST_RECORD_ID), mockCompanyRecord); - snapshot.set( - recordStoreFamilyState(TEST_PERSON_RECORD_ID), - mockPersonRecord, - ); + setRecordInStores(snapshot, TEST_RECORD_ID, mockCompanyRecord); + setRecordInStores(snapshot, TEST_PERSON_RECORD_ID, mockPersonRecord); }; return ( @@ -1809,12 +1814,14 @@ export const TimelineActivityRelationCardWidget: Story = { }), pageLayoutData, ); - snapshot.set( - recordStoreFamilyState(TEST_TIMELINE_ACTIVITY_RECORD_ID), + setRecordInStores( + snapshot, + TEST_TIMELINE_ACTIVITY_RECORD_ID, mockTimelineActivityRecord, ); - snapshot.set( - recordStoreFamilyState('test-workspace-member-xyz'), + setRecordInStores( + snapshot, + 'test-workspace-member-xyz', mockWorkspaceMemberRecord, ); }; @@ -1971,13 +1978,10 @@ export const OneToManyRelationCardWidgetWithProgressiveLoading: Story = { }), pageLayoutData, ); - snapshot.set( - recordStoreFamilyState(TEST_RECORD_ID), - companyWithManyPeople, - ); + setRecordInStores(snapshot, TEST_RECORD_ID, companyWithManyPeople); // Set each person record in the store mockPeople.forEach((person) => { - snapshot.set(recordStoreFamilyState(person.id), person); + setRecordInStores(snapshot, person.id, person); }); }; diff --git a/packages/twenty-front/src/modules/sse-db-event/components/ListenRecordUpdatesEffect.tsx b/packages/twenty-front/src/modules/sse-db-event/components/ListenRecordUpdatesEffect.tsx index 3e85d75bd56..79368ac2188 100644 --- a/packages/twenty-front/src/modules/sse-db-event/components/ListenRecordUpdatesEffect.tsx +++ b/packages/twenty-front/src/modules/sse-db-event/components/ListenRecordUpdatesEffect.tsx @@ -10,7 +10,9 @@ import { useGenerateDepthRecordGqlFieldsFromObject } from '@/object-record/graph import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions'; import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; +import { recordStoreFamilyStateV2 } from '@/object-record/record-store/states/recordStoreFamilyStateV2'; import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { useOnDbEvent } from '@/sse-db-event/hooks/useOnDbEvent'; import { useRecoilCallback } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; @@ -48,6 +50,7 @@ export const ListenRecordUpdatesEffect = ({ ({ set }) => (record: ObjectRecord) => { set(recordStoreFamilyState(record.id), record); + jotaiStore.set(recordStoreFamilyStateV2.atomFamily(record.id), record); }, [], ); diff --git a/packages/twenty-front/src/modules/ui/field/display/components/DateDisplay.tsx b/packages/twenty-front/src/modules/ui/field/display/components/DateDisplay.tsx index dc9dd6ef8b5..06ce1656744 100644 --- a/packages/twenty-front/src/modules/ui/field/display/components/DateDisplay.tsx +++ b/packages/twenty-front/src/modules/ui/field/display/components/DateDisplay.tsx @@ -1,8 +1,8 @@ import { type FieldDateMetadataSettings } from '@/object-record/record-field/ui/types/FieldMetadata'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { UserContext } from '@/users/contexts/UserContext'; import { useContext } from 'react'; -import { useRecoilValue } from 'recoil'; -import { dateLocaleState } from '~/localization/states/dateLocaleState'; +import { dateLocaleStateV2 } from '~/localization/states/dateLocaleStateV2'; import { formatDateString } from '~/utils/string/formatDateString'; import { EllipsisDisplay } from './EllipsisDisplay'; @@ -12,7 +12,7 @@ type DateDisplayProps = { }; export const DateDisplay = ({ value, dateFieldSettings }: DateDisplayProps) => { const { dateFormat } = useContext(UserContext); - const dateLocale = useRecoilValue(dateLocaleState); + const dateLocale = useRecoilValueV2(dateLocaleStateV2); const formattedDate = formatDateString({ value, diff --git a/packages/twenty-front/src/modules/ui/field/display/components/DateTimeDisplay.tsx b/packages/twenty-front/src/modules/ui/field/display/components/DateTimeDisplay.tsx index cc5578fb0ba..2adc3bd1a76 100644 --- a/packages/twenty-front/src/modules/ui/field/display/components/DateTimeDisplay.tsx +++ b/packages/twenty-front/src/modules/ui/field/display/components/DateTimeDisplay.tsx @@ -1,12 +1,12 @@ import { type FieldDateMetadataSettings } from '@/object-record/record-field/ui/types/FieldMetadata'; import { TimeZoneAbbreviation } from '@/ui/input/components/internal/date/components/TimeZoneAbbreviation'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { UserContext } from '@/users/contexts/UserContext'; import styled from '@emotion/styled'; import { isNonEmptyString } from '@sniptt/guards'; import { useContext } from 'react'; -import { useRecoilValue } from 'recoil'; import { Temporal } from 'temporal-polyfill'; -import { dateLocaleState } from '~/localization/states/dateLocaleState'; +import { dateLocaleStateV2 } from '~/localization/states/dateLocaleStateV2'; import { formatDateTimeString } from '~/utils/string/formatDateTimeString'; import { EllipsisDisplay } from './EllipsisDisplay'; @@ -24,7 +24,7 @@ export const DateTimeDisplay = ({ dateFieldSettings, }: DateTimeDisplayProps) => { const { dateFormat, timeFormat, timeZone } = useContext(UserContext); - const dateLocale = useRecoilValue(dateLocaleState); + const dateLocale = useRecoilValueV2(dateLocaleStateV2); const formattedDate = formatDateTimeString({ value, diff --git a/packages/twenty-front/src/modules/ui/field/display/components/FilesDisplay.tsx b/packages/twenty-front/src/modules/ui/field/display/components/FilesDisplay.tsx index c2751dfd970..8bdfe2ac923 100644 --- a/packages/twenty-front/src/modules/ui/field/display/components/FilesDisplay.tsx +++ b/packages/twenty-front/src/modules/ui/field/display/components/FilesDisplay.tsx @@ -1,11 +1,12 @@ import { downloadFile } from '@/activities/files/utils/downloadFile'; -import { isAttachmentPreviewEnabledState } from '@/client-config/states/isAttachmentPreviewEnabledState'; +import { isAttachmentPreviewEnabledStateV2 } from '@/client-config/states/isAttachmentPreviewEnabledStateV2'; import { type FieldFilesValue } from '@/object-record/record-field/ui/types/FieldMetadata'; import { FileChip } from '@/ui/field/display/components/FileChip'; import { UploadFileChip } from '@/ui/field/display/components/UploadFileChip'; -import { filePreviewState } from '@/ui/field/display/states/filePreviewState'; +import { filePreviewStateV2 } from '@/ui/field/display/states/filePreviewStateV2'; import { ExpandableList } from '@/ui/layout/expandable-list/components/ExpandableList'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; +import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; import { isDefined } from 'twenty-shared/utils'; type FilesDisplayProps = { @@ -21,9 +22,9 @@ export const FilesDisplay = ({ isUploadWindowOpen = false, isFileUploading = false, }: FilesDisplayProps) => { - const setFilePreview = useSetRecoilState(filePreviewState); - const isAttachmentPreviewEnabled = useRecoilValue( - isAttachmentPreviewEnabledState, + const setFilePreview = useSetRecoilStateV2(filePreviewStateV2); + const isAttachmentPreviewEnabled = useRecoilValueV2( + isAttachmentPreviewEnabledStateV2, ); const handlePreview = (file: FieldFilesValue) => { diff --git a/packages/twenty-front/src/modules/ui/field/display/components/GlobalFilePreviewModal.tsx b/packages/twenty-front/src/modules/ui/field/display/components/GlobalFilePreviewModal.tsx index b8956f18a78..4dff5d7b3d6 100644 --- a/packages/twenty-front/src/modules/ui/field/display/components/GlobalFilePreviewModal.tsx +++ b/packages/twenty-front/src/modules/ui/field/display/components/GlobalFilePreviewModal.tsx @@ -1,13 +1,13 @@ import { downloadFile } from '@/activities/files/utils/downloadFile'; -import { filePreviewState } from '@/ui/field/display/states/filePreviewState'; +import { filePreviewStateV2 } from '@/ui/field/display/states/filePreviewStateV2'; import { Modal } from '@/ui/layout/modal/components/Modal'; import { useModal } from '@/ui/layout/modal/hooks/useModal'; +import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2'; import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper'; import styled from '@emotion/styled'; import { useLingui } from '@lingui/react/macro'; import { lazy, Suspense, useEffect } from 'react'; import { createPortal } from 'react-dom'; -import { useRecoilState } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { IconDownload, IconX } from 'twenty-ui/display'; import { IconButton } from 'twenty-ui/input'; @@ -72,7 +72,7 @@ const StyledLoadingText = styled.div` export const GlobalFilePreviewModal = (): JSX.Element | null => { const { t } = useLingui(); - const [filePreview, setFilePreview] = useRecoilState(filePreviewState); + const [filePreview, setFilePreview] = useRecoilStateV2(filePreviewStateV2); const { openModal, closeModal } = useModal(); useEffect(() => { diff --git a/packages/twenty-front/src/modules/ui/field/display/states/filePreviewStateV2.ts b/packages/twenty-front/src/modules/ui/field/display/states/filePreviewStateV2.ts new file mode 100644 index 00000000000..7d56a73facb --- /dev/null +++ b/packages/twenty-front/src/modules/ui/field/display/states/filePreviewStateV2.ts @@ -0,0 +1,7 @@ +import { type FieldFilesValue } from '@/object-record/record-field/ui/types/FieldMetadata'; +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; + +export const filePreviewStateV2 = createStateV2({ + key: 'filePreviewStateV2', + defaultValue: null, +}); diff --git a/packages/twenty-front/src/modules/ui/utilities/state/jotai/hooks/useFamilyRecoilValueV2.ts b/packages/twenty-front/src/modules/ui/utilities/state/jotai/hooks/useFamilyRecoilValueV2.ts new file mode 100644 index 00000000000..2961e8800f1 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/utilities/state/jotai/hooks/useFamilyRecoilValueV2.ts @@ -0,0 +1,10 @@ +import { useAtomValue } from 'jotai'; + +import { type FamilyStateV2 } from '@/ui/utilities/state/jotai/types/FamilyStateV2'; + +export const useFamilyRecoilValueV2 = ( + familyState: FamilyStateV2, + familyKey: FamilyKey, +): ValueType => { + return useAtomValue(familyState.atomFamily(familyKey)); +}; diff --git a/packages/twenty-front/src/modules/ui/utilities/state/jotai/hooks/useFamilySelectorStateV2.ts b/packages/twenty-front/src/modules/ui/utilities/state/jotai/hooks/useFamilySelectorStateV2.ts new file mode 100644 index 00000000000..69c82361584 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/utilities/state/jotai/hooks/useFamilySelectorStateV2.ts @@ -0,0 +1,13 @@ +import { useAtom } from 'jotai'; + +import { type WritableFamilySelectorV2 } from '@/ui/utilities/state/jotai/types/WritableFamilySelectorV2'; + +export const useFamilySelectorStateV2 = ( + familySelector: WritableFamilySelectorV2, + familyKey: FamilyKey, +): [ + ValueType, + (value: ValueType | ((prev: ValueType) => ValueType)) => void, +] => { + return useAtom(familySelector.selectorFamily(familyKey)); +}; diff --git a/packages/twenty-front/src/modules/ui/utilities/state/jotai/hooks/useFamilySelectorValueV2.ts b/packages/twenty-front/src/modules/ui/utilities/state/jotai/hooks/useFamilySelectorValueV2.ts new file mode 100644 index 00000000000..9013cc7dc40 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/utilities/state/jotai/hooks/useFamilySelectorValueV2.ts @@ -0,0 +1,13 @@ +import { useAtomValue } from 'jotai'; + +import { type FamilySelectorV2 } from '@/ui/utilities/state/jotai/types/FamilySelectorV2'; +import { type WritableFamilySelectorV2 } from '@/ui/utilities/state/jotai/types/WritableFamilySelectorV2'; + +export const useFamilySelectorValueV2 = ( + familySelector: + | FamilySelectorV2 + | WritableFamilySelectorV2, + familyKey: FamilyKey, +): ValueType => { + return useAtomValue(familySelector.selectorFamily(familyKey)); +}; diff --git a/packages/twenty-front/src/modules/ui/utilities/state/jotai/hooks/useRecoilStateV2.ts b/packages/twenty-front/src/modules/ui/utilities/state/jotai/hooks/useRecoilStateV2.ts new file mode 100644 index 00000000000..0929d9f38f7 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/utilities/state/jotai/hooks/useRecoilStateV2.ts @@ -0,0 +1,13 @@ +import { useAtom } from 'jotai'; + +import { type StateV2 } from '@/ui/utilities/state/jotai/types/StateV2'; +import { type WritableSelectorV2 } from '@/ui/utilities/state/jotai/types/WritableSelectorV2'; + +export const useRecoilStateV2 = ( + state: StateV2 | WritableSelectorV2, +): [ + ValueType, + (value: ValueType | ((prev: ValueType) => ValueType)) => void, +] => { + return useAtom(state.atom); +}; diff --git a/packages/twenty-front/src/modules/ui/utilities/state/jotai/hooks/useRecoilValueV2.ts b/packages/twenty-front/src/modules/ui/utilities/state/jotai/hooks/useRecoilValueV2.ts new file mode 100644 index 00000000000..3e2589fc9fb --- /dev/null +++ b/packages/twenty-front/src/modules/ui/utilities/state/jotai/hooks/useRecoilValueV2.ts @@ -0,0 +1,14 @@ +import { useAtomValue } from 'jotai'; + +import { type SelectorV2 } from '@/ui/utilities/state/jotai/types/SelectorV2'; +import { type StateV2 } from '@/ui/utilities/state/jotai/types/StateV2'; +import { type WritableSelectorV2 } from '@/ui/utilities/state/jotai/types/WritableSelectorV2'; + +export const useRecoilValueV2 = ( + state: + | StateV2 + | SelectorV2 + | WritableSelectorV2, +): ValueType => { + return useAtomValue(state.atom); +}; diff --git a/packages/twenty-front/src/modules/ui/utilities/state/jotai/hooks/useSetFamilyRecoilStateV2.ts b/packages/twenty-front/src/modules/ui/utilities/state/jotai/hooks/useSetFamilyRecoilStateV2.ts new file mode 100644 index 00000000000..ab0ce630ccb --- /dev/null +++ b/packages/twenty-front/src/modules/ui/utilities/state/jotai/hooks/useSetFamilyRecoilStateV2.ts @@ -0,0 +1,10 @@ +import { useSetAtom } from 'jotai'; + +import { type FamilyStateV2 } from '@/ui/utilities/state/jotai/types/FamilyStateV2'; + +export const useSetFamilyRecoilStateV2 = ( + familyState: FamilyStateV2, + familyKey: FamilyKey, +): ((value: ValueType | ((prev: ValueType) => ValueType)) => void) => { + return useSetAtom(familyState.atomFamily(familyKey)); +}; diff --git a/packages/twenty-front/src/modules/ui/utilities/state/jotai/hooks/useSetRecoilStateV2.ts b/packages/twenty-front/src/modules/ui/utilities/state/jotai/hooks/useSetRecoilStateV2.ts new file mode 100644 index 00000000000..f15c1c22907 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/utilities/state/jotai/hooks/useSetRecoilStateV2.ts @@ -0,0 +1,9 @@ +import { useSetAtom } from 'jotai'; + +import { type StateV2 } from '@/ui/utilities/state/jotai/types/StateV2'; + +export const useSetRecoilStateV2 = ( + state: StateV2, +): ((value: ValueType | ((prev: ValueType) => ValueType)) => void) => { + return useSetAtom(state.atom); +}; diff --git a/packages/twenty-front/src/modules/ui/utilities/state/jotai/jotaiStore.ts b/packages/twenty-front/src/modules/ui/utilities/state/jotai/jotaiStore.ts new file mode 100644 index 00000000000..cc3298ce1c9 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/utilities/state/jotai/jotaiStore.ts @@ -0,0 +1,3 @@ +import { createStore } from 'jotai'; + +export const jotaiStore = createStore(); diff --git a/packages/twenty-front/src/modules/ui/utilities/state/jotai/types/FamilySelectorV2.ts b/packages/twenty-front/src/modules/ui/utilities/state/jotai/types/FamilySelectorV2.ts new file mode 100644 index 00000000000..7089b16a068 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/utilities/state/jotai/types/FamilySelectorV2.ts @@ -0,0 +1,7 @@ +import { type Atom } from 'jotai'; + +export type FamilySelectorV2 = { + type: 'FamilySelectorV2'; + key: string; + selectorFamily: (key: FamilyKey) => Atom; +}; diff --git a/packages/twenty-front/src/modules/ui/utilities/state/jotai/types/FamilyStateV2.ts b/packages/twenty-front/src/modules/ui/utilities/state/jotai/types/FamilyStateV2.ts new file mode 100644 index 00000000000..a5600dfd67f --- /dev/null +++ b/packages/twenty-front/src/modules/ui/utilities/state/jotai/types/FamilyStateV2.ts @@ -0,0 +1,13 @@ +import { type WritableAtom } from 'jotai'; + +type JotaiWritableAtom = WritableAtom< + ValueType, + [ValueType | ((prev: ValueType) => ValueType)], + void +>; + +export type FamilyStateV2 = { + type: 'FamilyStateV2'; + key: string; + atomFamily: (key: FamilyKey) => JotaiWritableAtom; +}; diff --git a/packages/twenty-front/src/modules/ui/utilities/state/jotai/types/SelectorCallbacksV2.ts b/packages/twenty-front/src/modules/ui/utilities/state/jotai/types/SelectorCallbacksV2.ts new file mode 100644 index 00000000000..bc92dee8b89 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/utilities/state/jotai/types/SelectorCallbacksV2.ts @@ -0,0 +1,26 @@ +import { type FamilyStateV2 } from '@/ui/utilities/state/jotai/types/FamilyStateV2'; +import { type StateV2 } from '@/ui/utilities/state/jotai/types/StateV2'; + +export type SelectorGetterV2 = { + get: { + (state: StateV2): ValueType; + ( + familyState: FamilyStateV2, + familyKey: FamilyKey, + ): ValueType; + }; +}; + +export type SelectorSetterV2 = SelectorGetterV2 & { + set: { + ( + state: StateV2, + value: ValueType | ((prev: ValueType) => ValueType), + ): void; + ( + familyState: FamilyStateV2, + familyKey: FamilyKey, + value: ValueType | ((prev: ValueType) => ValueType), + ): void; + }; +}; diff --git a/packages/twenty-front/src/modules/ui/utilities/state/jotai/types/SelectorV2.ts b/packages/twenty-front/src/modules/ui/utilities/state/jotai/types/SelectorV2.ts new file mode 100644 index 00000000000..799989c8a3d --- /dev/null +++ b/packages/twenty-front/src/modules/ui/utilities/state/jotai/types/SelectorV2.ts @@ -0,0 +1,7 @@ +import { type Atom } from 'jotai'; + +export type SelectorV2 = { + type: 'SelectorV2'; + key: string; + atom: Atom; +}; diff --git a/packages/twenty-front/src/modules/ui/utilities/state/jotai/types/StateV2.ts b/packages/twenty-front/src/modules/ui/utilities/state/jotai/types/StateV2.ts new file mode 100644 index 00000000000..55913d3f6bc --- /dev/null +++ b/packages/twenty-front/src/modules/ui/utilities/state/jotai/types/StateV2.ts @@ -0,0 +1,11 @@ +import { type WritableAtom } from 'jotai'; + +export type StateV2 = { + type: 'StateV2'; + key: string; + atom: WritableAtom< + ValueType, + [ValueType | ((prev: ValueType) => ValueType)], + void + >; +}; diff --git a/packages/twenty-front/src/modules/ui/utilities/state/jotai/types/WritableFamilySelectorV2.ts b/packages/twenty-front/src/modules/ui/utilities/state/jotai/types/WritableFamilySelectorV2.ts new file mode 100644 index 00000000000..20833f972d5 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/utilities/state/jotai/types/WritableFamilySelectorV2.ts @@ -0,0 +1,13 @@ +import { type WritableAtom } from 'jotai'; + +export type WritableFamilySelectorV2 = { + type: 'WritableFamilySelectorV2'; + key: string; + selectorFamily: ( + key: FamilyKey, + ) => WritableAtom< + ValueType, + [ValueType | ((prev: ValueType) => ValueType)], + void + >; +}; diff --git a/packages/twenty-front/src/modules/ui/utilities/state/jotai/types/WritableSelectorV2.ts b/packages/twenty-front/src/modules/ui/utilities/state/jotai/types/WritableSelectorV2.ts new file mode 100644 index 00000000000..47f6c6db2c5 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/utilities/state/jotai/types/WritableSelectorV2.ts @@ -0,0 +1,11 @@ +import { type WritableAtom } from 'jotai'; + +export type WritableSelectorV2 = { + type: 'WritableSelectorV2'; + key: string; + atom: WritableAtom< + ValueType, + [ValueType | ((prev: ValueType) => ValueType)], + void + >; +}; diff --git a/packages/twenty-front/src/modules/ui/utilities/state/jotai/utils/buildGetHelper.ts b/packages/twenty-front/src/modules/ui/utilities/state/jotai/utils/buildGetHelper.ts new file mode 100644 index 00000000000..f3f98129181 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/utilities/state/jotai/utils/buildGetHelper.ts @@ -0,0 +1,21 @@ +import { type Getter } from 'jotai'; + +import { type FamilyStateV2 } from '@/ui/utilities/state/jotai/types/FamilyStateV2'; +import { type StateV2 } from '@/ui/utilities/state/jotai/types/StateV2'; + +export const buildGetHelper = + (jotaiGet: Getter) => + ( + stateOrFamily: StateV2 | FamilyStateV2, + familyKey?: FamilyKey, + ): ValueType => { + if (stateOrFamily.type === 'FamilyStateV2') { + return jotaiGet( + (stateOrFamily as FamilyStateV2).atomFamily( + familyKey as FamilyKey, + ), + ); + } + + return jotaiGet((stateOrFamily as StateV2).atom); + }; diff --git a/packages/twenty-front/src/modules/ui/utilities/state/jotai/utils/buildSetHelper.ts b/packages/twenty-front/src/modules/ui/utilities/state/jotai/utils/buildSetHelper.ts new file mode 100644 index 00000000000..7b0d7d057eb --- /dev/null +++ b/packages/twenty-front/src/modules/ui/utilities/state/jotai/utils/buildSetHelper.ts @@ -0,0 +1,27 @@ +import { type Setter } from 'jotai'; + +import { type FamilyStateV2 } from '@/ui/utilities/state/jotai/types/FamilyStateV2'; +import { type StateV2 } from '@/ui/utilities/state/jotai/types/StateV2'; + +export const buildSetHelper = + (jotaiSet: Setter) => + ( + stateOrFamily: StateV2 | FamilyStateV2, + valueOrFamilyKey: ValueType | ((prev: ValueType) => ValueType) | FamilyKey, + familyValue?: ValueType | ((prev: ValueType) => ValueType), + ): void => { + if (stateOrFamily.type === 'FamilyStateV2') { + jotaiSet( + (stateOrFamily as FamilyStateV2).atomFamily( + valueOrFamilyKey as FamilyKey, + ), + familyValue as ValueType | ((prev: ValueType) => ValueType), + ); + return; + } + + jotaiSet( + (stateOrFamily as StateV2).atom, + valueOrFamilyKey as ValueType | ((prev: ValueType) => ValueType), + ); + }; diff --git a/packages/twenty-front/src/modules/ui/utilities/state/jotai/utils/createFamilySelectorV2.ts b/packages/twenty-front/src/modules/ui/utilities/state/jotai/utils/createFamilySelectorV2.ts new file mode 100644 index 00000000000..87601a7d5c8 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/utilities/state/jotai/utils/createFamilySelectorV2.ts @@ -0,0 +1,45 @@ +import { atom, type Atom } from 'jotai'; + +import { type FamilySelectorV2 } from '@/ui/utilities/state/jotai/types/FamilySelectorV2'; +import { type SelectorGetterV2 } from '@/ui/utilities/state/jotai/types/SelectorCallbacksV2'; +import { buildGetHelper } from '@/ui/utilities/state/jotai/utils/buildGetHelper'; + +export const createFamilySelectorV2 = ({ + key, + get, +}: { + key: string; + get: (familyKey: FamilyKey) => (callbacks: SelectorGetterV2) => ValueType; +}): FamilySelectorV2 => { + const atomCache = new Map>(); + + const selectorFamily = (familyKey: FamilyKey): Atom => { + const cacheKey = + typeof familyKey === 'string' ? familyKey : JSON.stringify(familyKey); + + const existing = atomCache.get(cacheKey); + + if (existing !== undefined) { + return existing; + } + + const getForKey = get(familyKey); + + const derivedAtom = atom((jotaiGet) => { + const getHelper = buildGetHelper(jotaiGet); + + return getForKey({ get: getHelper }); + }); + + derivedAtom.debugLabel = `${key}__${cacheKey}`; + atomCache.set(cacheKey, derivedAtom); + + return derivedAtom; + }; + + return { + type: 'FamilySelectorV2', + key, + selectorFamily, + }; +}; diff --git a/packages/twenty-front/src/modules/ui/utilities/state/jotai/utils/createFamilyStateV2.ts b/packages/twenty-front/src/modules/ui/utilities/state/jotai/utils/createFamilyStateV2.ts new file mode 100644 index 00000000000..c6bd2597074 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/utilities/state/jotai/utils/createFamilyStateV2.ts @@ -0,0 +1,41 @@ +import { atom } from 'jotai'; + +import { type FamilyStateV2 } from '@/ui/utilities/state/jotai/types/FamilyStateV2'; + +export const createFamilyStateV2 = ({ + key, + defaultValue, +}: { + key: string; + defaultValue: ValueType; +}): FamilyStateV2 => { + const atomCache = new Map< + string, + ReturnType['atomFamily']> + >(); + + const familyFunction = ( + familyKey: FamilyKey, + ): ReturnType['atomFamily']> => { + const cacheKey = + typeof familyKey === 'string' ? familyKey : JSON.stringify(familyKey); + + const existing = atomCache.get(cacheKey); + + if (existing !== undefined) { + return existing; + } + + const baseAtom = atom(defaultValue); + baseAtom.debugLabel = `${key}__${cacheKey}`; + atomCache.set(cacheKey, baseAtom); + + return baseAtom; + }; + + return { + type: 'FamilyStateV2', + key, + atomFamily: familyFunction, + }; +}; diff --git a/packages/twenty-front/src/modules/ui/utilities/state/jotai/utils/createSelectorV2.ts b/packages/twenty-front/src/modules/ui/utilities/state/jotai/utils/createSelectorV2.ts new file mode 100644 index 00000000000..ae4558051a5 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/utilities/state/jotai/utils/createSelectorV2.ts @@ -0,0 +1,27 @@ +import { atom } from 'jotai'; + +import { type SelectorGetterV2 } from '@/ui/utilities/state/jotai/types/SelectorCallbacksV2'; +import { type SelectorV2 } from '@/ui/utilities/state/jotai/types/SelectorV2'; +import { buildGetHelper } from '@/ui/utilities/state/jotai/utils/buildGetHelper'; + +export const createSelectorV2 = ({ + key, + get, +}: { + key: string; + get: (callbacks: SelectorGetterV2) => ValueType; +}): SelectorV2 => { + const derivedAtom = atom((jotaiGet) => { + const getHelper = buildGetHelper(jotaiGet); + + return get({ get: getHelper }); + }); + + derivedAtom.debugLabel = key; + + return { + type: 'SelectorV2', + key, + atom: derivedAtom, + }; +}; diff --git a/packages/twenty-front/src/modules/ui/utilities/state/jotai/utils/createStateV2.ts b/packages/twenty-front/src/modules/ui/utilities/state/jotai/utils/createStateV2.ts new file mode 100644 index 00000000000..65e72cedfa7 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/utilities/state/jotai/utils/createStateV2.ts @@ -0,0 +1,20 @@ +import { atom } from 'jotai'; + +import { type StateV2 } from '@/ui/utilities/state/jotai/types/StateV2'; + +export const createStateV2 = ({ + key, + defaultValue, +}: { + key: string; + defaultValue: ValueType; +}): StateV2 => { + const baseAtom = atom(defaultValue); + baseAtom.debugLabel = key; + + return { + type: 'StateV2', + key, + atom: baseAtom, + }; +}; diff --git a/packages/twenty-front/src/modules/ui/utilities/state/jotai/utils/createWritableFamilySelectorV2.ts b/packages/twenty-front/src/modules/ui/utilities/state/jotai/utils/createWritableFamilySelectorV2.ts new file mode 100644 index 00000000000..b4c46ee448e --- /dev/null +++ b/packages/twenty-front/src/modules/ui/utilities/state/jotai/utils/createWritableFamilySelectorV2.ts @@ -0,0 +1,86 @@ +import { atom, type WritableAtom } from 'jotai'; + +import { + type SelectorGetterV2, + type SelectorSetterV2, +} from '@/ui/utilities/state/jotai/types/SelectorCallbacksV2'; +import { type WritableFamilySelectorV2 } from '@/ui/utilities/state/jotai/types/WritableFamilySelectorV2'; +import { buildGetHelper } from '@/ui/utilities/state/jotai/utils/buildGetHelper'; +import { buildSetHelper } from '@/ui/utilities/state/jotai/utils/buildSetHelper'; + +export const createWritableFamilySelectorV2 = ({ + key, + get, + set, +}: { + key: string; + get: (familyKey: FamilyKey) => (callbacks: SelectorGetterV2) => ValueType; + set: ( + familyKey: FamilyKey, + ) => (callbacks: SelectorSetterV2, newValue: ValueType) => void; +}): WritableFamilySelectorV2 => { + const atomCache = new Map< + string, + WritableAtom< + ValueType, + [ValueType | ((prev: ValueType) => ValueType)], + void + > + >(); + + const selectorFamily = ( + familyKey: FamilyKey, + ): WritableAtom< + ValueType, + [ValueType | ((prev: ValueType) => ValueType)], + void + > => { + const cacheKey = + typeof familyKey === 'string' ? familyKey : JSON.stringify(familyKey); + + const existing = atomCache.get(cacheKey); + + if (existing !== undefined) { + return existing; + } + + const getForKey = get(familyKey); + const setForKey = set(familyKey); + + const derivedAtom = atom( + (jotaiGet) => { + const getHelper = buildGetHelper(jotaiGet); + + return getForKey({ get: getHelper }); + }, + ( + jotaiGet, + jotaiSet, + valueOrUpdater: ValueType | ((prev: ValueType) => ValueType), + ) => { + const getHelper = buildGetHelper(jotaiGet); + const setHelper = buildSetHelper(jotaiSet); + + const resolvedValue = + typeof valueOrUpdater === 'function' + ? (valueOrUpdater as (prev: ValueType) => ValueType)( + getForKey({ get: getHelper }), + ) + : valueOrUpdater; + + setForKey({ get: getHelper, set: setHelper }, resolvedValue); + }, + ); + + derivedAtom.debugLabel = `${key}__${cacheKey}`; + atomCache.set(cacheKey, derivedAtom); + + return derivedAtom; + }; + + return { + type: 'WritableFamilySelectorV2', + key, + selectorFamily, + }; +}; diff --git a/packages/twenty-front/src/modules/ui/utilities/state/jotai/utils/createWritableSelectorV2.ts b/packages/twenty-front/src/modules/ui/utilities/state/jotai/utils/createWritableSelectorV2.ts new file mode 100644 index 00000000000..c10ca3c0ec9 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/utilities/state/jotai/utils/createWritableSelectorV2.ts @@ -0,0 +1,52 @@ +import { atom } from 'jotai'; + +import { + type SelectorGetterV2, + type SelectorSetterV2, +} from '@/ui/utilities/state/jotai/types/SelectorCallbacksV2'; +import { type WritableSelectorV2 } from '@/ui/utilities/state/jotai/types/WritableSelectorV2'; +import { buildGetHelper } from '@/ui/utilities/state/jotai/utils/buildGetHelper'; +import { buildSetHelper } from '@/ui/utilities/state/jotai/utils/buildSetHelper'; + +export const createWritableSelectorV2 = ({ + key, + get, + set, +}: { + key: string; + get: (callbacks: SelectorGetterV2) => ValueType; + set: (callbacks: SelectorSetterV2, newValue: ValueType) => void; +}): WritableSelectorV2 => { + const derivedAtom = atom( + (jotaiGet) => { + const getHelper = buildGetHelper(jotaiGet); + + return get({ get: getHelper }); + }, + ( + jotaiGet, + jotaiSet, + valueOrUpdater: ValueType | ((prev: ValueType) => ValueType), + ) => { + const getHelper = buildGetHelper(jotaiGet); + const setHelper = buildSetHelper(jotaiSet); + + const resolvedValue = + typeof valueOrUpdater === 'function' + ? (valueOrUpdater as (prev: ValueType) => ValueType)( + get({ get: getHelper }), + ) + : valueOrUpdater; + + set({ get: getHelper, set: setHelper }, resolvedValue); + }, + ); + + derivedAtom.debugLabel = key; + + return { + type: 'WritableSelectorV2', + key, + atom: derivedAtom, + }; +}; diff --git a/packages/twenty-front/src/modules/users/components/MetadataProviderEffect.tsx b/packages/twenty-front/src/modules/users/components/MetadataProviderEffect.tsx index fb91302ddc4..7fdb9c77617 100644 --- a/packages/twenty-front/src/modules/users/components/MetadataProviderEffect.tsx +++ b/packages/twenty-front/src/modules/users/components/MetadataProviderEffect.tsx @@ -15,6 +15,7 @@ import { type PageLayout } from '@/page-layout/types/PageLayout'; import { transformPageLayout } from '@/page-layout/utils/transformPageLayout'; import { logicFunctionsState } from '@/settings/logic-functions/states/logicFunctionsState'; import { getDateFnsLocale } from '@/ui/field/display/utils/getDateFnsLocale.util'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { coreViewsState } from '@/views/states/coreViewState'; import { type CoreViewWithRelations } from '@/views/types/CoreViewWithRelations'; import { type ColorScheme } from '@/workspace-member/types/WorkspaceMember'; @@ -32,6 +33,7 @@ import { useFindManyLogicFunctionsQuery, } from '~/generated-metadata/graphql'; import { dateLocaleState } from '~/localization/states/dateLocaleState'; +import { dateLocaleStateV2 } from '~/localization/states/dateLocaleStateV2'; import { dynamicActivate } from '~/utils/i18n/dynamicActivate'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; import { isMatchingLocation } from '~/utils/isMatchingLocation'; @@ -61,10 +63,12 @@ export const MetadataProviderEffect = () => { const localeValue = snapshot.getLoadable(dateLocaleState).getValue(); if (localeValue.locale !== newLocale) { getDateFnsLocale(newLocale).then((localeCatalog) => { - set(dateLocaleState, { + const newValue = { locale: newLocale, localeCatalog: localeCatalog || enUS, - }); + }; + set(dateLocaleState, newValue); + jotaiStore.set(dateLocaleStateV2.atom, newValue); }); } }, diff --git a/packages/twenty-front/src/pages/settings/profile/appearance/components/LocalePicker.tsx b/packages/twenty-front/src/pages/settings/profile/appearance/components/LocalePicker.tsx index 18cdef9322e..8e59c0df97f 100644 --- a/packages/twenty-front/src/pages/settings/profile/appearance/components/LocalePicker.tsx +++ b/packages/twenty-front/src/pages/settings/profile/appearance/components/LocalePicker.tsx @@ -8,12 +8,14 @@ import { getDateFnsLocale } from '@/ui/field/display/utils/getDateFnsLocale.util import { Select } from '@/ui/input/components/Select'; import { useRefreshObjectMetadataItems } from '@/object-metadata/hooks/useRefreshObjectMetadataItems'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { useRefreshAllCoreViews } from '@/views/hooks/useRefreshAllCoreViews'; import { useLingui } from '@lingui/react/macro'; import { enUS } from 'date-fns/locale'; import { APP_LOCALES } from 'twenty-shared/translations'; import { isDefined } from 'twenty-shared/utils'; import { dateLocaleState } from '~/localization/states/dateLocaleState'; +import { dateLocaleStateV2 } from '~/localization/states/dateLocaleStateV2'; import { dynamicActivate } from '~/utils/i18n/dynamicActivate'; import { logError } from '~/utils/logError'; @@ -63,10 +65,12 @@ export const LocalePicker = () => { await updateWorkspaceMember({ locale: value }); const dateFnsLocale = await getDateFnsLocale(value); - setDateLocale({ + const newDateLocale = { locale: value, localeCatalog: dateFnsLocale || enUS, - }); + }; + setDateLocale(newDateLocale); + jotaiStore.set(dateLocaleStateV2.atom, newDateLocale); await dynamicActivate(value); try { diff --git a/packages/twenty-front/src/testing/decorators/PageDecorator.tsx b/packages/twenty-front/src/testing/decorators/PageDecorator.tsx index 8d3f1398c4d..62615610df5 100644 --- a/packages/twenty-front/src/testing/decorators/PageDecorator.tsx +++ b/packages/twenty-front/src/testing/decorators/PageDecorator.tsx @@ -1,6 +1,7 @@ import { ApolloProvider } from '@apollo/client'; import { loadDevMessages } from '@apollo/client/dev'; import { type Decorator } from '@storybook/react-vite'; +import { Provider as JotaiProvider } from 'jotai'; import { HelmetProvider } from 'react-helmet-async'; import { createMemoryRouter, @@ -15,6 +16,7 @@ import { ClientConfigProviderEffect } from '@/client-config/components/ClientCon import { ApolloCoreClientMockedProvider } from '@/object-metadata/hooks/__mocks__/ApolloCoreClientMockedProvider'; import { DefaultLayout } from '@/ui/layout/page/components/DefaultLayout'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { MetadataProviderEffect } from '@/users/components/MetadataProviderEffect'; import { ClientConfigProvider } from '~/modules/client-config/components/ClientConfigProvider'; import { UserProvider } from '~/modules/users/components/UserProvider'; @@ -75,42 +77,44 @@ await dynamicActivate(SOURCE_LOCALE); const Providers = () => { return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); }; diff --git a/packages/twenty-front/src/testing/decorators/RootDecorator.tsx b/packages/twenty-front/src/testing/decorators/RootDecorator.tsx index de5b883f2c8..0e5f51ac70c 100644 --- a/packages/twenty-front/src/testing/decorators/RootDecorator.tsx +++ b/packages/twenty-front/src/testing/decorators/RootDecorator.tsx @@ -1,8 +1,10 @@ import { ApolloProvider } from '@apollo/client'; import { type Decorator } from '@storybook/react-vite'; +import { Provider as JotaiProvider } from 'jotai'; import { RecoilRoot } from 'recoil'; import { ApolloCoreClientMockedProvider } from '@/object-metadata/hooks/__mocks__/ApolloCoreClientMockedProvider'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { mockedApolloClient } from '~/testing/mockedApolloClient'; @@ -10,12 +12,14 @@ export const RootDecorator: Decorator = (Story, context) => { const { parameters } = context; return ( - - - - - - - + + + + + + + + + ); }; diff --git a/packages/twenty-front/src/testing/jest/getJestMetadataAndApolloMocksWrapper.tsx b/packages/twenty-front/src/testing/jest/getJestMetadataAndApolloMocksWrapper.tsx index 0163eadff60..4de1a5669bc 100644 --- a/packages/twenty-front/src/testing/jest/getJestMetadataAndApolloMocksWrapper.tsx +++ b/packages/twenty-front/src/testing/jest/getJestMetadataAndApolloMocksWrapper.tsx @@ -1,4 +1,5 @@ import { MockedProvider, type MockedResponse } from '@apollo/client/testing'; +import { Provider as JotaiProvider } from 'jotai'; import { type ReactNode } from 'react'; import { RecoilRoot, type MutableSnapshot } from 'recoil'; @@ -7,6 +8,7 @@ import { ContextStoreComponentInstanceContext } from '@/context-store/states/con import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { RecordComponentInstanceContextsWrapper } from '@/object-record/components/RecordComponentInstanceContextsWrapper'; import { SnackBarComponentInstanceContext } from '@/ui/feedback/snack-bar-manager/contexts/SnackBarComponentInstanceContext'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext'; import { type InMemoryCache } from '@apollo/client'; import { JestContextStoreSetter } from '~/testing/jest/JestContextStoreSetter'; @@ -26,28 +28,30 @@ export const getJestMetadataAndApolloMocksWrapper = ({ objectMetadataItems?: ObjectMetadataItem[]; }) => { return ({ children }: { children: ReactNode }) => ( - - - - - - + + + + + - - {children} - - - - - - - + + {children} + + + + + + + + ); }; diff --git a/packages/twenty-ui/package.json b/packages/twenty-ui/package.json index a158459d823..a2b3c3ff1e9 100644 --- a/packages/twenty-ui/package.json +++ b/packages/twenty-ui/package.json @@ -33,6 +33,7 @@ "framer-motion": "^11.18.0", "glob": "^11.1.0", "hex-rgb": "^5.0.0", + "jotai": "^2.17.1", "react": "^18.2.0", "react-dom": "^18.2.0", "react-responsive": "^9.0.2", diff --git a/packages/twenty-ui/src/display/avatar/components/Avatar.tsx b/packages/twenty-ui/src/display/avatar/components/Avatar.tsx index 2b8b8beeb54..3e5c4862176 100644 --- a/packages/twenty-ui/src/display/avatar/components/Avatar.tsx +++ b/packages/twenty-ui/src/display/avatar/components/Avatar.tsx @@ -1,8 +1,9 @@ import { styled } from '@linaria/react'; import { isNonEmptyString, isNull, isUndefined } from '@sniptt/guards'; +import { useAtom } from 'jotai'; import { useContext } from 'react'; -import { invalidAvatarUrlsState } from '@ui/display/avatar/components/states/isInvalidAvatarUrlState'; +import { invalidAvatarUrlsAtomV2 } from '@ui/display/avatar/components/states/invalidAvatarUrlsAtomV2'; import { AVATAR_PROPERTIES_BY_SIZE } from '@ui/display/avatar/constants/AvatarPropertiesBySize'; import { type AvatarSize } from '@ui/display/avatar/types/AvatarSize'; import { type AvatarType } from '@ui/display/avatar/types/AvatarType'; @@ -10,7 +11,6 @@ import { type IconComponent } from '@ui/display/icon/types/IconComponent'; import { ThemeContext } from '@ui/theme'; import { stringToThemeColorP3String } from '@ui/utilities'; import { REACT_APP_SERVER_BASE_URL } from '@ui/utilities/config'; -import { useRecoilState } from 'recoil'; import { type Nullable } from 'twenty-shared/types'; import { getImageAbsoluteURI } from 'twenty-shared/utils'; @@ -72,7 +72,6 @@ export type AvatarProps = { onClick?: () => void; }; -// TODO: Remove recoil because we don't want it into twenty-ui and find a solution for invalid avatar urls export const Avatar = ({ avatarUrl, size = 'md', @@ -86,8 +85,8 @@ export const Avatar = ({ backgroundColor, }: AvatarProps) => { const { theme } = useContext(ThemeContext); - const [invalidAvatarUrls, setInvalidAvatarUrls] = useRecoilState( - invalidAvatarUrlsState, + const [invalidAvatarUrls, setInvalidAvatarUrls] = useAtom( + invalidAvatarUrlsAtomV2, ); const avatarImageURI = isNonEmptyString(avatarUrl) diff --git a/packages/twenty-ui/src/display/avatar/components/states/invalidAvatarUrlsAtomV2.ts b/packages/twenty-ui/src/display/avatar/components/states/invalidAvatarUrlsAtomV2.ts new file mode 100644 index 00000000000..94ae8d03b3c --- /dev/null +++ b/packages/twenty-ui/src/display/avatar/components/states/invalidAvatarUrlsAtomV2.ts @@ -0,0 +1,4 @@ +import { atom } from 'jotai'; + +export const invalidAvatarUrlsAtomV2 = atom([]); +invalidAvatarUrlsAtomV2.debugLabel = 'invalidAvatarUrlsAtomV2'; diff --git a/packages/twenty-ui/src/display/index.ts b/packages/twenty-ui/src/display/index.ts index 7757562ddf4..2f0dc3d49c1 100644 --- a/packages/twenty-ui/src/display/index.ts +++ b/packages/twenty-ui/src/display/index.ts @@ -11,6 +11,7 @@ export type { AvatarProps } from './avatar/components/Avatar'; export { Avatar } from './avatar/components/Avatar'; export type { AvatarGroupProps } from './avatar/components/AvatarGroup'; export { AvatarGroup } from './avatar/components/AvatarGroup'; +export { invalidAvatarUrlsAtomV2 } from './avatar/components/states/invalidAvatarUrlsAtomV2'; export { invalidAvatarUrlsState } from './avatar/components/states/isInvalidAvatarUrlState'; export { AVATAR_PROPERTIES_BY_SIZE } from './avatar/constants/AvatarPropertiesBySize'; export type { AvatarSize } from './avatar/types/AvatarSize'; diff --git a/yarn.lock b/yarn.lock index 493340049da..eae48f05b0c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -41855,6 +41855,27 @@ __metadata: languageName: node linkType: hard +"jotai@npm:^2.17.1": + version: 2.17.1 + resolution: "jotai@npm:2.17.1" + peerDependencies: + "@babel/core": ">=7.0.0" + "@babel/template": ">=7.0.0" + "@types/react": ">=17.0.0" + react: ">=17.0.0" + peerDependenciesMeta: + "@babel/core": + optional: true + "@babel/template": + optional: true + "@types/react": + optional: true + react: + optional: true + checksum: 10c0/45e52a77a7d33d0947f3ceafe8930a161e170e1d28e934572a325267fdc0177f766761f06b38b884955f468deb27ebb530646658566ea5bbba68991317fb09a4 + languageName: node + linkType: hard + "js-cookie@npm:^3.0.5": version: 3.0.5 resolution: "js-cookie@npm:3.0.5" @@ -56895,6 +56916,7 @@ __metadata: graphql: "npm:16.8.1" graphql-sse: "npm:^2.5.4" input-otp: "npm:^1.4.2" + jotai: "npm:^2.17.1" js-cookie: "npm:^3.0.5" json-2-csv: "npm:^5.4.0" json-logic-js: "npm:^2.0.5" @@ -57257,6 +57279,7 @@ __metadata: framer-motion: "npm:^11.18.0" glob: "npm:^11.1.0" hex-rgb: "npm:^5.0.0" + jotai: "npm:^2.17.1" react: "npm:^18.2.0" react-dom: "npm:^18.2.0" react-responsive: "npm:^9.0.2" @@ -57451,6 +57474,7 @@ __metadata: jest-environment-jsdom: "npm:30.0.0-beta.3" jest-environment-node: "npm:^29.4.1" jest-fetch-mock: "npm:^3.0.3" + jotai: "npm:^2.17.1" jsdom: "npm:~22.1.0" libphonenumber-js: "npm:^1.10.26" lodash.camelcase: "npm:^4.3.0"