From b51eb6471f0ae6e88a96a7157d03c8d056e2f38a Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Mon, 23 Feb 2026 12:01:37 +0100 Subject: [PATCH] Jotai 12 (#18160) Co-authored-by: Cursor --- packages/twenty-front/jest.config.mjs | 2 +- ...sePageChangeEffectNavigateLocation.test.ts | 6 +- .../usePageChangeEffectNavigateLocation.ts | 10 +- .../contexts/ActionMenuContextProvider.tsx | 4 +- .../useShouldActionBeRegisteredParams.ts | 46 ++- .../calendar/components/CalendarEventRow.tsx | 4 +- .../files/components/AttachmentList.tsx | 4 +- .../useActivityTargetObjectRecords.test.tsx | 44 +-- .../useOpenCreateActivityDrawer.test.tsx | 15 +- .../hooks/useActivityTargetObjectRecords.ts | 3 +- .../useActivityTargetsForTargetableObjects.ts | 4 +- .../useOpenActivityTargetCellEditMode.ts | 8 +- .../hooks/useUpdateActivityTargetFromCell.ts | 8 +- .../components/EventRow.tsx | 5 +- .../useLinkedObjectObjectMetadataItem.ts | 5 +- .../src/modules/ai/components/AIChatTab.tsx | 4 +- .../src/modules/ai/hooks/useAgentChat.ts | 4 +- .../modules/ai/hooks/useBrowsingContext.ts | 16 +- .../modules/apollo/hooks/useApolloFactory.ts | 33 +- .../src/modules/app/components/AppRouter.tsx | 4 +- .../app/components/AppRouterProviders.tsx | 6 +- .../effect-components/PageChangeEffect.tsx | 22 +- .../app/hooks/useInitializeQueryParamState.ts | 89 +++-- .../src/modules/app/hooks/useMetadataStore.ts | 84 +++++ .../states/isAppEffectRedirectEnabledState.ts | 4 +- .../modules/app/states/isAppLoadingState.ts | 12 + .../modules/app/states/metadataStoreState.ts | 29 ++ .../states/verifyEmailRedirectPathState.ts | 4 +- .../modules/auth/components/AuthProvider.tsx | 8 +- ...TwoFactorAuthenticationProvisionEffect.tsx | 3 +- .../auth/components/VerifyEmailEffect.tsx | 7 +- .../components/VerifyLoginTokenEffect.tsx | 4 +- .../auth/hooks/__tests__/useAuth.test.tsx | 13 +- .../auth/hooks/__tests__/useIsLogged.test.ts | 5 +- .../src/modules/auth/hooks/useAuth.ts | 152 +++++---- .../src/modules/auth/hooks/useIsLogged.ts | 5 +- .../components/EmailVerificationSent.tsx | 4 +- .../components/SignInUpGlobalScopeForm.tsx | 5 +- .../components/SignInUpWorkspaceScopeForm.tsx | 6 +- .../SignInUpGlobalScopeFormEffect.tsx | 4 +- .../SignInUpSSOIdentityProviderSelection.tsx | 4 +- ...gnInUpTwoFactorAuthenticationProvision.tsx | 4 +- ...nUpTwoFactorAuthenticationVerification.tsx | 7 +- .../internal/SignInUpWithCredentials.tsx | 13 +- .../internal/SignInUpWithGoogle.tsx | 10 +- .../internal/SignInUpWithMicrosoft.tsx | 10 +- .../components/internal/SignInUpWithSSO.tsx | 15 +- .../SignInUpWorkspaceScopeFormEffect.tsx | 11 +- .../__tests__/useHandleResetPassword.test.ts | 26 +- .../hooks/__tests__/useSignInUpForm.test.tsx | 33 +- .../hooks/useHandleResetPassword.ts | 6 +- .../hooks/useHasMultipleAuthMethods.ts | 4 +- .../auth/sign-in-up/hooks/useSignInUp.ts | 6 +- .../auth/sign-in-up/hooks/useSignInUpForm.ts | 6 +- .../hooks/useSignInWithMicrosoft.ts | 4 +- .../sign-in-up/hooks/useWorkspaceBypass.ts | 10 +- .../hooks/useWorkspaceFromInviteHash.ts | 4 +- .../auth/states/availableWorkspacesState.ts | 4 +- .../states/billingCheckoutSessionState.ts | 11 +- .../modules/auth/states/currentUserState.ts | 4 +- .../auth/states/currentUserWorkspaceState.ts | 4 +- .../currentWorkspaceDeletedMembersState.ts | 4 +- .../states/currentWorkspaceMemberState.ts | 4 +- .../states/currentWorkspaceMembersState.ts | 4 +- .../auth/states/currentWorkspaceState.ts | 4 +- .../auth/states/isCurrentUserLoadedState.ts | 4 +- .../auth/states/isImpersonatingState.ts | 7 +- .../states/lastAuthenticatedMethodState.ts | 17 +- .../modules/auth/states/loginTokenState.ts | 4 +- .../states/objectPermissionsFamilySelector.ts | 4 +- .../modules/auth/states/signInUpStepState.ts | 4 +- .../src/modules/auth/states/tokenPairState.ts | 20 +- .../auth/states/workspacePublicDataState.ts | 4 +- .../components/SettingsBillingContent.tsx | 4 +- .../SettingsBillingSubscriptionInfo.tsx | 4 +- .../internal/MeteredPriceSelector.tsx | 4 +- .../billing/hooks/useBillingWording.ts | 4 +- .../billing/hooks/useCurrentBillingFlags.ts | 4 +- .../billing/hooks/useCurrentMetered.ts | 4 +- .../modules/billing/hooks/useCurrentPlan.ts | 4 +- .../hooks/useEndSubscriptionTrialPeriod.ts | 4 +- .../billing/hooks/useHasNextBillingPhase.ts | 4 +- .../billing/hooks/useNextBillingPhase.ts | 4 +- .../blocks/MentionInlineContent.tsx | 4 +- .../CaptchaProviderScriptLoaderEffect.tsx | 12 +- .../__tests__/useReadCaptchaToken.test.tsx | 37 +- .../useRequestFreshCaptchaToken.test.tsx | 55 ++- .../captcha/hooks/useReadCaptchaToken.ts | 22 +- .../hooks/useRequestFreshCaptchaToken.ts | 81 +++-- .../captcha/states/captchaTokenState.ts | 4 +- .../states/isCaptchaScriptLoadedState.ts | 4 +- .../states/isRequestingCaptchaTokenState.ts | 4 +- .../ChromeExtensionSidecarEffect.tsx | 15 +- .../isLoadingTokensFromExtensionState.ts | 4 +- .../components/ClientConfigProvider.tsx | 5 +- .../components/ClientConfigProviderEffect.tsx | 4 +- .../modules/client-config/hooks/useCaptcha.ts | 10 +- .../client-config/hooks/useClientConfig.ts | 55 ++- .../states/allowRequestsToTwentyIcons.ts | 4 +- .../client-config/states/apiConfigState.ts | 4 +- .../client-config/states/billingState.ts | 4 +- .../states/calendarBookingPageIdState.ts | 4 +- .../states/canManageFeatureFlagsState.ts | 4 +- .../client-config/states/captchaState.ts | 4 +- .../states/clientConfigApiStatusState.ts | 4 +- .../states/isAttachmentPreviewEnabledState.ts | 4 +- .../states/isClickHouseConfiguredState.ts | 4 +- .../isCloudflareIntegrationEnabledState.ts | 4 +- .../isConfigVariablesInDbEnabledState.ts | 4 +- .../isDeveloperDefaultSignInPrefilledState.ts | 4 +- .../isEmailVerificationRequiredState.ts | 4 +- .../states/isEmailingDomainsEnabledState.ts | 4 +- .../states/isGoogleCalendarEnabledState.ts | 4 +- .../states/isGoogleMessagingEnabledState.ts | 4 +- .../states/isImapSmtpCaldavEnabledState.ts | 4 +- .../states/isMicrosoftCalendarEnabledState.ts | 4 +- .../isMicrosoftMessagingEnabledState.ts | 4 +- .../states/isMultiWorkspaceEnabledState.ts | 4 +- .../client-config/states/sentryConfigState.ts | 4 +- .../client-config/states/supportChatState.ts | 4 +- .../components/CommandMenuContainer.tsx | 4 +- .../CommandMenuContextRecordsChip.tsx | 4 +- .../components/CommandMenuRecordInfo.tsx | 4 +- .../ResetContextToSelectionCommandButton.tsx | 4 +- .../__stories__/CommandMenu.stories.tsx | 9 +- .../hooks/useCommandMenuContextChips.tsx | 13 +- .../hooks/useOpenRecordInCommandMenu.ts | 15 +- .../components/CommandMenuObjectMenuItem.tsx | 9 +- .../CommandMenuPageLayoutGraphFilter.tsx | 4 +- .../__tests__/useChartSettingsValues.test.tsx | 26 +- .../hooks/useChartSettingsValues.ts | 4 +- .../hooks/useGraphGroupBySortOptionLabels.ts | 4 +- .../hooks/useGraphXSortOptionLabels.ts | 4 +- .../components/MainContextStoreProvider.tsx | 25 +- .../MainContextStoreProviderEffect.tsx | 10 +- .../useContextStoreObjectMetadataItem.ts | 4 +- ...seFindManyRecordsSelectedInContextStore.ts | 4 +- ...useBuildSearchParamsFromUrlSyncedStates.ts | 34 +- .../useGetPublicWorkspaceDataByDomain.ts | 19 +- .../hooks/useIsCurrentLocationOnAWorkspace.ts | 8 +- .../useIsCurrentLocationOnDefaultDomain.ts | 6 +- .../useLastAuthenticatedWorkspaceDomain.ts | 7 +- .../useReadDefaultDomainFromConfiguration.ts | 8 +- .../hooks/useRedirectToWorkspaceDomain.ts | 6 +- .../states/domainConfigurationState.ts | 4 +- .../lastAuthenticatedWorkspaceDomainState.ts | 16 +- .../components/SentryInitEffect.tsx | 13 +- .../CurrentWorkspaceMemberFavorites.tsx | 4 +- ...CurrentWorkspaceMemberFavoritesFolders.tsx | 4 +- .../CurrentWorkspaceMemberOrphanFavorites.tsx | 4 +- .../components/FavoritesFolderContent.tsx | 4 +- .../__tests__/useCreateFavorite.test.tsx | 28 +- .../__tests__/useDeleteFavorite.test.tsx | 28 +- .../hooks/__tests__/useFavorites.test.tsx | 32 +- .../useHandleFavoriteDragAndDrop.test.tsx | 46 ++- .../favorites/hooks/useCreateFavorite.ts | 4 +- .../modules/favorites/hooks/useFavorites.ts | 8 +- .../favorites/hooks/useFavoritesByFolder.ts | 4 +- .../favorites/hooks/useFavoritesMetadata.ts | 6 +- .../hooks/usePrefetchedFavoritesData.ts | 6 +- .../usePrefetchedFavoritesFoldersData.ts | 8 +- .../favorites/hooks/useSortedFavorites.ts | 4 +- .../favorites/hooks/useWorkspaceFavorites.ts | 10 +- .../favoriteViewsWithMinimalDataSelector.ts | 5 +- .../useFrontComponentExecutionContext.ts | 5 +- .../InformationBannerIsImpersonating.tsx | 6 +- .../hooks/useAccountToReconnect.ts | 4 +- .../__tests__/useDateTimeFormat.test.tsx | 36 +- .../__tests__/useFormatPreferences.test.tsx | 58 ++-- .../hooks/__tests__/useNumberFormat.test.tsx | 25 +- .../localization/hooks/useDateTimeFormat.ts | 5 +- .../hooks/useFormatPreferences.ts | 10 +- .../hooks/useInitializeFormatPreferences.ts | 4 +- .../localization/hooks/useNumberFormat.ts | 4 +- .../workspaceMemberFormatPreferencesState.ts | 4 +- ...rkspaceMemberNavigationMenuItemFolders.tsx | 7 +- ...rentWorkspaceMemberNavigationMenuItems.tsx | 4 +- ...rkspaceMemberOrphanNavigationMenuItems.tsx | 4 +- .../NavigationMenuItemDragProvider.tsx | 39 --- .../NavigationMenuItemFolderContent.tsx | 4 +- .../components/NavigationMenuItemIcon.tsx | 4 +- ...spaceNavigationMenuItemFolderDragClone.tsx | 4 +- .../WorkspaceNavigationMenuItems.tsx | 32 +- .../WorkspaceNavigationMenuItemsFolder.tsx | 6 +- .../useAddRecordToNavigationMenuDraft.ts | 4 +- .../hooks/useCreateNavigationMenuItem.ts | 4 +- .../hooks/useHandleAddToNavigationDrop.ts | 4 +- .../hooks/useNavigationMenuItemsByFolder.ts | 6 +- .../hooks/useNavigationMenuItemsDraftState.ts | 3 +- ...seNavigationMenuObjectMetadataFromDraft.ts | 4 +- .../usePrefetchedNavigationMenuItemsData.ts | 8 +- ...emoveNavigationMenuItemByTargetRecordId.ts | 58 ++-- .../hooks/useSaveNavigationMenuItemsDraft.ts | 315 +++++++++--------- ...avigationMenuItemEditItemObjectMetadata.ts | 5 +- .../hooks/useSortedNavigationMenuItems.ts | 6 +- .../hooks/useWorkspaceNavigationMenuItems.ts | 4 +- .../hooks/useWorkspaceSectionItems.ts | 6 +- .../components/MainNavigationDrawer.tsx | 6 +- .../components/MobileNavigationBar.tsx | 3 +- .../AppNavigationDrawer.stories.tsx | 3 +- .../__tests__/useDefaultHomePagePath.test.ts | 39 ++- .../hooks/useDefaultHomePagePath.ts | 66 ++-- .../navigation/hooks/useIsSettingsDrawer.ts | 4 +- .../navigation/hooks/useLastVisitedView.ts | 4 +- .../navigation/hooks/useOpenSettings.ts | 3 +- .../useSetLastVisitedObjectMetadataId.ts | 43 +-- ...tVisitedViewForObjectMetadataNamePlural.ts | 74 ++-- .../currentMobileNavigationDrawerState.ts | 5 +- .../lastVisitedObjectMetadataItemIdState.ts | 9 +- ...stVisitedViewPerObjectMetadataItemState.ts | 7 +- ...igationDrawerItemForObjectMetadataItem.tsx | 12 +- ...vigationDrawerSectionForWorkspaceItems.tsx | 6 +- .../ObjectMetadataItemsLoadEffect.tsx | 9 +- .../ObjectMetadataItemsProvider.tsx | 6 +- .../PreComputedChipGeneratorsProvider.tsx | 6 +- .../RemoteNavigationDrawerSection.tsx | 5 +- ...olumnDefinitionsFromObjectMetadata.test.ts | 15 +- .../useFilteredObjectMetadataItems.test.tsx | 29 +- ...ectRecordIdentifierByNameSingular.test.tsx | 23 +- .../__tests__/useGetRelationMetadata.test.tsx | 40 +-- .../useColumnDefinitionsFromObjectMetadata.ts | 16 +- .../hooks/useDoObjectMetadataItemsExist.ts | 9 +- .../hooks/useFieldMetadataItemById.ts | 4 +- .../hooks/useFieldMetadataItemByIdOrThrow.ts | 4 +- .../hooks/useFilteredObjectMetadataItems.ts | 5 +- .../hooks/useGetFieldMetadataItemById.ts | 9 +- .../hooks/useGetObjectMetadataItemById.ts | 5 +- ...GetObjectRecordIdentifierByNameSingular.ts | 5 +- .../hooks/useGetRelationMetadata.ts | 59 ++-- .../hooks/useLoadMockedObjectMetadataItems.ts | 43 +-- .../hooks/useObjectMetadataItem.ts | 13 +- .../hooks/useObjectMetadataItemById.ts | 5 +- .../hooks/useObjectMetadataItems.ts | 4 +- .../hooks/useObjectNamePluralFromSingular.ts | 10 +- .../hooks/useObjectNameSingularFromPlural.ts | 10 +- .../hooks/useRefreshObjectMetadataItems.ts | 117 +++---- .../hooks/useUpdateOneFieldMetadataItem.ts | 4 +- ...eldMetadataItemsForFilterFamilySelector.ts | 56 ++-- ...FieldMetadataItemsForSortFamilySelector.ts | 41 +-- .../states/fieldMetadataItemByIdSelector.ts | 4 +- .../flattenedFieldMetadataItemsSelector.ts | 8 +- ...nedReadableFieldMetadataItemIdsSelector.ts | 8 +- ...tadataItemFilterableAndSortableSelector.ts | 111 +++--- ...ieldMetadataItemLabelIdentifierSelector.ts | 55 ++- ...abelIdentifierFieldMetadataItemSelector.ts | 17 +- .../lastFieldMetadataItemUpdateState.ts | 4 +- .../objectMetadataItemFamilySelector.ts | 5 +- ...ectMetadataItemsByNamePluralMapSelector.ts | 5 +- ...tMetadataItemsByNameSingularMapSelector.ts | 5 +- ...jectMetadataItemsBySingularNameSelector.ts | 5 +- .../states/objectMetadataItemsState.ts | 4 +- .../states/shouldAppBeLoadingState.ts | 5 +- ...ilterCommandMenuCreateRootFilterButton.tsx | 9 +- ...useGetDefaultFieldMetadataItemForFilter.ts | 48 ++- .../useUpsertFindManyRecordsQueryInCache.ts | 5 +- .../__tests__/useFindManyRecords.test.tsx | 19 +- .../useBuildRecordInputFromRLSPredicates.ts | 4 +- .../hooks/useCreateManyRecords.ts | 4 +- .../hooks/useCreateManyRecordsMutation.ts | 4 +- .../object-record/hooks/useCreateOneRecord.ts | 4 +- .../hooks/useCreateOneRecordMutation.ts | 5 +- .../hooks/useDeleteManyRecords.ts | 4 +- .../hooks/useDestroyManyRecords.ts | 4 +- .../hooks/useFindDuplicatesRecordsQuery.ts | 4 +- .../hooks/useFindManyRecordsQuery.ts | 5 +- .../hooks/useFindOneRecordQuery.ts | 4 +- .../hooks/useGroupByRecordsQuery.ts | 5 +- .../hooks/useMergeManyRecordsMutation.ts | 4 +- .../hooks/useObjectPermissions.ts | 5 +- .../hooks/useObjectRecordSearchRecords.ts | 4 +- .../hooks/useRestoreManyRecords.ts | 4 +- .../hooks/useUpdateManyRecords.ts | 4 +- .../object-record/hooks/useUpdateOneRecord.ts | 4 +- .../useCombinedFindManyRecords.test.tsx | 19 +- ...useGenerateCombinedFindManyRecordsQuery.ts | 4 +- .../ObjectFilterDropdownDateInput.tsx | 3 +- .../ObjectFilterDropdownDateTimeInput.tsx | 3 +- .../ObjectFilterDropdownRecordSelect.tsx | 4 +- ...dataItemUsedInDropdownComponentSelector.ts | 5 +- .../ObjectOptionsDropdownContent.stories.tsx | 13 +- .../useSetViewTypeFromLayoutOptionsMenu.ts | 13 +- .../components/ObjectSortDropdownButton.tsx | 9 +- .../RecordCalendarMonth.stories.tsx | 4 +- .../hooks/useRecordCalendarMonthDaysRange.tsx | 3 +- ...lendarDayRecordsComponentFamilySelector.ts | 5 +- .../RecordDetailRelationSection.tsx | 8 +- .../useFilterVisibleAndReadableRecordField.ts | 4 +- .../visibleRecordFieldsComponentSelector.ts | 3 +- .../record-field/ui/hooks/useClearField.ts | 9 +- .../ui/hooks/useOpenFieldInputEditMode.ts | 11 +- .../useOpenJunctionRelationFieldInput.ts | 7 +- .../RelationManyToOneFieldInput.stories.tsx | 16 +- .../RelationOneToManyFieldInput.stories.tsx | 12 +- ...seOpenMorphRelationOneToManyFieldInput.tsx | 7 +- .../useOpenRelationFromManyFieldInput.tsx | 7 +- .../hooks/useFilterValueDependencies.ts | 17 +- .../hooks/useFilterableFieldMetadataItems.ts | 9 +- ...seGetRelativeDateFilterWithUserTimezone.ts | 4 +- .../states/hasAnySoftDeleteFilterOnView.ts | 5 +- .../record-group/hooks/useSetRecordGroups.ts | 7 +- ...ecordIndexLoadBaseOnContextStoreEffect.tsx | 10 +- .../hooks/useLoadRecordIndexStates.ts | 143 ++++---- ...seRecordIndexFieldMetadataDerivedStates.ts | 9 +- .../record-show/components/SummaryCard.tsx | 3 +- .../recordStoreIdentifierSelector.ts | 3 +- .../recordStoreIdentifiersSelector.ts | 24 +- .../perf/RecordTableCell.perf.stories.tsx | 13 +- .../hooks/useBuildRecordInputFromFilters.ts | 5 +- .../useCloseRecordTableCellInGroup.test.tsx | 81 +++-- .../useCloseRecordTableCellNoGroup.test.tsx | 85 ++--- .../components/RecordTableColumnHead.tsx | 16 +- .../RecordTableColumnHeadDropdownMenu.tsx | 16 +- ...leVirtualizedFieldMetadataUpdateEffect.tsx | 4 +- ...cordTableVirtualizedSSESubscribeEffect.tsx | 21 +- .../useBuildSpreadSheetImportFields.test.tsx | 244 ++++++-------- .../hooks/useBuildSpreadSheetImportFields.ts | 4 +- .../__tests__/useOnboardingStatus.test.ts | 20 +- .../useSetNextOnboardingStatus.test.ts | 29 +- .../onboarding/hooks/useOnboardingStatus.ts | 5 +- .../hooks/useSetNextOnboardingStatus.ts | 51 ++- .../hooks/useIsCurrentObjectCustom.ts | 9 +- .../hooks/useOpportunityDefaultChartConfig.ts | 9 +- .../components/WidgetActionFieldSeeAll.tsx | 9 +- .../DashboardWidgetPlaceholder.stories.tsx | 7 +- .../__stories__/WidgetRenderer.stories.tsx | 104 ++++-- .../__stories__/FieldWidget.stories.tsx | 119 +++++-- .../useOpenFieldWidgetFieldInputEditMode.ts | 7 +- .../GraphWidgetBarChartRenderer.tsx | 12 +- .../hooks/useGraphBarChartWidgetData.ts | 2 +- .../GraphWidgetLineChartRenderer.tsx | 9 +- .../hooks/useGraphLineChartWidgetData.ts | 3 +- .../GraphWidgetPieChartRenderer.tsx | 9 +- .../hooks/useGraphPieChartWidgetData.ts | 3 +- .../hooks/usePieChartCenterMetricData.ts | 3 +- .../hooks/useGraphWidgetAggregateQuery.ts | 2 +- .../graph/hooks/useGraphWidgetQueryCommon.ts | 2 +- .../PrefetchRunFavoriteQueriesEffect.tsx | 85 ++--- ...etchRunNavigationMenuItemQueriesEffect.tsx | 45 +-- .../prefetch/hooks/useIsPrefetchLoading.ts | 12 +- .../states/prefetchFavoriteFoldersState.ts | 4 +- .../prefetch/states/prefetchFavoritesState.ts | 4 +- .../states/prefetchIsLoadedFamilyState.ts | 4 +- .../prefetchNavigationMenuItemsState.ts | 4 +- .../SettingsAccountsBlocklistSection.tsx | 5 +- ...tingsAccountsCalendarChannelsContainer.tsx | 4 +- ...ettingsAccountsCalendarChannelsGeneral.tsx | 4 +- .../SettingsAccountsListEmptyStateCard.tsx | 16 +- ...ttingsAccountsMessageChannelsContainer.tsx | 4 +- .../hooks/useImapSmtpCaldavConnectionForm.ts | 4 +- .../components/SettingsAdminContent.tsx | 4 +- .../components/SettingsAdminGeneral.tsx | 6 +- .../SettingsAdminWorkspaceContent.tsx | 8 +- .../ConfigVariableActionButtons.tsx | 4 +- .../components/ConfigVariableHelpText.tsx | 4 +- .../ConfigVariableOptionsDropdownContent.tsx | 4 +- .../components/ConfigVariableValueInput.tsx | 4 +- .../admin-panel/hooks/useImpersonationAuth.ts | 6 +- .../components/SettingsItemTypeTag.tsx | 4 +- .../SettingsDataModelOverviewField.tsx | 5 +- .../components/SettingsCustomDomain.tsx | 3 +- .../components/SettingsDomainRecords.tsx | 4 +- .../domains/components/SettingsSubdomain.tsx | 6 +- .../SettingsWorkspaceDomainCard.tsx | 8 +- .../hooks/useCheckCustomDomainValidRecords.ts | 5 +- .../components/FormatPreferencesSettings.tsx | 4 +- .../useSettingsNavigationItems.test.tsx | 48 ++- .../hooks/useSettingsNavigationItems.tsx | 10 +- .../lab/components/SettingsLabContent.tsx | 4 +- .../lab/hooks/useLabPublicFeatureFlags.ts | 4 +- .../profile/components/DeleteAccount.tsx | 9 +- .../profile/components/DeleteWorkspace.tsx | 4 +- .../profile/components/EmailField.tsx | 4 +- .../profile/components/NameFields.tsx | 7 +- .../components/SetOrChangePassword.tsx | 4 +- .../profile/hooks/useCanChangePassword.ts | 7 +- .../profile/hooks/useCanEditProfileField.ts | 8 +- .../settings/profile/hooks/useUpdateEmail.ts | 4 +- .../components/SettingsRolesDefaultRole.tsx | 4 +- .../components/SettingsRolesTableRow.tsx | 6 +- .../roles/hooks/useHasPermissionFlag.ts | 6 +- .../roles/hooks/usePermissionFlagMap.ts | 4 +- .../components/SettingsRoleAssignment.tsx | 7 +- ...oleAssignmentConfirmationModalSubtitle.tsx | 6 +- .../SettingsRoleAssignmentTableRow.tsx | 5 +- ...ntWorkspaceMemberPickerDropdownContent.tsx | 6 +- ...gsRolePermissionsObjectLevelObjectForm.tsx | 3 +- ...rmissionsObjectLevelRecordLevelSection.tsx | 4 +- .../useRecordLevelPermissionFilterActions.ts | 9 +- ...SettingsSSOIdentitiesProvidersListCard.tsx | 5 +- .../SettingsSecurityAuthBypassOptionsList.tsx | 4 +- ...ttingsSecurityAuthProvidersOptionsList.tsx | 5 +- .../SettingsSecurityEditableProfileFields.tsx | 4 +- .../security/components/Toggle2FA.tsx | 5 +- .../DeleteTwoFactorAuthenticationMethod.tsx | 5 +- ...orAuthenticationSetupForSettingsEffect.tsx | 4 +- ...entUserWorkspaceTwoFactorAuthentication.ts | 4 +- ...eWorkspaceTwoFactorAuthenticationPolicy.ts | 4 +- .../WorkspaceMemberPictureUploader.tsx | 4 +- .../workspace/components/NameField.tsx | 7 +- .../components/ToggleImpersonate.tsx | 5 +- .../components/WorkspaceLogoUploader.tsx | 5 +- .../SignInAppNavigationDrawerMock.tsx | 4 +- .../SignInBackgroundMockContainer.tsx | 4 +- .../components/SSEClientEffect.tsx | 3 +- .../components/SSEEventStreamEffect.tsx | 5 +- .../useHandleSseClientConnectionRetry.ts | 7 +- .../hooks/useListenToEventsForQuery.ts | 25 +- .../hooks/useInstantiateSupportChat.ts | 8 +- .../internal/date/components/DatePicker.tsx | 4 +- .../date/components/DatePickerHeader.tsx | 4 +- .../components/DatePickerWithoutCalendar.tsx | 4 +- .../date/components/DateTimePickerHeader.tsx | 4 +- ...ateTimeToIMaskDateTimeInputString.test.tsx | 31 +- .../hooks/__tests__/useUserTimezone.test.tsx | 86 ++--- .../internal/date/hooks/useUserDateFormat.ts | 4 +- .../date/hooks/useUserFirstDayOfTheWeek.ts | 4 +- .../internal/date/hooks/useUserTimeFormat.ts | 4 +- .../internal/date/hooks/useUserTimezone.ts | 4 +- ...ltiWorkspaceDropdownClickableComponent.tsx | 5 +- ...ultiWorkspaceDropdownDefaultComponents.tsx | 10 +- ...kspaceDropdownWorkspacesListComponents.tsx | 6 +- .../components/WorkspacesForSignIn.tsx | 6 +- .../components/WorkspacesForSignUp.tsx | 6 +- .../NavigationDrawerSectionTitle.tsx | 3 +- .../__stories__/NavigationDrawer.stories.tsx | 15 +- .../hooks/useFilteredAvailableWorkspaces.ts | 4 +- .../hooks/__tests__/useColorScheme.test.tsx | 15 +- .../modules/ui/theme/hooks/useColorScheme.ts | 7 +- .../page-favicon/components/PageFavicon.tsx | 4 +- .../utilities/state/jotai/MIGRATION_PLAN.md | 12 +- .../state/jotai/types/SelectorCallbacksV2.ts | 8 +- .../state/jotai/utils/buildGetHelper.ts | 20 +- .../jotai/utils/createJotaiCookieStorage.ts | 93 ++++++ .../state/jotai/utils/createStateV2.ts | 48 ++- .../components/EagerMetadataLoadEffect.tsx | 121 +++++++ .../components/LazyMetadataLoadEffect.tsx | 122 +++++++ .../components/MetadataProviderEffect.tsx | 182 +--------- .../modules/users/components/UserProvider.tsx | 16 +- .../modules/users/hooks/useLoadCurrentUser.ts | 22 +- .../ViewBarAnyFieldFilterEffect.tsx | 9 +- ...wBarFilterDropdownAdvancedFilterButton.tsx | 9 +- .../components/ViewBarRecordFieldEffect.tsx | 9 +- .../components/ViewBarRecordFilterEffect.tsx | 9 +- .../ViewBarRecordFilterGroupEffect.tsx | 9 +- .../components/ViewBarRecordSortEffect.tsx | 9 +- .../ViewBarFilterDropdown.stories.tsx | 4 +- ...GroupsToCurrentRecordFilterGroups.test.tsx | 22 +- ...ViewFiltersToCurrentRecordFilters.test.tsx | 34 +- ...rentViewSortsToCurrentRecordSorts.test.tsx | 18 +- ...dataItemFromViewBarFilterDropdown.test.tsx | 40 ++- .../hooks/internal/usePerformViewAPIUpdate.ts | 4 +- ...rrentViewAnyFieldFilterToAnyFieldFilter.ts | 31 +- ...FilterGroupsToCurrentRecordFilterGroups.ts | 16 +- ...urrentViewFiltersToCurrentRecordFilters.ts | 40 +-- ...plyCurrentViewSortsToCurrentRecordSorts.ts | 9 +- ...eComputeRecordRelationFilterLabelValue.tsx | 4 +- .../hooks/useCreateViewFromCurrentView.ts | 16 +- .../views/hooks/useGetCurrentViewOnly.ts | 10 +- .../src/modules/views/hooks/useGetViewById.ts | 10 +- .../hooks/useGetViewFromPrefetchState.ts | 24 +- .../views/hooks/useRefreshAllCoreViews.ts | 41 +-- .../useRefreshCoreViewsByObjectMetadataId.ts | 25 +- .../views/hooks/useUpdateCurrentView.ts | 26 +- .../views/hooks/useUpdateViewAggregate.ts | 100 +++--- ...useViewOrDefaultViewFromPrefetchedViews.ts | 17 +- .../hooks/useViewsSideEffectsOnViewGroups.ts | 4 +- ...seTriggerViewFieldGroupOptimisticEffect.ts | 227 +++++++------ .../useTriggerViewFieldOptimisticEffect.ts | 203 ++++++----- .../useTriggerViewFilterOptimisticEffect.ts | 195 ++++++----- .../useTriggerViewGroupOptimisticEffect.ts | 229 +++++++------ .../useTriggerViewSortOptimisticEffect.ts | 197 ++++++----- .../src/modules/views/states/coreViewState.ts | 4 +- ...wIdFromObjectMetadataItemFamilySelector.ts | 6 +- .../coreViewFromViewIdFamilySelector.ts | 5 +- ...IdsFromObjectMetadataItemFamilySelector.ts | 32 +- .../selectors/coreViewLengthFamilySelector.ts | 5 +- ...reViewsByObjectMetadataIdFamilySelector.ts | 64 ++-- ...ewsFromObjectMetadataItemFamilySelector.ts | 34 +- .../components/ViewPickerContentEffect.tsx | 9 +- .../ViewPickerFavoriteFoldersDropdown.tsx | 10 +- .../components/ViewPickerListContent.tsx | 9 +- .../useGetAvailableFieldsForCalendar.test.tsx | 48 +-- ...etAvailableFieldsToGroupRecordsBy.test.tsx | 48 +-- .../hooks/useDestroyViewFromCurrentState.ts | 20 +- .../hooks/useGetAvailableFieldsForCalendar.ts | 4 +- .../useGetAvailableFieldsToGroupRecordsBy.ts | 4 +- ...kflowRunOpeningInCommandMenuSideEffects.ts | 9 +- .../workflow/hooks/useRunWorkflowVersion.tsx | 5 +- .../WorkflowEditActionEmailBase.tsx | 4 +- .../WorkflowEditTriggerWebhookForm.tsx | 4 +- .../hooks/useStepsOutputSchema.ts | 9 +- .../components/WorkspaceProviderEffect.tsx | 9 +- .../__tests__/useSubscriptionStatus.test.ts | 17 +- .../workspace/hooks/useFeatureFlagsMap.ts | 4 +- .../workspace/hooks/useIsFeatureEnabled.ts | 5 +- .../useIsSomeMeteredProductCapReached.ts | 5 +- .../useIsWorkspaceActivationStatusEqualsTo.ts | 5 +- .../workspace/hooks/useSubscriptionStatus.ts | 5 +- .../workspaceAuthBypassProvidersState.ts | 4 +- .../states/workspaceAuthProvidersState.ts | 4 +- .../states/workspaceBypassModeState.ts | 4 +- .../src/pages/auth/PasswordReset.tsx | 7 +- .../twenty-front/src/pages/auth/SignInUp.tsx | 14 +- .../auth/__stories__/SignInUp.stories.tsx | 4 +- .../SignInUpWithInvite.stories.tsx | 4 +- .../src/pages/onboarding/BookCall.tsx | 6 +- .../src/pages/onboarding/ChooseYourPlan.tsx | 4 +- .../src/pages/onboarding/CreateProfile.tsx | 7 +- .../src/pages/onboarding/CreateWorkspace.tsx | 4 +- .../src/pages/onboarding/InviteTeam.tsx | 6 +- .../src/pages/onboarding/PaymentSuccess.tsx | 4 +- .../src/pages/onboarding/SyncEmails.tsx | 15 +- .../internal/ChooseYourPlanContent.tsx | 12 +- .../src/pages/settings/SettingsBilling.tsx | 5 +- .../src/pages/settings/SettingsProfile.tsx | 4 +- .../settings/accounts/SettingsAccounts.tsx | 4 +- .../SettingsAdminConfigVariableDetails.tsx | 4 +- .../pages/settings/ai/SettingsAIPrompts.tsx | 7 +- .../ai/components/SettingsAIModelsTab.tsx | 5 +- .../SettingsApplicationDetailContentTab.tsx | 4 +- .../SettingsApplicationPermissionsTab.tsx | 5 +- .../tabs/SettingsApplicationsCreateTab.tsx | 4 +- ...gsAvailableApplicationDetailContentTab.tsx | 4 +- .../settings/data-model/SettingsNewObject.tsx | 4 +- .../data-model/SettingsObjectTable.tsx | 3 +- .../pages/settings/domains/SettingsDomain.tsx | 7 +- .../SettingsLogicFunctionDetail.tsx | 4 +- .../members/SettingsWorkspaceMember.tsx | 6 +- .../members/SettingsWorkspaceMembers.tsx | 7 +- .../appearance/components/LocalePicker.tsx | 5 +- .../settings/security/SettingsSecurity.tsx | 11 +- .../event-logs/components/EventLogFilters.tsx | 7 +- .../ObjectMetadataItemsDecorator.tsx | 13 +- .../decorators/PrefetchLoadedDecorator.tsx | 12 +- .../decorators/PrefetchLoadingDecorator.tsx | 12 +- .../decorators/RecordTableDecorator.tsx | 15 +- .../testing/decorators/WorkspaceDecorator.tsx | 4 +- .../jest/JestObjectMetadataItemSetter.tsx | 4 +- 538 files changed, 4664 insertions(+), 4127 deletions(-) create mode 100644 packages/twenty-front/src/modules/app/hooks/useMetadataStore.ts create mode 100644 packages/twenty-front/src/modules/app/states/isAppLoadingState.ts create mode 100644 packages/twenty-front/src/modules/app/states/metadataStoreState.ts delete mode 100644 packages/twenty-front/src/modules/navigation-menu-item/components/NavigationMenuItemDragProvider.tsx create mode 100644 packages/twenty-front/src/modules/ui/utilities/state/jotai/utils/createJotaiCookieStorage.ts create mode 100644 packages/twenty-front/src/modules/users/components/EagerMetadataLoadEffect.tsx create mode 100644 packages/twenty-front/src/modules/users/components/LazyMetadataLoadEffect.tsx diff --git a/packages/twenty-front/jest.config.mjs b/packages/twenty-front/jest.config.mjs index aa2adb6765..9bf6bb6683 100644 --- a/packages/twenty-front/jest.config.mjs +++ b/packages/twenty-front/jest.config.mjs @@ -63,7 +63,7 @@ const jestConfig = { global: { statements: 49.5, lines: 48, - functions: 40, + functions: 39.5, }, }, collectCoverageFrom: ['/src/**/*.ts'], diff --git a/packages/twenty-front/src/hooks/__tests__/usePageChangeEffectNavigateLocation.test.ts b/packages/twenty-front/src/hooks/__tests__/usePageChangeEffectNavigateLocation.test.ts index e2b122f5ad..c3066dbf5c 100644 --- a/packages/twenty-front/src/hooks/__tests__/usePageChangeEffectNavigateLocation.test.ts +++ b/packages/twenty-front/src/hooks/__tests__/usePageChangeEffectNavigateLocation.test.ts @@ -3,7 +3,7 @@ import { useDefaultHomePagePath } from '@/navigation/hooks/useDefaultHomePagePat import { useOnboardingStatus } from '@/onboarding/hooks/useOnboardingStatus'; import { useIsWorkspaceActivationStatusEqualsTo } from '@/workspace/hooks/useIsWorkspaceActivationStatusEqualsTo'; import { useParams } from 'react-router-dom'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { AppPath, SettingsPath } from 'twenty-shared/types'; import { getSettingsPath } from 'twenty-shared/utils'; @@ -63,14 +63,14 @@ const setupMockUseParams = (objectNamePlural?: string) => { .mockReturnValueOnce({ objectNamePlural: objectNamePlural ?? '' }); }; -jest.mock('recoil'); +jest.mock('@/ui/utilities/state/jotai/hooks/useRecoilValueV2'); const setupMockRecoil = ( objectNamePlural?: string, verifyEmailRedirectPath?: string, calendarBookingPageId?: string | null, ) => { jest - .mocked(useRecoilValue) + .mocked(useRecoilValueV2) .mockReturnValueOnce(calendarBookingPageId ?? 'mock-calendar-id') .mockReturnValueOnce([{ namePlural: objectNamePlural ?? '' }]) .mockReturnValueOnce(verifyEmailRedirectPath); diff --git a/packages/twenty-front/src/hooks/usePageChangeEffectNavigateLocation.ts b/packages/twenty-front/src/hooks/usePageChangeEffectNavigateLocation.ts index b72462db85..4f2687f486 100644 --- a/packages/twenty-front/src/hooks/usePageChangeEffectNavigateLocation.ts +++ b/packages/twenty-front/src/hooks/usePageChangeEffectNavigateLocation.ts @@ -5,9 +5,9 @@ import { useIsCurrentLocationOnAWorkspace } from '@/domain-manager/hooks/useIsCu import { useDefaultHomePagePath } from '@/navigation/hooks/useDefaultHomePagePath'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { useOnboardingStatus } from '@/onboarding/hooks/useOnboardingStatus'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { useIsWorkspaceActivationStatusEqualsTo } from '@/workspace/hooks/useIsWorkspaceActivationStatusEqualsTo'; import { useLocation, useParams } from 'react-router-dom'; -import { useRecoilValue } from 'recoil'; import { AppPath, SettingsPath } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { WorkspaceActivationStatus } from 'twenty-shared/workspace'; @@ -23,7 +23,7 @@ export const usePageChangeEffectNavigateLocation = () => { ); const { defaultHomePagePath } = useDefaultHomePagePath(); const location = useLocation(); - const calendarBookingPageId = useRecoilValue(calendarBookingPageIdState); + const calendarBookingPageId = useRecoilValueV2(calendarBookingPageIdState); const someMatchingLocationOf = (appPaths: AppPath[]): boolean => appPaths.some((appPath) => isMatchingLocation(location, appPath)); @@ -45,11 +45,13 @@ export const usePageChangeEffectNavigateLocation = () => { ]; const objectNamePlural = useParams().objectNamePlural ?? ''; - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); const objectMetadataItem = objectMetadataItems?.find( (objectMetadataItem) => objectMetadataItem.namePlural === objectNamePlural, ); - const verifyEmailRedirectPath = useRecoilValue(verifyEmailRedirectPathState); + const verifyEmailRedirectPath = useRecoilValueV2( + verifyEmailRedirectPathState, + ); if ( (!isLoggedIn || (isLoggedIn && !isOnAWorkspace)) && diff --git a/packages/twenty-front/src/modules/action-menu/contexts/ActionMenuContextProvider.tsx b/packages/twenty-front/src/modules/action-menu/contexts/ActionMenuContextProvider.tsx index d9059c70b9..9b1af7a97c 100644 --- a/packages/twenty-front/src/modules/action-menu/contexts/ActionMenuContextProvider.tsx +++ b/packages/twenty-front/src/modules/action-menu/contexts/ActionMenuContextProvider.tsx @@ -6,7 +6,7 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadat import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { isDefined } from 'twenty-shared/utils'; export const ActionMenuContextProvider = ({ @@ -23,7 +23,7 @@ export const ActionMenuContextProvider = ({ contextStoreCurrentObjectMetadataItemIdComponentState, ); - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); const objectMetadataItem = objectMetadataItemOverride ?? diff --git a/packages/twenty-front/src/modules/action-menu/hooks/useShouldActionBeRegisteredParams.ts b/packages/twenty-front/src/modules/action-menu/hooks/useShouldActionBeRegisteredParams.ts index 3b663d7ab1..1c3f4c9939 100644 --- a/packages/twenty-front/src/modules/action-menu/hooks/useShouldActionBeRegisteredParams.ts +++ b/packages/twenty-front/src/modules/action-menu/hooks/useShouldActionBeRegisteredParams.ts @@ -16,9 +16,11 @@ import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPe import { hasAnySoftDeleteFilterOnViewComponentSelector } from '@/object-record/record-filter/states/hasAnySoftDeleteFilterOnView'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; -import { useContext, useMemo } from 'react'; -import { useRecoilCallback, useRecoilValue } from 'recoil'; +import { useCallback, useContext, useMemo } from 'react'; +import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { FeatureFlagKey } from '~/generated-metadata/graphql'; @@ -104,31 +106,25 @@ export const useShouldActionBeRegisteredParams = ({ const isSelectAll = contextStoreTargetedRecordsRule.mode === 'exclusion'; - const getObjectReadPermission = useRecoilCallback( - ({ snapshot }) => - (objectMetadataNameSingular: string) => { - return snapshot - .getLoadable( - objectPermissionsFamilySelector({ - objectNameSingular: objectMetadataNameSingular, - }), - ) - .getValue().canRead; - }, + const getObjectReadPermission = useCallback( + (objectMetadataNameSingular: string) => { + return jotaiStore.get( + objectPermissionsFamilySelector.selectorFamily({ + objectNameSingular: objectMetadataNameSingular, + }), + ).canRead; + }, [], ); - const getObjectWritePermission = useRecoilCallback( - ({ snapshot }) => - (objectMetadataNameSingular: string) => { - return snapshot - .getLoadable( - objectPermissionsFamilySelector({ - objectNameSingular: objectMetadataNameSingular, - }), - ) - .getValue().canUpdate; - }, + const getObjectWritePermission = useCallback( + (objectMetadataNameSingular: string) => { + return jotaiStore.get( + objectPermissionsFamilySelector.selectorFamily({ + objectNameSingular: objectMetadataNameSingular, + }), + ).canUpdate; + }, [], ); @@ -136,7 +132,7 @@ export const useShouldActionBeRegisteredParams = ({ forceRegisteredActionsByKeyState, ); - const currentWorkspace = useRecoilValue(currentWorkspaceState); + const currentWorkspace = useRecoilValueV2(currentWorkspaceState); const isFeatureFlagEnabled = (featureFlagKey: FeatureFlagKey) => { const featureFlag = currentWorkspace?.featureFlags?.find( diff --git a/packages/twenty-front/src/modules/activities/calendar/components/CalendarEventRow.tsx b/packages/twenty-front/src/modules/activities/calendar/components/CalendarEventRow.tsx index 9e72fede5a..cfb9b2dcd1 100644 --- a/packages/twenty-front/src/modules/activities/calendar/components/CalendarEventRow.tsx +++ b/packages/twenty-front/src/modules/activities/calendar/components/CalendarEventRow.tsx @@ -2,7 +2,7 @@ import { css, useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { t } from '@lingui/core/macro'; import { format } from 'date-fns'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { CalendarChannelVisibility, type TimelineCalendarEvent, @@ -85,7 +85,7 @@ export const CalendarEventRow = ({ className, }: CalendarEventRowProps) => { const theme = useTheme(); - const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); + const currentWorkspaceMember = useRecoilValueV2(currentWorkspaceMemberState); const { openCalendarEventInCommandMenu } = useOpenCalendarEventInCommandMenu(); diff --git a/packages/twenty-front/src/modules/activities/files/components/AttachmentList.tsx b/packages/twenty-front/src/modules/activities/files/components/AttachmentList.tsx index 59b22ebaca..536c0ca50a 100644 --- a/packages/twenty-front/src/modules/activities/files/components/AttachmentList.tsx +++ b/packages/twenty-front/src/modules/activities/files/components/AttachmentList.tsx @@ -11,7 +11,7 @@ import { type ActivityTargetableObject } from '@/activities/types/ActivityTarget import { isAttachmentPreviewEnabledState } from '@/client-config/states/isAttachmentPreviewEnabledState'; import { Modal } from '@/ui/layout/modal/components/Modal'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { ActivityList } from '@/activities/components/ActivityList'; import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag'; @@ -135,7 +135,7 @@ export const AttachmentList = ({ const [previewedAttachment, setPreviewedAttachment] = useState(null); - const isAttachmentPreviewEnabled = useRecoilValue( + const isAttachmentPreviewEnabled = useRecoilValueV2( isAttachmentPreviewEnabledState, ); diff --git a/packages/twenty-front/src/modules/activities/hooks/__tests__/useActivityTargetObjectRecords.test.tsx b/packages/twenty-front/src/modules/activities/hooks/__tests__/useActivityTargetObjectRecords.test.tsx index f1688336af..c170b3eab8 100644 --- a/packages/twenty-front/src/modules/activities/hooks/__tests__/useActivityTargetObjectRecords.test.tsx +++ b/packages/twenty-front/src/modules/activities/hooks/__tests__/useActivityTargetObjectRecords.test.tsx @@ -2,12 +2,14 @@ import { gql, InMemoryCache } from '@apollo/client'; import { MockedProvider } from '@apollo/client/testing'; import { act, renderHook } from '@testing-library/react'; import { type ReactNode } from 'react'; +import { Provider as JotaiProvider } from 'jotai'; import { RecoilRoot, useSetRecoilState } from 'recoil'; import { useActivityTargetObjectRecords } from '@/activities/hooks/useActivityTargetObjectRecords'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { SnackBarComponentInstanceContext } from '@/ui/feedback/snack-bar-manager/contexts/SnackBarComponentInstanceContext'; import { JestObjectMetadataItemSetter } from '~/testing/jest/JestObjectMetadataItemSetter'; import { mockWorkspaceMembers } from '~/testing/mock-data/workspace-members'; @@ -111,30 +113,32 @@ const task = { }; const Wrapper = ({ children }: { children: ReactNode }) => ( - - - - - {children} - - - - + + + + + + {children} + + + + + ); describe('useActivityTargetObjectRecords', () => { it('return targetObjects', async () => { + jotaiStore.set(currentWorkspaceMemberState.atom, mockWorkspaceMembers[0]); + + jotaiStore.set( + objectMetadataItemsState.atom, + generatedMockObjectMetadataItems, + ); + const { result } = renderHook( () => { - const setCurrentWorkspaceMember = useSetRecoilState( - currentWorkspaceMemberState, - ); - const setObjectMetadataItems = useSetRecoilState( - objectMetadataItemsState, - ); - const setRecordFromStore = useSetRecoilState( recordStoreFamilyState(task.id), ); @@ -145,8 +149,6 @@ describe('useActivityTargetObjectRecords', () => { return { activityTargetObjectRecords, - setCurrentWorkspaceMember, - setObjectMetadataItems, setRecordFromStore, }; }, @@ -154,8 +156,6 @@ describe('useActivityTargetObjectRecords', () => { ); act(() => { - result.current.setCurrentWorkspaceMember(mockWorkspaceMembers[0]); - result.current.setObjectMetadataItems(generatedMockObjectMetadataItems); result.current.setRecordFromStore(task); }); diff --git a/packages/twenty-front/src/modules/activities/hooks/__tests__/useOpenCreateActivityDrawer.test.tsx b/packages/twenty-front/src/modules/activities/hooks/__tests__/useOpenCreateActivityDrawer.test.tsx index aeb135f671..161db27750 100644 --- a/packages/twenty-front/src/modules/activities/hooks/__tests__/useOpenCreateActivityDrawer.test.tsx +++ b/packages/twenty-front/src/modules/activities/hooks/__tests__/useOpenCreateActivityDrawer.test.tsx @@ -1,11 +1,12 @@ import { type MockedResponse } from '@apollo/client/testing'; import { act, renderHook } from '@testing-library/react'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; +import { useRecoilValue } from 'recoil'; import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import gql from 'graphql-tag'; import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; import { mockedTasks } from '~/testing/mock-data/tasks'; @@ -72,6 +73,10 @@ const Wrapper = getJestMetadataAndApolloMocksWrapper({ const mockObjectMetadataItems = generatedMockObjectMetadataItems; describe('useOpenCreateActivityDrawer', () => { + beforeEach(() => { + jotaiStore.set(objectMetadataItemsState.atom, mockObjectMetadataItems); + }); + it('works as expected', async () => { const { result } = renderHook( () => { @@ -79,13 +84,9 @@ describe('useOpenCreateActivityDrawer', () => { activityObjectNameSingular: CoreObjectNameSingular.Note, }); const viewableRecordId = useRecoilValue(viewableRecordIdState); - const setObjectMetadataItems = useSetRecoilState( - objectMetadataItemsState, - ); return { openActivityRightDrawer, viewableRecordId, - setObjectMetadataItems, }; }, { @@ -93,10 +94,6 @@ describe('useOpenCreateActivityDrawer', () => { }, ); - act(() => { - result.current.setObjectMetadataItems(mockObjectMetadataItems); - }); - expect(result.current.viewableRecordId).toBeNull(); await act(async () => { result.current.openActivityRightDrawer({ diff --git a/packages/twenty-front/src/modules/activities/hooks/useActivityTargetObjectRecords.ts b/packages/twenty-front/src/modules/activities/hooks/useActivityTargetObjectRecords.ts index 6c0e00fb89..563b1a6889 100644 --- a/packages/twenty-front/src/modules/activities/hooks/useActivityTargetObjectRecords.ts +++ b/packages/twenty-front/src/modules/activities/hooks/useActivityTargetObjectRecords.ts @@ -7,6 +7,7 @@ import { type TaskTarget } from '@/activities/types/TaskTarget'; import { getActivityTargetObjectRecords } from '@/activities/utils/getActivityTargetObjectRecords'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { type Nullable } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; @@ -14,7 +15,7 @@ export const useActivityTargetObjectRecords = ( activityRecordId?: string, activityTargets?: Nullable, ) => { - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); const activity = useRecoilValue( recordStoreFamilyState(activityRecordId ?? ''), diff --git a/packages/twenty-front/src/modules/activities/hooks/useActivityTargetsForTargetableObjects.ts b/packages/twenty-front/src/modules/activities/hooks/useActivityTargetsForTargetableObjects.ts index 85ec4f6882..d0470b62e9 100644 --- a/packages/twenty-front/src/modules/activities/hooks/useActivityTargetsForTargetableObjects.ts +++ b/packages/twenty-front/src/modules/activities/hooks/useActivityTargetsForTargetableObjects.ts @@ -1,4 +1,3 @@ -import { useRecoilValue } from 'recoil'; import { type RecordGqlOperationOrderBy } from 'twenty-shared/types'; import { findActivityTargetsOperationSignatureFactory } from '@/activities/graphql/operation-signatures/factories/findActivityTargetsOperationSignatureFactory'; @@ -10,6 +9,7 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadat import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { FeatureFlagKey } from '~/generated-metadata/graphql'; @@ -32,7 +32,7 @@ export const useActivityTargetsForTargetableObjects = ({ activityTargetsOrderByVariables: RecordGqlOperationOrderBy; limit: number; }) => { - const objectMetadataItems = useRecoilValue( + const objectMetadataItems = useRecoilValueV2( objectMetadataItemsState, ); const isNoteTargetMigrated = useIsFeatureEnabled( diff --git a/packages/twenty-front/src/modules/activities/inline-cell/hooks/useOpenActivityTargetCellEditMode.ts b/packages/twenty-front/src/modules/activities/inline-cell/hooks/useOpenActivityTargetCellEditMode.ts index 9c3c9f6b78..8a1652bc56 100644 --- a/packages/twenty-front/src/modules/activities/inline-cell/hooks/useOpenActivityTargetCellEditMode.ts +++ b/packages/twenty-front/src/modules/activities/inline-cell/hooks/useOpenActivityTargetCellEditMode.ts @@ -1,5 +1,6 @@ import { type ActivityTargetWithTargetRecord } from '@/activities/types/ActivityTargetObject'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useMultipleRecordPickerOpen } from '@/object-record/record-picker/multiple-record-picker/hooks/useMultipleRecordPickerOpen'; import { useMultipleRecordPickerPerformSearch } from '@/object-record/record-picker/multiple-record-picker/hooks/useMultipleRecordPickerPerformSearch'; @@ -24,14 +25,13 @@ export const useOpenActivityTargetCellEditMode = () => { const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack(); const openActivityTargetCellEditMode = useRecoilCallback( - ({ set, snapshot }) => + ({ set }) => ({ recordPickerInstanceId, activityTargetObjectRecords, }: OpenActivityTargetCellEditModeProps) => { - const objectMetadataItems = snapshot - .getLoadable(objectMetadataItemsState) - .getValue() + const objectMetadataItems = jotaiStore + .get(objectMetadataItemsState.atom) .filter( (objectMetadataItem) => objectMetadataItem.isSearchable && diff --git a/packages/twenty-front/src/modules/activities/inline-cell/hooks/useUpdateActivityTargetFromCell.ts b/packages/twenty-front/src/modules/activities/inline-cell/hooks/useUpdateActivityTargetFromCell.ts index 301dabc7ea..3ae3d426c8 100644 --- a/packages/twenty-front/src/modules/activities/inline-cell/hooks/useUpdateActivityTargetFromCell.ts +++ b/packages/twenty-front/src/modules/activities/inline-cell/hooks/useUpdateActivityTargetFromCell.ts @@ -5,7 +5,6 @@ import { getActivityTargetFieldNameForObject } from '@/activities/utils/getActiv import { getJoinObjectNameSingular } from '@/activities/utils/getJoinObjectNameSingular'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; -import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord'; import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord'; import { searchRecordStoreFamilyState } from '@/object-record/record-picker/multiple-record-picker/states/searchRecordStoreComponentFamilyState'; @@ -16,6 +15,7 @@ import { useRecoilCallback, useSetRecoilState } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { v4 } from 'uuid'; import { FeatureFlagKey } from '~/generated-metadata/graphql'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; type UpdateActivityTargetFromCellProps = { recordPickerInstanceId: string; @@ -79,9 +79,9 @@ export const useUpdateActivityTargetFromCell = ({ ? 'task' : 'note'; - const objectMetadataItems = snapshot - .getLoadable(objectMetadataItemsState) - .getValue(); + const objectMetadataItems = jotaiStore.get( + objectMetadataItemsState.atom, + ); const pickedObjectMetadataItem = objectMetadataItems.find( (objectMetadataItem) => diff --git a/packages/twenty-front/src/modules/activities/timeline-activities/components/EventRow.tsx b/packages/twenty-front/src/modules/activities/timeline-activities/components/EventRow.tsx index 78e001d5ff..3fe577b858 100644 --- a/packages/twenty-front/src/modules/activities/timeline-activities/components/EventRow.tsx +++ b/packages/twenty-front/src/modules/activities/timeline-activities/components/EventRow.tsx @@ -1,4 +1,5 @@ import styled from '@emotion/styled'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { useContext } from 'react'; import { useRecoilValue } from 'recoil'; @@ -92,9 +93,9 @@ export const EventRow = ({ event, mainObjectMetadataItem, }: EventRowProps) => { - const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); + const currentWorkspaceMember = useRecoilValueV2(currentWorkspaceMemberState); - const allowRequestsToTwentyIcons = useRecoilValue( + const allowRequestsToTwentyIcons = useRecoilValueV2( allowRequestsToTwentyIconsState, ); diff --git a/packages/twenty-front/src/modules/activities/timeline-activities/hooks/useLinkedObjectObjectMetadataItem.ts b/packages/twenty-front/src/modules/activities/timeline-activities/hooks/useLinkedObjectObjectMetadataItem.ts index 12731be614..ad92b5a353 100644 --- a/packages/twenty-front/src/modules/activities/timeline-activities/hooks/useLinkedObjectObjectMetadataItem.ts +++ b/packages/twenty-front/src/modules/activities/timeline-activities/hooks/useLinkedObjectObjectMetadataItem.ts @@ -1,10 +1,9 @@ -import { useRecoilValue } from 'recoil'; - import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; export const useLinkedObjectObjectMetadataItem = (id: string | null) => { - const objectMetadataItems: ObjectMetadataItem[] = useRecoilValue( + const objectMetadataItems: ObjectMetadataItem[] = useRecoilValueV2( objectMetadataItemsState, ); diff --git a/packages/twenty-front/src/modules/ai/components/AIChatTab.tsx b/packages/twenty-front/src/modules/ai/components/AIChatTab.tsx index 2d79036623..9407a666b3 100644 --- a/packages/twenty-front/src/modules/ai/components/AIChatTab.tsx +++ b/packages/twenty-front/src/modules/ai/components/AIChatTab.tsx @@ -1,7 +1,6 @@ import styled from '@emotion/styled'; import { EditorContent } from '@tiptap/react'; import { useState } from 'react'; -import { useRecoilValue } from 'recoil'; import { LightButton } from 'twenty-ui/input'; import { DropZone } from '@/activities/files/components/DropZone'; @@ -23,6 +22,7 @@ import { useAIChatFileUpload } from '@/ai/hooks/useAIChatFileUpload'; import { useAgentChatContextOrThrow } from '@/ai/hooks/useAgentChatContextOrThrow'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; const StyledContainer = styled.div<{ isDraggingFile: boolean }>` background: ${({ theme }) => theme.background.primary}; @@ -144,7 +144,7 @@ export const AIChatTab = () => { const hasMessages = messages.length > 0; const { uploadFiles } = useAIChatFileUpload(); - const currentWorkspace = useRecoilValue(currentWorkspaceState); + const currentWorkspace = useRecoilValueV2(currentWorkspaceState); const smartModelLabel = useAiModelLabel(currentWorkspace?.smartModel, false); const { editor, handleSendAndClear } = useAIChatEditor({ diff --git a/packages/twenty-front/src/modules/ai/hooks/useAgentChat.ts b/packages/twenty-front/src/modules/ai/hooks/useAgentChat.ts index 773a6c4430..39e5f9c8e4 100644 --- a/packages/twenty-front/src/modules/ai/hooks/useAgentChat.ts +++ b/packages/twenty-front/src/modules/ai/hooks/useAgentChat.ts @@ -1,5 +1,3 @@ -import { useSetRecoilState } from 'recoil'; - import { useGetBrowsingContext } from '@/ai/hooks/useBrowsingContext'; import { agentChatSelectedFilesStateV2 } from '@/ai/states/agentChatSelectedFilesStateV2'; import { agentChatUploadedFilesStateV2 } from '@/ai/states/agentChatUploadedFilesStateV2'; @@ -23,7 +21,7 @@ import { REACT_APP_SERVER_BASE_URL } from '~/config'; import { cookieStorage } from '~/utils/cookie-storage'; export const useAgentChat = (uiMessages: ExtendedUIMessage[]) => { - const setTokenPair = useSetRecoilState(tokenPairState); + const setTokenPair = useSetRecoilStateV2(tokenPairState); const setAgentChatUsage = useSetRecoilStateV2(agentChatUsageStateV2); const { getBrowsingContext } = useGetBrowsingContext(); diff --git a/packages/twenty-front/src/modules/ai/hooks/useBrowsingContext.ts b/packages/twenty-front/src/modules/ai/hooks/useBrowsingContext.ts index 3f51e3f9e8..aaf8d77a29 100644 --- a/packages/twenty-front/src/modules/ai/hooks/useBrowsingContext.ts +++ b/packages/twenty-front/src/modules/ai/hooks/useBrowsingContext.ts @@ -41,9 +41,7 @@ export const useGetBrowsingContext = () => { ) .getValue(); - const objectMetadataItems = snapshot - .getLoadable(objectMetadataItemsState) - .getValue(); + const objectMetadataItems = store.get(objectMetadataItemsState.atom); const objectMetadataItem = objectMetadataItems.find( (item) => item.id === objectMetadataItemId, @@ -115,13 +113,11 @@ export const useGetBrowsingContext = () => { ) .getValue(); - const currentView = snapshot - .getLoadable( - coreViewFromViewIdFamilySelector({ - viewId: currentViewId ?? '', - }), - ) - .getValue(); + const currentView = store.get( + coreViewFromViewIdFamilySelector.selectorFamily({ + viewId: currentViewId ?? '', + }), + ); if (!currentView) { return null; diff --git a/packages/twenty-front/src/modules/apollo/hooks/useApolloFactory.ts b/packages/twenty-front/src/modules/apollo/hooks/useApolloFactory.ts index b8504c4d42..f16dd58f27 100644 --- a/packages/twenty-front/src/modules/apollo/hooks/useApolloFactory.ts +++ b/packages/twenty-front/src/modules/apollo/hooks/useApolloFactory.ts @@ -1,41 +1,42 @@ import { InMemoryCache, type NormalizedCacheObject } from '@apollo/client'; import { useMemo, useRef } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; -import { useRecoilState, useSetRecoilState } from 'recoil'; +import { ApolloFactory, type Options } from '@/apollo/services/apollo.factory'; import { currentUserState } from '@/auth/states/currentUserState'; +import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { previousUrlState } from '@/auth/states/previousUrlState'; import { tokenPairState } from '@/auth/states/tokenPairState'; +import { appVersionState } from '@/client-config/states/appVersionState'; +import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; +import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; +import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; +import { AppPath } from 'twenty-shared/types'; +import { isDefined } from 'twenty-shared/utils'; import { REACT_APP_SERVER_BASE_URL } from '~/config'; import { useUpdateEffect } from '~/hooks/useUpdateEffect'; import { isMatchingLocation } from '~/utils/isMatchingLocation'; -import { ApolloFactory, type Options } from '@/apollo/services/apollo.factory'; -import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState'; -import { appVersionState } from '@/client-config/states/appVersionState'; -import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; -import { AppPath } from 'twenty-shared/types'; -import { isDefined } from 'twenty-shared/utils'; -import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; -import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; - export const useApolloFactory = (options: Partial> = {}) => { // eslint-disable-next-line twenty/no-state-useref const apolloRef = useRef | null>(null); const navigate = useNavigate(); - const setTokenPair = useSetRecoilState(tokenPairState); - const [currentWorkspace, setCurrentWorkspace] = useRecoilState( + const setTokenPair = useSetRecoilStateV2(tokenPairState); + const [currentWorkspace, setCurrentWorkspace] = useRecoilStateV2( currentWorkspaceState, ); const appVersion = useRecoilValueV2(appVersionState); - const [currentWorkspaceMember, setCurrentWorkspaceMember] = useRecoilState( + const [currentWorkspaceMember, setCurrentWorkspaceMember] = useRecoilStateV2( currentWorkspaceMemberState, ); - const setCurrentUser = useSetRecoilState(currentUserState); - const setCurrentUserWorkspace = useSetRecoilState(currentUserWorkspaceState); + const setCurrentUser = useSetRecoilStateV2(currentUserState); + const setCurrentUserWorkspace = useSetRecoilStateV2( + currentUserWorkspaceState, + ); const setPreviousUrl = useSetRecoilStateV2(previousUrlState); const location = useLocation(); @@ -66,8 +67,6 @@ export const useApolloFactory = (options: Partial> = {}) => { setTokenPair(tokenPair); }, onUnauthenticatedError: () => { - // eslint-disable-next-line no-console - console.log('onUnauthenticatedError, resetting state'); setTokenPair(null); setCurrentUser(null); setCurrentWorkspaceMember(null); diff --git a/packages/twenty-front/src/modules/app/components/AppRouter.tsx b/packages/twenty-front/src/modules/app/components/AppRouter.tsx index ea85a1fdb4..3c172479d6 100644 --- a/packages/twenty-front/src/modules/app/components/AppRouter.tsx +++ b/packages/twenty-front/src/modules/app/components/AppRouter.tsx @@ -1,13 +1,13 @@ import { useCreateAppRouter } from '@/app/hooks/useCreateAppRouter'; import { currentUserState } from '@/auth/states/currentUserState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { RouterProvider } from 'react-router-dom'; -import { useRecoilValue } from 'recoil'; export const AppRouter = () => { // We want to disable logic function settings but keep the code for now const isFunctionSettingsEnabled = false; - const currentUser = useRecoilValue(currentUserState); + const currentUser = useRecoilValueV2(currentUserState); const isAdminPageEnabled = (currentUser?.canImpersonate || currentUser?.canAccessFullAdminPanel) ?? diff --git a/packages/twenty-front/src/modules/app/components/AppRouterProviders.tsx b/packages/twenty-front/src/modules/app/components/AppRouterProviders.tsx index 304a8d4859..33cdabbaab 100644 --- a/packages/twenty-front/src/modules/app/components/AppRouterProviders.tsx +++ b/packages/twenty-front/src/modules/app/components/AppRouterProviders.tsx @@ -13,7 +13,6 @@ import { ErrorMessageEffect } from '@/error-handler/components/ErrorMessageEffec import { PromiseRejectionEffect } from '@/error-handler/components/PromiseRejectionEffect'; import { HeadlessFrontComponentMountRoot } from '@/front-components/components/HeadlessFrontComponentMountRoot'; import { ApolloCoreProvider } from '@/object-metadata/components/ApolloCoreProvider'; -import { ObjectMetadataItemsLoadEffect } from '@/object-metadata/components/ObjectMetadataItemsLoadEffect'; import { ObjectMetadataItemsProvider } from '@/object-metadata/components/ObjectMetadataItemsProvider'; import { PrefetchDataProvider } from '@/prefetch/components/PrefetchDataProvider'; import { SSEProvider } from '@/sse-db-event/components/SSEProvider'; @@ -26,6 +25,8 @@ import { BaseThemeProvider } from '@/ui/theme/components/BaseThemeProvider'; import { UserThemeProviderEffect } from '@/ui/theme/components/UserThemeProviderEffect'; import { PageFavicon } from '@/ui/utilities/page-favicon/components/PageFavicon'; import { PageTitle } from '@/ui/utilities/page-title/components/PageTitle'; +import { EagerMetadataLoadEffect } from '@/users/components/EagerMetadataLoadEffect'; +import { LazyMetadataLoadEffect } from '@/users/components/LazyMetadataLoadEffect'; import { MetadataProviderEffect } from '@/users/components/MetadataProviderEffect'; import { UserProvider } from '@/users/components/UserProvider'; import { WorkspaceProviderEffect } from '@/workspace/components/WorkspaceProviderEffect'; @@ -42,6 +43,8 @@ export const AppRouterProviders = () => { + + @@ -51,7 +54,6 @@ export const AppRouterProviders = () => { - diff --git a/packages/twenty-front/src/modules/app/effect-components/PageChangeEffect.tsx b/packages/twenty-front/src/modules/app/effect-components/PageChangeEffect.tsx index ab3b69989e..45f3c2521d 100644 --- a/packages/twenty-front/src/modules/app/effect-components/PageChangeEffect.tsx +++ b/packages/twenty-front/src/modules/app/effect-components/PageChangeEffect.tsx @@ -1,12 +1,3 @@ -import { useEffect, useState } from 'react'; -import { - matchPath, - useLocation, - useNavigate, - useParams, -} from 'react-router-dom'; -import { useRecoilCallback, useRecoilValue } from 'recoil'; - import { setSessionId, useEventTracker, @@ -15,6 +6,15 @@ import { useExecuteTasksOnAnyLocationChange } from '@/app/hooks/useExecuteTasksO import { isAppEffectRedirectEnabledState } from '@/app/states/isAppEffectRedirectEnabledState'; import { useRequestFreshCaptchaToken } from '@/captcha/hooks/useRequestFreshCaptchaToken'; import { isCaptchaScriptLoadedState } from '@/captcha/states/isCaptchaScriptLoadedState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; +import { useEffect, useState } from 'react'; +import { + matchPath, + useLocation, + useNavigate, + useParams, +} from 'react-router-dom'; +import { useRecoilCallback } from 'recoil'; import { isCaptchaRequiredForPath } from '@/captcha/utils/isCaptchaRequiredForPath'; import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu'; import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState'; @@ -92,7 +92,7 @@ export const PageChangeEffect = () => { const { executeTasksOnAnyLocationChange } = useExecuteTasksOnAnyLocationChange(); - const isAppEffectRedirectEnabled = useRecoilValue( + const isAppEffectRedirectEnabled = useRecoilValueV2( isAppEffectRedirectEnabledState, ); @@ -365,7 +365,7 @@ export const PageChangeEffect = () => { }, [eventTracker, location.pathname]); const { requestFreshCaptchaToken } = useRequestFreshCaptchaToken(); - const isCaptchaScriptLoaded = useRecoilValue(isCaptchaScriptLoadedState); + const isCaptchaScriptLoaded = useRecoilValueV2(isCaptchaScriptLoadedState); useEffect(() => { if (isCaptchaScriptLoaded && isCaptchaRequiredForPath(location.pathname)) { diff --git a/packages/twenty-front/src/modules/app/hooks/useInitializeQueryParamState.ts b/packages/twenty-front/src/modules/app/hooks/useInitializeQueryParamState.ts index ba7556e222..7f84ba7a5f 100644 --- a/packages/twenty-front/src/modules/app/hooks/useInitializeQueryParamState.ts +++ b/packages/twenty-front/src/modules/app/hooks/useInitializeQueryParamState.ts @@ -1,63 +1,60 @@ -import { useRecoilCallback } from 'recoil'; +import { useCallback } from 'react'; import { billingCheckoutSessionState } from '@/auth/states/billingCheckoutSessionState'; import { type BillingCheckoutSession } from '@/auth/types/billingCheckoutSession.type'; import { BILLING_CHECKOUT_SESSION_DEFAULT_VALUE } from '@/billing/constants/BillingCheckoutSessionDefaultValue'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import deepEqual from 'deep-equal'; // Initialize state that are hydrated from query parameters // We used to use recoil-sync to do this, but it was causing issues with Firefox export const useInitializeQueryParamState = () => { - const initializeQueryParamState = useRecoilCallback( - ({ set, snapshot }) => - () => { - const handlers = { - billingCheckoutSession: (value: string) => { - const billingCheckoutSession = snapshot - .getLoadable(billingCheckoutSessionState) - .getValue(); + const initializeQueryParamState = useCallback(() => { + const handlers = { + billingCheckoutSession: (value: string) => { + const billingCheckoutSession = jotaiStore.get( + billingCheckoutSessionState.atom, + ); - try { - const parsedValue = JSON.parse(decodeURIComponent(value)); + try { + const parsedValue = JSON.parse(decodeURIComponent(value)); - if ( - typeof parsedValue === 'object' && - parsedValue !== null && - 'plan' in parsedValue && - 'interval' in parsedValue && - 'requirePaymentMethod' in parsedValue && - !deepEqual(billingCheckoutSession, parsedValue) - ) { - set( - billingCheckoutSessionState, - parsedValue as BillingCheckoutSession, - ); - } - } catch (error) { - // eslint-disable-next-line no-console - console.error( - 'Failed to parse billingCheckoutSession from URL', - error, - ); - set( - billingCheckoutSessionState, - BILLING_CHECKOUT_SESSION_DEFAULT_VALUE, - ); - } - }, - }; - - const queryParams = new URLSearchParams(window.location.search); - - for (const [paramName, handler] of Object.entries(handlers)) { - const value = queryParams.get(paramName); - if (value !== null) { - handler(value); + if ( + typeof parsedValue === 'object' && + parsedValue !== null && + 'plan' in parsedValue && + 'interval' in parsedValue && + 'requirePaymentMethod' in parsedValue && + !deepEqual(billingCheckoutSession, parsedValue) + ) { + jotaiStore.set( + billingCheckoutSessionState.atom, + parsedValue as BillingCheckoutSession, + ); } + } catch (error) { + // eslint-disable-next-line no-console + console.error( + 'Failed to parse billingCheckoutSession from URL', + error, + ); + jotaiStore.set( + billingCheckoutSessionState.atom, + BILLING_CHECKOUT_SESSION_DEFAULT_VALUE, + ); } }, - [], - ); + }; + + const queryParams = new URLSearchParams(window.location.search); + + for (const [paramName, handler] of Object.entries(handlers)) { + const value = queryParams.get(paramName); + if (value !== null) { + handler(value); + } + } + }, []); return { initializeQueryParamState }; }; diff --git a/packages/twenty-front/src/modules/app/hooks/useMetadataStore.ts b/packages/twenty-front/src/modules/app/hooks/useMetadataStore.ts new file mode 100644 index 0000000000..355bbc5938 --- /dev/null +++ b/packages/twenty-front/src/modules/app/hooks/useMetadataStore.ts @@ -0,0 +1,84 @@ +import { + metadataStoreState, + type MetadataKey, + type MetadataLoadEntry, +} from '@/app/states/metadataStoreState'; +import { shouldAppBeLoadingState } from '@/object-metadata/states/shouldAppBeLoadingState'; +import { useStore } from 'jotai'; +import { useCallback } from 'react'; + +const EMPTY_ENTRY: MetadataLoadEntry = { + current: [], + draft: [], + status: 'empty', +}; + +// Views reference objects via objectMetadataId — both drafts must agree on IDs. +const areDraftsConsistent = ( + objectsDraft: object[], + viewsDraft: object[], +): boolean => { + const objectIds = new Set( + objectsDraft.map((item) => (item as { id: string }).id), + ); + + return viewsDraft.every((view) => + objectIds.has((view as { objectMetadataId: string }).objectMetadataId), + ); +}; + +export const useMetadataStore = () => { + const store = useStore(); + + const updateDraft = useCallback( + (key: MetadataKey, data: object[]) => { + store.set(metadataStoreState.atomFamily(key), (prev) => ({ + ...prev, + draft: data, + status: 'draft_pending' as const, + })); + }, + [store], + ); + + // Validates consistency then atomically promotes all 'draft_pending' drafts + // to current. If objects and views are inconsistent (e.g. mocked objects with + // real views), resets the stale entries back to 'empty' so effects re-fetch. + const applyChanges = useCallback(() => { + const objectsEntry = store.get(metadataStoreState.atomFamily('objects')); + const viewsEntry = store.get(metadataStoreState.atomFamily('views')); + + if ( + objectsEntry.status === 'draft_pending' && + viewsEntry.status === 'draft_pending' && + !areDraftsConsistent(objectsEntry.draft, viewsEntry.draft) + ) { + store.set(metadataStoreState.atomFamily('objects'), EMPTY_ENTRY); + store.set(metadataStoreState.atomFamily('views'), EMPTY_ENTRY); + return; + } + + const allKeys: MetadataKey[] = [ + 'objects', + 'views', + 'pageLayouts', + 'logicFunctions', + ]; + + for (const key of allKeys) { + const entry = store.get(metadataStoreState.atomFamily(key)); + + if (entry.status === 'draft_pending') { + store.set(metadataStoreState.atomFamily(key), { + current: entry.draft, + draft: [], + status: 'loaded', + }); + } + } + + store.set(shouldAppBeLoadingState.atom, false); + }, [store]); + + return { updateDraft, applyChanges }; +}; diff --git a/packages/twenty-front/src/modules/app/states/isAppEffectRedirectEnabledState.ts b/packages/twenty-front/src/modules/app/states/isAppEffectRedirectEnabledState.ts index 1328590827..739694fb94 100644 --- a/packages/twenty-front/src/modules/app/states/isAppEffectRedirectEnabledState.ts +++ b/packages/twenty-front/src/modules/app/states/isAppEffectRedirectEnabledState.ts @@ -1,5 +1,5 @@ -import { createState } from '@/ui/utilities/state/utils/createState'; -export const isAppEffectRedirectEnabledState = createState({ +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; +export const isAppEffectRedirectEnabledState = createStateV2({ key: 'isAppEffectRedirectEnabledState', defaultValue: true, }); diff --git a/packages/twenty-front/src/modules/app/states/isAppLoadingState.ts b/packages/twenty-front/src/modules/app/states/isAppLoadingState.ts new file mode 100644 index 0000000000..4b02ae86ba --- /dev/null +++ b/packages/twenty-front/src/modules/app/states/isAppLoadingState.ts @@ -0,0 +1,12 @@ +import { metadataStoreState } from '@/app/states/metadataStoreState'; +import { createSelectorV2 } from '@/ui/utilities/state/jotai/utils/createSelectorV2'; + +export const isAppLoadingState = createSelectorV2({ + key: 'isAppLoadingState', + get: ({ get }) => { + const objectsEntry = get(metadataStoreState, 'objects'); + const viewsEntry = get(metadataStoreState, 'views'); + + return objectsEntry.status !== 'loaded' || viewsEntry.status !== 'loaded'; + }, +}); diff --git a/packages/twenty-front/src/modules/app/states/metadataStoreState.ts b/packages/twenty-front/src/modules/app/states/metadataStoreState.ts new file mode 100644 index 0000000000..3ea166d962 --- /dev/null +++ b/packages/twenty-front/src/modules/app/states/metadataStoreState.ts @@ -0,0 +1,29 @@ +import { createFamilyStateV2 } from '@/ui/utilities/state/jotai/utils/createFamilyStateV2'; + +export type MetadataLoadStatus = 'empty' | 'draft_pending' | 'loaded'; + +export type MetadataKey = + | 'objects' + | 'views' + | 'pageLayouts' + | 'logicFunctions'; + +export type MetadataLoadEntry = { + current: object[]; + draft: object[]; + status: MetadataLoadStatus; +}; + +const METADATA_LOAD_ENTRY_DEFAULT: MetadataLoadEntry = { + current: [], + draft: [], + status: 'empty', +}; + +export const metadataStoreState = createFamilyStateV2< + MetadataLoadEntry, + MetadataKey +>({ + key: 'metadataStoreState', + defaultValue: METADATA_LOAD_ENTRY_DEFAULT, +}); diff --git a/packages/twenty-front/src/modules/app/states/verifyEmailRedirectPathState.ts b/packages/twenty-front/src/modules/app/states/verifyEmailRedirectPathState.ts index 2b368eb791..af1409f3e1 100644 --- a/packages/twenty-front/src/modules/app/states/verifyEmailRedirectPathState.ts +++ b/packages/twenty-front/src/modules/app/states/verifyEmailRedirectPathState.ts @@ -1,6 +1,6 @@ -import { createState } from '@/ui/utilities/state/utils/createState'; +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; -export const verifyEmailRedirectPathState = createState({ +export const verifyEmailRedirectPathState = createStateV2({ key: 'verifyEmailRedirectPathState', defaultValue: undefined, }); diff --git a/packages/twenty-front/src/modules/auth/components/AuthProvider.tsx b/packages/twenty-front/src/modules/auth/components/AuthProvider.tsx index 9649907e4a..0bcddad5ba 100644 --- a/packages/twenty-front/src/modules/auth/components/AuthProvider.tsx +++ b/packages/twenty-front/src/modules/auth/components/AuthProvider.tsx @@ -1,13 +1,15 @@ import React from 'react'; -import { useRecoilValue } from 'recoil'; import { AuthContext } from '@/auth/contexts/AuthContext'; import { currentWorkspaceDeletedMembersState } from '@/auth/states/currentWorkspaceDeletedMembersState'; import { currentWorkspaceMembersState } from '@/auth/states/currentWorkspaceMembersState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; export const AuthProvider = ({ children }: React.PropsWithChildren) => { - const currentWorkspaceMembers = useRecoilValue(currentWorkspaceMembersState); - const currentWorkspaceDeletedMembers = useRecoilValue( + const currentWorkspaceMembers = useRecoilValueV2( + currentWorkspaceMembersState, + ); + const currentWorkspaceDeletedMembers = useRecoilValueV2( currentWorkspaceDeletedMembersState, ); diff --git a/packages/twenty-front/src/modules/auth/components/TwoFactorAuthenticationProvisionEffect.tsx b/packages/twenty-front/src/modules/auth/components/TwoFactorAuthenticationProvisionEffect.tsx index 71dd50b586..6e19e67886 100644 --- a/packages/twenty-front/src/modules/auth/components/TwoFactorAuthenticationProvisionEffect.tsx +++ b/packages/twenty-front/src/modules/auth/components/TwoFactorAuthenticationProvisionEffect.tsx @@ -5,7 +5,6 @@ import { useCurrentUserWorkspaceTwoFactorAuthentication } from '@/settings/two-f import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { useLingui } from '@lingui/react/macro'; import { useEffect } from 'react'; -import { useRecoilValue } from 'recoil'; import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; import { AppPath } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; @@ -19,7 +18,7 @@ export const TwoFactorAuthenticationSetupEffect = () => { const navigate = useNavigateApp(); const { origin } = useOrigin(); - const loginToken = useRecoilValue(loginTokenState); + const loginToken = useRecoilValueV2(loginTokenState); const qrCode = useRecoilValueV2(qrCodeState); const setQrCodeState = useSetRecoilStateV2(qrCodeState); diff --git a/packages/twenty-front/src/modules/auth/components/VerifyEmailEffect.tsx b/packages/twenty-front/src/modules/auth/components/VerifyEmailEffect.tsx index 849bdfe442..58623c232c 100644 --- a/packages/twenty-front/src/modules/auth/components/VerifyEmailEffect.tsx +++ b/packages/twenty-front/src/modules/auth/components/VerifyEmailEffect.tsx @@ -12,11 +12,12 @@ import { Modal } from '@/ui/layout/modal/components/Modal'; import { useLingui } from '@lingui/react/macro'; import { useEffect, useState } from 'react'; import { useSearchParams } from 'react-router-dom'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { isDefined } from 'twenty-shared/utils'; import { useNavigateApp } from '~/hooks/useNavigateApp'; import { getWorkspaceUrl } from '~/utils/getWorkspaceUrl'; import { EmailVerificationSent } from '@/auth/sign-in-up/components/EmailVerificationSent'; +import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; export const VerifyEmailEffect = () => { const { @@ -29,7 +30,7 @@ export const VerifyEmailEffect = () => { const [searchParams] = useSearchParams(); const [isError, setIsError] = useState(false); - const setVerifyEmailRedirectPath = useSetRecoilState( + const setVerifyEmailRedirectPath = useSetRecoilStateV2( verifyEmailRedirectPathState, ); @@ -41,7 +42,7 @@ export const VerifyEmailEffect = () => { const { redirectToWorkspaceDomain } = useRedirectToWorkspaceDomain(); const { verifyLoginToken } = useVerifyLogin(); const { isOnAWorkspace } = useIsCurrentLocationOnAWorkspace(); - const clientConfigApiStatus = useRecoilValue(clientConfigApiStatusState); + const clientConfigApiStatus = useRecoilValueV2(clientConfigApiStatusState); const { t } = useLingui(); useEffect(() => { diff --git a/packages/twenty-front/src/modules/auth/components/VerifyLoginTokenEffect.tsx b/packages/twenty-front/src/modules/auth/components/VerifyLoginTokenEffect.tsx index 922bcccde9..cb2cb48db7 100644 --- a/packages/twenty-front/src/modules/auth/components/VerifyLoginTokenEffect.tsx +++ b/packages/twenty-front/src/modules/auth/components/VerifyLoginTokenEffect.tsx @@ -4,7 +4,7 @@ import { useSearchParams } from 'react-router-dom'; import { useIsLogged } from '@/auth/hooks/useIsLogged'; import { useVerifyLogin } from '@/auth/hooks/useVerifyLogin'; import { clientConfigApiStatusState } from '@/client-config/states/clientConfigApiStatusState'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { AppPath } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { useNavigateApp } from '~/hooks/useNavigateApp'; @@ -17,7 +17,7 @@ export const VerifyLoginTokenEffect = () => { const navigate = useNavigateApp(); const { verifyLoginToken } = useVerifyLogin(); - const { isSaved: clientConfigLoaded } = useRecoilValue( + const { isSaved: clientConfigLoaded } = useRecoilValueV2( clientConfigApiStatusState, ); diff --git a/packages/twenty-front/src/modules/auth/hooks/__tests__/useAuth.test.tsx b/packages/twenty-front/src/modules/auth/hooks/__tests__/useAuth.test.tsx index 91b59b15e0..a8b23bbe71 100644 --- a/packages/twenty-front/src/modules/auth/hooks/__tests__/useAuth.test.tsx +++ b/packages/twenty-front/src/modules/auth/hooks/__tests__/useAuth.test.tsx @@ -4,11 +4,12 @@ import { isDeveloperDefaultSignInPrefilledState } from '@/client-config/states/i import { supportChatState } from '@/client-config/states/supportChatState'; import { workspaceAuthProvidersState } from '@/workspace/states/workspaceAuthProvidersState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { useApolloClient } from '@apollo/client'; import { MockedProvider } from '@apollo/client/testing'; import { type ReactNode, act } from 'react'; import { MemoryRouter } from 'react-router-dom'; -import { RecoilRoot, useRecoilValue } from 'recoil'; +import { RecoilRoot } from 'recoil'; import { email, @@ -158,15 +159,15 @@ describe('useAuth', () => { const { result } = renderHook( () => { const client = useApolloClient(); - const workspaceAuthProviders = useRecoilValue( + const workspaceAuthProviders = useRecoilValueV2( workspaceAuthProvidersState, ); - const billing = useRecoilValue(billingState); - const isDeveloperDefaultSignInPrefilled = useRecoilValue( + const billing = useRecoilValueV2(billingState); + const isDeveloperDefaultSignInPrefilled = useRecoilValueV2( isDeveloperDefaultSignInPrefilledState, ); - const supportChat = useRecoilValue(supportChatState); - const isMultiWorkspaceEnabled = useRecoilValue( + const supportChat = useRecoilValueV2(supportChatState); + const isMultiWorkspaceEnabled = useRecoilValueV2( isMultiWorkspaceEnabledState, ); return { diff --git a/packages/twenty-front/src/modules/auth/hooks/__tests__/useIsLogged.test.ts b/packages/twenty-front/src/modules/auth/hooks/__tests__/useIsLogged.test.ts index 4f88277a7a..9bec4278a8 100644 --- a/packages/twenty-front/src/modules/auth/hooks/__tests__/useIsLogged.test.ts +++ b/packages/twenty-front/src/modules/auth/hooks/__tests__/useIsLogged.test.ts @@ -1,14 +1,15 @@ import { act, renderHook } from '@testing-library/react'; -import { RecoilRoot, useSetRecoilState } from 'recoil'; +import { RecoilRoot } from 'recoil'; import { useIsLogged } from '@/auth/hooks/useIsLogged'; import { tokenPairState } from '@/auth/states/tokenPairState'; +import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; const renderHooks = () => { const { result } = renderHook( () => { const isLogged = useIsLogged(); - const setTokenPair = useSetRecoilState(tokenPairState); + const setTokenPair = useSetRecoilStateV2(tokenPairState); return { isLogged, diff --git a/packages/twenty-front/src/modules/auth/hooks/useAuth.ts b/packages/twenty-front/src/modules/auth/hooks/useAuth.ts index 98cc7884e9..f61883e140 100644 --- a/packages/twenty-front/src/modules/auth/hooks/useAuth.ts +++ b/packages/twenty-front/src/modules/auth/hooks/useAuth.ts @@ -4,14 +4,9 @@ import { snapshot_UNSTABLE, useGotoRecoilSnapshot, useRecoilCallback, - useRecoilValue, - useSetRecoilState, } from 'recoil'; import { AppPath } from 'twenty-shared/types'; -import { billingState } from '@/client-config/states/billingState'; -import { clientConfigApiStatusState } from '@/client-config/states/clientConfigApiStatusState'; -import { supportChatState } from '@/client-config/states/supportChatState'; import { REACT_APP_SERVER_BASE_URL } from '~/config'; import { useCheckUserExistsLazyQuery, @@ -28,11 +23,19 @@ import { } from '~/generated-metadata/graphql'; import { tokenPairState } from '@/auth/states/tokenPairState'; -import { isDeveloperDefaultSignInPrefilledState } from '@/client-config/states/isDeveloperDefaultSignInPrefilledState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; +import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { isAppEffectRedirectEnabledState } from '@/app/states/isAppEffectRedirectEnabledState'; +import { availableWorkspacesState } from '@/auth/states/availableWorkspacesState'; +import { currentUserState } from '@/auth/states/currentUserState'; +import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState'; +import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; +import { currentWorkspaceMembersState } from '@/auth/states/currentWorkspaceMembersState'; +import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { useSignUpInNewWorkspace } from '@/auth/sign-in-up/hooks/useSignUpInNewWorkspace'; -import { isCurrentUserLoadedState } from '@/auth/states/isCurrentUserLoadedState'; +import { metadataStoreState } from '@/app/states/metadataStoreState'; import { lastAuthenticatedMethodState } from '@/auth/states/lastAuthenticatedMethodState'; import { loginTokenState } from '@/auth/states/loginTokenState'; import { @@ -40,6 +43,7 @@ import { signInUpStepState, } from '@/auth/states/signInUpStepState'; import { workspacePublicDataState } from '@/auth/states/workspacePublicDataState'; +import { coreViewsState } from '@/views/states/coreViewState'; import { type BillingCheckoutSession } from '@/auth/types/billingCheckoutSession.type'; import { countAvailableWorkspaces, @@ -47,11 +51,8 @@ import { } from '@/auth/utils/availableWorkspacesUtils'; import { useRequestFreshCaptchaToken } from '@/captcha/hooks/useRequestFreshCaptchaToken'; import { isCaptchaScriptLoadedState } from '@/captcha/states/isCaptchaScriptLoadedState'; -import { apiConfigState } from '@/client-config/states/apiConfigState'; -import { captchaState } from '@/client-config/states/captchaState'; import { isEmailVerificationRequiredState } from '@/client-config/states/isEmailVerificationRequiredState'; import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState'; -import { sentryConfigState } from '@/client-config/states/sentryConfigState'; import { useLastAuthenticatedWorkspaceDomain } from '@/domain-manager/hooks/useLastAuthenticatedWorkspaceDomain'; import { useOrigin } from '@/domain-manager/hooks/useOrigin'; import { useRedirect } from '@/domain-manager/hooks/useRedirect'; @@ -71,17 +72,19 @@ import { cookieStorage } from '~/utils/cookie-storage'; import { getWorkspaceUrl } from '~/utils/getWorkspaceUrl'; export const useAuth = () => { - const setTokenPair = useSetRecoilState(tokenPairState); - const setLoginToken = useSetRecoilState(loginTokenState); - const setIsAppEffectRedirectEnabled = useSetRecoilState( + const setTokenPair = useSetRecoilStateV2(tokenPairState); + const setLoginToken = useSetRecoilStateV2(loginTokenState); + const setIsAppEffectRedirectEnabled = useSetRecoilStateV2( isAppEffectRedirectEnabledState, ); const { origin } = useOrigin(); const { requestFreshCaptchaToken } = useRequestFreshCaptchaToken(); - const isCaptchaScriptLoaded = useRecoilValue(isCaptchaScriptLoadedState); - const isMultiWorkspaceEnabled = useRecoilValue(isMultiWorkspaceEnabledState); - const isEmailVerificationRequired = useRecoilValue( + const isCaptchaScriptLoaded = useRecoilValueV2(isCaptchaScriptLoadedState); + const isMultiWorkspaceEnabled = useRecoilValueV2( + isMultiWorkspaceEnabledState, + ); + const isEmailVerificationRequired = useRecoilValueV2( isEmailVerificationRequiredState, ); const { loadCurrentUser } = useLoadCurrentUser(); @@ -89,7 +92,7 @@ export const useAuth = () => { const { refreshObjectMetadataItems } = useRefreshObjectMetadataItems(); const { createWorkspace } = useSignUpInNewWorkspace(); - const setSignInUpStep = useSetRecoilState(signInUpStepState); + const setSignInUpStep = useSetRecoilStateV2(signInUpStepState); const { redirect } = useRedirect(); const { redirectToWorkspaceDomain } = useRedirectToWorkspaceDomain(); @@ -106,7 +109,7 @@ export const useAuth = () => { useVerifyEmailAndGetWorkspaceAgnosticTokenMutation(); const [getAuthTokensFromOtp] = useGetAuthTokensFromOtpMutation(); - const workspacePublicData = useRecoilValue(workspacePublicDataState); + const workspacePublicData = useRecoilValueV2(workspacePublicDataState); const { setLastAuthenticateWorkspaceDomain } = useLastAuthenticatedWorkspaceDomain(); @@ -123,7 +126,7 @@ export const useAuth = () => { const { loadMockedObjectMetadataItems } = useLoadMockedObjectMetadataItems(); const clearSession = useRecoilCallback( - ({ snapshot, set }) => + ({ snapshot }) => async () => { const sseClient = getSnapshotValue(snapshot, sseClientState); @@ -131,53 +134,23 @@ export const useAuth = () => { const emptySnapshot = snapshot_UNSTABLE(); - const authProvidersValue = snapshot - .getLoadable(workspaceAuthProvidersState) - .getValue(); - const billing = snapshot.getLoadable(billingState).getValue(); - const isDeveloperDefaultSignInPrefilled = snapshot - .getLoadable(isDeveloperDefaultSignInPrefilledState) - .getValue(); - const supportChat = snapshot.getLoadable(supportChatState).getValue(); - const captcha = snapshot.getLoadable(captchaState).getValue(); - const clientConfigApiStatus = snapshot - .getLoadable(clientConfigApiStatusState) - .getValue(); - const isCurrentUserLoaded = snapshot - .getLoadable(isCurrentUserLoadedState) - .getValue(); - const isMultiWorkspaceEnabled = snapshot - .getLoadable(isMultiWorkspaceEnabledState) - .getValue(); - const domainConfiguration = snapshot - .getLoadable(domainConfigurationState) - .getValue(); - const apiConfig = snapshot.getLoadable(apiConfigState).getValue(); - const sentryConfig = snapshot.getLoadable(sentryConfigState).getValue(); - const workspacePublicData = snapshot - .getLoadable(workspacePublicDataState) - .getValue(); - const lastAuthenticatedMethod = snapshot - .getLoadable(lastAuthenticatedMethodState) - .getValue(); + const authProvidersValue = jotaiStore.get( + workspaceAuthProvidersState.atom, + ); + const domainConfigurationValue = jotaiStore.get( + domainConfigurationState.atom, + ); + const workspacePublicDataValue = jotaiStore.get( + workspacePublicDataState.atom, + ); + const lastAuthenticatedMethod = jotaiStore.get( + lastAuthenticatedMethodState.atom, + ); + const isCaptchaScriptLoadedValue = jotaiStore.get( + isCaptchaScriptLoadedState.atom, + ); - const initialSnapshot = emptySnapshot.map(({ set }) => { - set(workspaceAuthProvidersState, authProvidersValue); - set(billingState, billing); - set( - isDeveloperDefaultSignInPrefilledState, - isDeveloperDefaultSignInPrefilled, - ); - set(supportChatState, supportChat); - set(captchaState, captcha); - set(apiConfigState, apiConfig); - set(sentryConfigState, sentryConfig); - set(workspacePublicDataState, workspacePublicData); - set(clientConfigApiStatusState, clientConfigApiStatus); - set(isCurrentUserLoadedState, isCurrentUserLoaded); - set(isMultiWorkspaceEnabledState, isMultiWorkspaceEnabled); - set(domainConfigurationState, domainConfiguration); - set(isCaptchaScriptLoadedState, isCaptchaScriptLoaded); + const initialSnapshot = emptySnapshot.map(() => { return undefined; }); @@ -186,7 +159,53 @@ export const useAuth = () => { goToRecoilSnapshot(initialSnapshot); - set(lastAuthenticatedMethodState, lastAuthenticatedMethod); + jotaiStore.set(workspaceAuthProvidersState.atom, authProvidersValue); + jotaiStore.set(workspacePublicDataState.atom, workspacePublicDataValue); + jotaiStore.set(metadataStoreState.atomFamily('objects'), { + current: [], + draft: [], + status: 'empty', + }); + jotaiStore.set(metadataStoreState.atomFamily('views'), { + current: [], + draft: [], + status: 'empty', + }); + jotaiStore.set(metadataStoreState.atomFamily('pageLayouts'), { + current: [], + draft: [], + status: 'empty', + }); + jotaiStore.set(metadataStoreState.atomFamily('logicFunctions'), { + current: [], + draft: [], + status: 'empty', + }); + jotaiStore.set(domainConfigurationState.atom, domainConfigurationValue); + jotaiStore.set( + isCaptchaScriptLoadedState.atom, + isCaptchaScriptLoadedValue, + ); + jotaiStore.set( + lastAuthenticatedMethodState.atom, + lastAuthenticatedMethod, + ); + + // Reset user-data Jotai states that were migrated from Recoil + // (Recoil snapshot reset no longer handles these since they are Jotai V2) + jotaiStore.set(tokenPairState.atom, null); + jotaiStore.set(currentUserState.atom, null); + jotaiStore.set(currentWorkspaceState.atom, null); + jotaiStore.set(currentUserWorkspaceState.atom, null); + jotaiStore.set(currentWorkspaceMemberState.atom, null); + jotaiStore.set(currentWorkspaceMembersState.atom, []); + jotaiStore.set(availableWorkspacesState.atom, { + availableWorkspacesForSignIn: [], + availableWorkspacesForSignUp: [], + }); + jotaiStore.set(loginTokenState.atom, null); + jotaiStore.set(signInUpStepState.atom, SignInUpStep.Init); + jotaiStore.set(coreViewsState.atom, []); await client.clearStore(); setLastAuthenticateWorkspaceDomain(null); @@ -199,7 +218,6 @@ export const useAuth = () => { setLastAuthenticateWorkspaceDomain, loadMockedObjectMetadataItems, navigate, - isCaptchaScriptLoaded, ], ); diff --git a/packages/twenty-front/src/modules/auth/hooks/useIsLogged.ts b/packages/twenty-front/src/modules/auth/hooks/useIsLogged.ts index ec8966e557..6b41f3e4ad 100644 --- a/packages/twenty-front/src/modules/auth/hooks/useIsLogged.ts +++ b/packages/twenty-front/src/modules/auth/hooks/useIsLogged.ts @@ -1,8 +1,7 @@ -import { useRecoilState } from 'recoil'; - import { tokenPairState } from '@/auth/states/tokenPairState'; +import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2'; export const useIsLogged = (): boolean => { - const [tokenPair] = useRecoilState(tokenPairState); + const [tokenPair] = useRecoilStateV2(tokenPairState); return !!tokenPair; }; diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/components/EmailVerificationSent.tsx b/packages/twenty-front/src/modules/auth/sign-in-up/components/EmailVerificationSent.tsx index e877882d70..a6ba4f5fa5 100644 --- a/packages/twenty-front/src/modules/auth/sign-in-up/components/EmailVerificationSent.tsx +++ b/packages/twenty-front/src/modules/auth/sign-in-up/components/EmailVerificationSent.tsx @@ -9,7 +9,7 @@ import { } from '@/auth/states/signInUpStepState'; import { OnboardingModalCircularIcon } from '@/onboarding/components/OnboardingModalCircularIcon'; import { t } from '@lingui/core/macro'; -import { useSetRecoilState } from 'recoil'; +import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; import { IconGmail, IconMail, @@ -89,7 +89,7 @@ export const EmailVerificationSent = ({ email: string | null; isError?: boolean; }) => { - const setSignInUpStep = useSetRecoilState(signInUpStepState); + const setSignInUpStep = useSetRecoilStateV2(signInUpStepState); const { handleResendEmailVerificationToken, loading: isLoading } = useHandleResendEmailVerificationToken(); diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/components/SignInUpGlobalScopeForm.tsx b/packages/twenty-front/src/modules/auth/sign-in-up/components/SignInUpGlobalScopeForm.tsx index 8357bce15c..ee0082e906 100644 --- a/packages/twenty-front/src/modules/auth/sign-in-up/components/SignInUpGlobalScopeForm.tsx +++ b/packages/twenty-front/src/modules/auth/sign-in-up/components/SignInUpGlobalScopeForm.tsx @@ -5,7 +5,6 @@ import styled from '@emotion/styled'; import { Trans, useLingui } from '@lingui/react/macro'; import { motion } from 'framer-motion'; import { FormProvider } from 'react-hook-form'; -import { useRecoilValue } from 'recoil'; import { ClickToActionLink, UndecoratedLink } from 'twenty-ui/navigation'; import { useAuth } from '@/auth/hooks/useAuth'; @@ -126,12 +125,12 @@ const StyledActionLinkContainer = styled.div` export const SignInUpGlobalScopeForm = () => { const authProviders = useRecoilValueV2(authProvidersState); - const signInUpStep = useRecoilValue(signInUpStepState); + const signInUpStep = useRecoilValueV2(signInUpStepState); const { buildWorkspaceUrl } = useBuildWorkspaceUrl(); const { signOut } = useAuth(); const { createWorkspace } = useSignUpInNewWorkspace(); - const availableWorkspaces = useRecoilValue(availableWorkspacesState); + const availableWorkspaces = useRecoilValueV2(availableWorkspacesState); const theme = useTheme(); const { t } = useLingui(); diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/components/SignInUpWorkspaceScopeForm.tsx b/packages/twenty-front/src/modules/auth/sign-in-up/components/SignInUpWorkspaceScopeForm.tsx index 7d0823a416..99d3a49e67 100644 --- a/packages/twenty-front/src/modules/auth/sign-in-up/components/SignInUpWorkspaceScopeForm.tsx +++ b/packages/twenty-front/src/modules/auth/sign-in-up/components/SignInUpWorkspaceScopeForm.tsx @@ -12,9 +12,9 @@ import { workspaceAuthProvidersState } from '@/workspace/states/workspaceAuthPro import styled from '@emotion/styled'; import { Trans } from '@lingui/react/macro'; import { FormProvider } from 'react-hook-form'; -import { useRecoilValue } from 'recoil'; import { HorizontalSeparator } from 'twenty-ui/display'; import { ClickToActionLink } from 'twenty-ui/navigation'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; const StyledContentContainer = styled.div` margin-bottom: ${({ theme }) => theme.spacing(8)}; @@ -23,8 +23,8 @@ const StyledContentContainer = styled.div` `; export const SignInUpWorkspaceScopeForm = () => { - const workspaceAuthProviders = useRecoilValue(workspaceAuthProvidersState); - const workspaceAuthBypassProviders = useRecoilValue( + const workspaceAuthProviders = useRecoilValueV2(workspaceAuthProvidersState); + const workspaceAuthBypassProviders = useRecoilValueV2( workspaceAuthBypassProvidersState, ); const { shouldOfferBypass, shouldUseBypass } = useWorkspaceBypass(); diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/components/internal/SignInUpGlobalScopeFormEffect.tsx b/packages/twenty-front/src/modules/auth/sign-in-up/components/internal/SignInUpGlobalScopeFormEffect.tsx index 707ee869b5..fecfc67d72 100644 --- a/packages/twenty-front/src/modules/auth/sign-in-up/components/internal/SignInUpGlobalScopeFormEffect.tsx +++ b/packages/twenty-front/src/modules/auth/sign-in-up/components/internal/SignInUpGlobalScopeFormEffect.tsx @@ -6,11 +6,11 @@ import { import { useLoadCurrentUser } from '@/users/hooks/useLoadCurrentUser'; import { useEffect } from 'react'; import { useSearchParams } from 'react-router-dom'; -import { useSetRecoilState } from 'recoil'; +import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; import { isDefined } from 'twenty-shared/utils'; export const SignInUpGlobalScopeFormEffect = () => { - const setSignInUpStep = useSetRecoilState(signInUpStepState); + const setSignInUpStep = useSetRecoilStateV2(signInUpStepState); const [searchParams, setSearchParams] = useSearchParams(); const { setAuthTokens } = useAuth(); const { loadCurrentUser } = useLoadCurrentUser(); diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/components/internal/SignInUpSSOIdentityProviderSelection.tsx b/packages/twenty-front/src/modules/auth/sign-in-up/components/internal/SignInUpSSOIdentityProviderSelection.tsx index 31eda6d459..32d86ef8ed 100644 --- a/packages/twenty-front/src/modules/auth/sign-in-up/components/internal/SignInUpSSOIdentityProviderSelection.tsx +++ b/packages/twenty-front/src/modules/auth/sign-in-up/components/internal/SignInUpSSOIdentityProviderSelection.tsx @@ -3,13 +3,13 @@ import { useSSO } from '@/auth/sign-in-up/hooks/useSSO'; import { guessSSOIdentityProviderIconByUrl } from '@/settings/security/utils/guessSSOIdentityProviderIconByUrl'; import styled from '@emotion/styled'; -import { useRecoilValue } from 'recoil'; import { workspaceAuthProvidersState } from '@/workspace/states/workspaceAuthProvidersState'; import React from 'react'; import { isDefined } from 'twenty-shared/utils'; import { HorizontalSeparator } from 'twenty-ui/display'; import { MainButton } from 'twenty-ui/input'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; const StyledContentContainer = styled.div` margin-bottom: ${({ theme }) => theme.spacing(8)}; @@ -17,7 +17,7 @@ const StyledContentContainer = styled.div` `; export const SignInUpSSOIdentityProviderSelection = () => { - const workspaceAuthProviders = useRecoilValue(workspaceAuthProvidersState); + const workspaceAuthProviders = useRecoilValueV2(workspaceAuthProvidersState); const { redirectToSSOLoginPage } = useSSO(); diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/components/internal/SignInUpTwoFactorAuthenticationProvision.tsx b/packages/twenty-front/src/modules/auth/sign-in-up/components/internal/SignInUpTwoFactorAuthenticationProvision.tsx index 532c83ebf5..300e894a4c 100644 --- a/packages/twenty-front/src/modules/auth/sign-in-up/components/internal/SignInUpTwoFactorAuthenticationProvision.tsx +++ b/packages/twenty-front/src/modules/auth/sign-in-up/components/internal/SignInUpTwoFactorAuthenticationProvision.tsx @@ -9,7 +9,7 @@ import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { Trans, useLingui } from '@lingui/react/macro'; import QRCode from 'react-qr-code'; -import { useSetRecoilState } from 'recoil'; +import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; import { IconCopy } from 'twenty-ui/display'; import { Loader } from 'twenty-ui/feedback'; import { MainButton } from 'twenty-ui/input'; @@ -71,7 +71,7 @@ export const SignInUpTwoFactorAuthenticationProvision = () => { const theme = useTheme(); const { copyToClipboard } = useCopyToClipboard(); const qrCode = useRecoilValueV2(qrCodeState); - const setSignInUpStep = useSetRecoilState(signInUpStepState); + const setSignInUpStep = useSetRecoilStateV2(signInUpStepState); const handleClick = () => { setSignInUpStep(SignInUpStep.TwoFactorAuthenticationVerification); diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/components/internal/SignInUpTwoFactorAuthenticationVerification.tsx b/packages/twenty-front/src/modules/auth/sign-in-up/components/internal/SignInUpTwoFactorAuthenticationVerification.tsx index d462678c8c..bf50d1d6e1 100644 --- a/packages/twenty-front/src/modules/auth/sign-in-up/components/internal/SignInUpTwoFactorAuthenticationVerification.tsx +++ b/packages/twenty-front/src/modules/auth/sign-in-up/components/internal/SignInUpTwoFactorAuthenticationVerification.tsx @@ -18,11 +18,12 @@ import { Trans, useLingui } from '@lingui/react/macro'; import { OTPInput, type SlotProps } from 'input-otp'; import { useState } from 'react'; import { Controller } from 'react-hook-form'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; import { AppPath } from 'twenty-shared/types'; import { MainButton } from 'twenty-ui/input'; import { ClickToActionLink } from 'twenty-ui/navigation'; import { useNavigateApp } from '~/hooks/useNavigateApp'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; +import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; const StyledMainContentContainer = styled.div` margin-bottom: ${({ theme }) => theme.spacing(8)}; @@ -186,8 +187,8 @@ export const SignInUpTOTPVerification = () => { const navigate = useNavigateApp(); const { readCaptchaToken } = useReadCaptchaToken(); const { isCaptchaReady } = useCaptcha(); - const loginToken = useRecoilValue(loginTokenState); - const setSignInUpStep = useSetRecoilState(signInUpStepState); + const loginToken = useRecoilValueV2(loginTokenState); + const setSignInUpStep = useSetRecoilStateV2(signInUpStepState); const { t } = useLingui(); const { form } = useTwoFactorAuthenticationForm(); diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/components/internal/SignInUpWithCredentials.tsx b/packages/twenty-front/src/modules/auth/sign-in-up/components/internal/SignInUpWithCredentials.tsx index 96ddc5c632..9d709b8e39 100644 --- a/packages/twenty-front/src/modules/auth/sign-in-up/components/internal/SignInUpWithCredentials.tsx +++ b/packages/twenty-front/src/modules/auth/sign-in-up/components/internal/SignInUpWithCredentials.tsx @@ -19,10 +19,11 @@ import styled from '@emotion/styled'; import { useLingui } from '@lingui/react/macro'; import { useMemo, useState } from 'react'; import { useFormContext } from 'react-hook-form'; -import { useRecoilState, useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { Loader } from 'twenty-ui/feedback'; import { MainButton } from 'twenty-ui/input'; +import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; const StyledForm = styled.form` align-items: center; @@ -39,13 +40,15 @@ export const SignInUpWithCredentials = ({ const { t } = useLingui(); const form = useFormContext
(); - const [signInUpStep, setSignInUpStep] = useRecoilState(signInUpStepState); + const [signInUpStep, setSignInUpStep] = useRecoilStateV2(signInUpStepState); const [showErrors, setShowErrors] = useState(false); - const captcha = useRecoilValue(captchaState); - const isRequestingCaptchaToken = useRecoilValue( + const captcha = useRecoilValueV2(captchaState); + const isRequestingCaptchaToken = useRecoilValueV2( isRequestingCaptchaTokenState, ); - const lastAuthenticatedMethod = useRecoilValue(lastAuthenticatedMethodState); + const lastAuthenticatedMethod = useRecoilValueV2( + lastAuthenticatedMethodState, + ); const hasMultipleAuthMethods = useHasMultipleAuthMethods(); const { diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/components/internal/SignInUpWithGoogle.tsx b/packages/twenty-front/src/modules/auth/sign-in-up/components/internal/SignInUpWithGoogle.tsx index fd0c789d07..4e61f26477 100644 --- a/packages/twenty-front/src/modules/auth/sign-in-up/components/internal/SignInUpWithGoogle.tsx +++ b/packages/twenty-front/src/modules/auth/sign-in-up/components/internal/SignInUpWithGoogle.tsx @@ -10,9 +10,10 @@ import { type SocialSSOSignInUpActionType } from '@/auth/types/socialSSOSignInUp import { useTheme } from '@emotion/react'; import { useLingui } from '@lingui/react/macro'; import { memo } from 'react'; -import { useRecoilState, useRecoilValue } from 'recoil'; import { HorizontalSeparator, IconGoogle } from 'twenty-ui/display'; import { MainButton } from 'twenty-ui/input'; +import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { LastUsedPill } from './LastUsedPill'; import { StyledSSOButtonContainer } from './SignInUpSSOButtonStyles'; @@ -29,10 +30,9 @@ export const SignInUpWithGoogle = ({ isGlobalScope?: boolean; }) => { const { t } = useLingui(); - const signInUpStep = useRecoilValue(signInUpStepState); - const [lastAuthenticatedMethod, setLastAuthenticatedMethod] = useRecoilState( - lastAuthenticatedMethodState, - ); + const signInUpStep = useRecoilValueV2(signInUpStepState); + const [lastAuthenticatedMethod, setLastAuthenticatedMethod] = + useRecoilStateV2(lastAuthenticatedMethodState); const { signInWithGoogle } = useSignInWithGoogle(); const hasMultipleAuthMethods = useHasMultipleAuthMethods(); diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/components/internal/SignInUpWithMicrosoft.tsx b/packages/twenty-front/src/modules/auth/sign-in-up/components/internal/SignInUpWithMicrosoft.tsx index c8faae462c..f16b95a541 100644 --- a/packages/twenty-front/src/modules/auth/sign-in-up/components/internal/SignInUpWithMicrosoft.tsx +++ b/packages/twenty-front/src/modules/auth/sign-in-up/components/internal/SignInUpWithMicrosoft.tsx @@ -9,9 +9,10 @@ import { AuthenticatedMethod } from '@/auth/types/AuthenticatedMethod.enum'; import { type SocialSSOSignInUpActionType } from '@/auth/types/socialSSOSignInUp.type'; import { useTheme } from '@emotion/react'; import { useLingui } from '@lingui/react/macro'; -import { useRecoilState, useRecoilValue } from 'recoil'; import { HorizontalSeparator, IconMicrosoft } from 'twenty-ui/display'; import { MainButton } from 'twenty-ui/input'; +import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { LastUsedPill } from './LastUsedPill'; import { StyledSSOButtonContainer } from './SignInUpSSOButtonStyles'; @@ -25,10 +26,9 @@ export const SignInUpWithMicrosoft = ({ const theme = useTheme(); const { t } = useLingui(); - const signInUpStep = useRecoilValue(signInUpStepState); - const [lastAuthenticatedMethod, setLastAuthenticatedMethod] = useRecoilState( - lastAuthenticatedMethodState, - ); + const signInUpStep = useRecoilValueV2(signInUpStepState); + const [lastAuthenticatedMethod, setLastAuthenticatedMethod] = + useRecoilStateV2(lastAuthenticatedMethodState); const { signInWithMicrosoft } = useSignInWithMicrosoft(); const hasMultipleAuthMethods = useHasMultipleAuthMethods(); diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/components/internal/SignInUpWithSSO.tsx b/packages/twenty-front/src/modules/auth/sign-in-up/components/internal/SignInUpWithSSO.tsx index a3af13ba05..a5c160aefa 100644 --- a/packages/twenty-front/src/modules/auth/sign-in-up/components/internal/SignInUpWithSSO.tsx +++ b/packages/twenty-front/src/modules/auth/sign-in-up/components/internal/SignInUpWithSSO.tsx @@ -7,9 +7,11 @@ import { } from '@/auth/states/signInUpStepState'; import { AuthenticatedMethod } from '@/auth/types/AuthenticatedMethod.enum'; import { workspaceAuthProvidersState } from '@/workspace/states/workspaceAuthProvidersState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; +import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2'; +import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; import { useTheme } from '@emotion/react'; import { useLingui } from '@lingui/react/macro'; -import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { HorizontalSeparator, IconLock } from 'twenty-ui/display'; import { MainButton } from 'twenty-ui/input'; @@ -19,12 +21,11 @@ import { StyledSSOButtonContainer } from './SignInUpSSOButtonStyles'; export const SignInUpWithSSO = () => { const theme = useTheme(); const { t } = useLingui(); - const setSignInUpStep = useSetRecoilState(signInUpStepState); - const workspaceAuthProviders = useRecoilValue(workspaceAuthProvidersState); - const signInUpStep = useRecoilValue(signInUpStepState); - const [lastAuthenticatedMethod, setLastAuthenticatedMethod] = useRecoilState( - lastAuthenticatedMethodState, - ); + const setSignInUpStep = useSetRecoilStateV2(signInUpStepState); + const workspaceAuthProviders = useRecoilValueV2(workspaceAuthProvidersState); + const signInUpStep = useRecoilValueV2(signInUpStepState); + const [lastAuthenticatedMethod, setLastAuthenticatedMethod] = + useRecoilStateV2(lastAuthenticatedMethodState); const hasMultipleAuthMethods = useHasMultipleAuthMethods(); const { redirectToSSOLoginPage } = useSSO(); diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/components/internal/SignInUpWorkspaceScopeFormEffect.tsx b/packages/twenty-front/src/modules/auth/sign-in-up/components/internal/SignInUpWorkspaceScopeFormEffect.tsx index 59fb79db63..cc903b0305 100644 --- a/packages/twenty-front/src/modules/auth/sign-in-up/components/internal/SignInUpWorkspaceScopeFormEffect.tsx +++ b/packages/twenty-front/src/modules/auth/sign-in-up/components/internal/SignInUpWorkspaceScopeFormEffect.tsx @@ -7,8 +7,9 @@ import { import { isRequestingCaptchaTokenState } from '@/captcha/states/isRequestingCaptchaTokenState'; import { captchaState } from '@/client-config/states/captchaState'; import { workspaceAuthProvidersState } from '@/workspace/states/workspaceAuthProvidersState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; +import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; import { useEffect, useState } from 'react'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; const searchParams = new URLSearchParams(window.location.search); @@ -21,13 +22,13 @@ enum LoadingStatus { } export const SignInUpWorkspaceScopeFormEffect = () => { - const workspaceAuthProviders = useRecoilValue(workspaceAuthProvidersState); + const workspaceAuthProviders = useRecoilValueV2(workspaceAuthProvidersState); - const isRequestingCaptchaToken = useRecoilValue( + const isRequestingCaptchaToken = useRecoilValueV2( isRequestingCaptchaTokenState, ); - const captcha = useRecoilValue(captchaState); + const captcha = useRecoilValueV2(captchaState); const [loadingStatus, setLoadingStatus] = useState( LoadingStatus.Loading, @@ -38,7 +39,7 @@ export const SignInUpWorkspaceScopeFormEffect = () => { const { signInUpStep, continueWithEmail, continueWithCredentials } = useSignInUp(form); - const setSignInUpStep = useSetRecoilState(signInUpStepState); + const setSignInUpStep = useSetRecoilStateV2(signInUpStepState); useEffect(() => { if (!workspaceAuthProviders) { diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/__tests__/useHandleResetPassword.test.ts b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/__tests__/useHandleResetPassword.test.ts index 33c9c7e819..5fb59de33d 100644 --- a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/__tests__/useHandleResetPassword.test.ts +++ b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/__tests__/useHandleResetPassword.test.ts @@ -1,11 +1,14 @@ import { i18n } from '@lingui/core'; import { I18nProvider } from '@lingui/react'; import { act, renderHook } from '@testing-library/react'; +import { type ReactNode, createElement } from 'react'; +import { Provider as JotaiProvider } from 'jotai'; import { RecoilRoot } from 'recoil'; import { useHandleResetPassword } from '@/auth/sign-in-up/hooks/useHandleResetPassword'; import { workspacePublicDataState } from '@/auth/states/workspacePublicDataState'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { SOURCE_LOCALE } from 'twenty-shared/translations'; import { type PublicWorkspaceDataOutput, @@ -20,16 +23,21 @@ jest.mock('~/generated-metadata/graphql'); dynamicActivate(SOURCE_LOCALE); const renderHooks = () => { + jotaiStore.set(workspacePublicDataState.atom, { + id: 'workspace-id', + } as PublicWorkspaceDataOutput); + const { result } = renderHook(() => useHandleResetPassword(), { - wrapper: ({ children }) => - RecoilRoot({ - initializeState: ({ set }) => { - set(workspacePublicDataState, { - id: 'workspace-id', - } as PublicWorkspaceDataOutput); - }, - children: I18nProvider({ i18n, children }), - }), + wrapper: ({ children }: { children: ReactNode }) => + createElement( + JotaiProvider, + { store: jotaiStore }, + createElement( + RecoilRoot, + null as any, + createElement(I18nProvider, { i18n }, children), + ), + ), }); return { result }; }; diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/__tests__/useSignInUpForm.test.tsx b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/__tests__/useSignInUpForm.test.tsx index 2986142644..34fad475ec 100644 --- a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/__tests__/useSignInUpForm.test.tsx +++ b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/__tests__/useSignInUpForm.test.tsx @@ -1,8 +1,13 @@ import { useSignInUpForm } from '@/auth/sign-in-up/hooks/useSignInUpForm'; import { isDeveloperDefaultSignInPrefilledState } from '@/client-config/states/isDeveloperDefaultSignInPrefilledState'; +import { + jotaiStore, + resetJotaiStore, +} from '@/ui/utilities/state/jotai/jotaiStore'; import { renderHook } from '@testing-library/react'; import { type ReactNode } from 'react'; import { MemoryRouter } from 'react-router-dom'; +import { Provider as JotaiProvider } from 'jotai'; import { RecoilRoot } from 'recoil'; const TestWrapper = ({ @@ -13,24 +18,24 @@ const TestWrapper = ({ children: ReactNode; initialEntry?: string; isDeveloperDefaultSignInPrefilled?: boolean; -}) => ( - - { - snapshot.set( - isDeveloperDefaultSignInPrefilledState, - isDeveloperDefaultSignInPrefilled, - ); - }} - > - {children} - - -); +}) => { + jotaiStore.set( + isDeveloperDefaultSignInPrefilledState.atom, + isDeveloperDefaultSignInPrefilled, + ); + return ( + + + {children} + + + ); +}; describe('useSignInUpForm', () => { beforeEach(() => { jest.clearAllMocks(); + resetJotaiStore(); }); it('should initialize the form with default values', async () => { diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useHandleResetPassword.ts b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useHandleResetPassword.ts index bbb94c694a..3cbdcca6b0 100644 --- a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useHandleResetPassword.ts +++ b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useHandleResetPassword.ts @@ -5,14 +5,14 @@ import { workspacePublicDataState } from '@/auth/states/workspacePublicDataState import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { ApolloError } from '@apollo/client'; import { useLingui } from '@lingui/react/macro'; -import { useRecoilValue } from 'recoil'; import { useEmailPasswordResetLinkMutation } from '~/generated-metadata/graphql'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; export const useHandleResetPassword = () => { const { enqueueErrorSnackBar, enqueueSuccessSnackBar } = useSnackBar(); const [emailPasswordResetLink] = useEmailPasswordResetLinkMutation(); - const workspacePublicData = useRecoilValue(workspacePublicDataState); - const currentUser = useRecoilValue(currentUserState); + const workspacePublicData = useRecoilValueV2(workspacePublicDataState); + const currentUser = useRecoilValueV2(currentUserState); const { t } = useLingui(); diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useHasMultipleAuthMethods.ts b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useHasMultipleAuthMethods.ts index ca19f894b6..12d97b5488 100644 --- a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useHasMultipleAuthMethods.ts +++ b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useHasMultipleAuthMethods.ts @@ -1,8 +1,8 @@ import { workspaceAuthProvidersState } from '@/workspace/states/workspaceAuthProvidersState'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; export const useHasMultipleAuthMethods = () => { - const workspaceAuthProviders = useRecoilValue(workspaceAuthProvidersState); + const workspaceAuthProviders = useRecoilValueV2(workspaceAuthProvidersState); if (!workspaceAuthProviders) { return false; diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSignInUp.ts b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSignInUp.ts index 24aefc2c4a..8c0a1444fc 100644 --- a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSignInUp.ts +++ b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSignInUp.ts @@ -18,23 +18,23 @@ import { useIsCurrentLocationOnAWorkspace } from '@/domain-manager/hooks/useIsCu import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { ApolloError } from '@apollo/client'; import { useLingui } from '@lingui/react/macro'; -import { useRecoilState, useSetRecoilState } from 'recoil'; import { AppPath } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { buildAppPathWithQueryParams } from '~/utils/buildAppPathWithQueryParams'; import { isMatchingLocation } from '~/utils/isMatchingLocation'; import { useAuth } from '@/auth/hooks/useAuth'; import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2'; +import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; export const useSignInUp = (form: UseFormReturn) => { const { enqueueErrorSnackBar } = useSnackBar(); const { t } = useLingui(); - const [signInUpStep, setSignInUpStep] = useRecoilState(signInUpStepState); + const [signInUpStep, setSignInUpStep] = useRecoilStateV2(signInUpStepState); const [signInUpMode, setSignInUpMode] = useRecoilStateV2(signInUpModeState); const { isOnAWorkspace } = useIsCurrentLocationOnAWorkspace(); const { isCaptchaReady } = useCaptcha(); - const setLastAuthenticatedMethod = useSetRecoilState( + const setLastAuthenticatedMethod = useSetRecoilStateV2( lastAuthenticatedMethodState, ); diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSignInUpForm.ts b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSignInUpForm.ts index 9ac3245428..eabc2b9474 100644 --- a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSignInUpForm.ts +++ b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSignInUpForm.ts @@ -3,7 +3,6 @@ import { t } from '@lingui/core/macro'; import { useEffect } from 'react'; import { useForm } from 'react-hook-form'; import { useSearchParams } from 'react-router-dom'; -import { useRecoilValue } from 'recoil'; import { z } from 'zod'; import { @@ -13,6 +12,7 @@ import { import { PASSWORD_REGEX } from '@/auth/utils/passwordRegex'; import { isDeveloperDefaultSignInPrefilledState } from '@/client-config/states/isDeveloperDefaultSignInPrefilledState'; import { isDefined } from 'twenty-shared/utils'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; const makeValidationSchema = (signInUpStep: SignInUpStep) => z @@ -34,11 +34,11 @@ const makeValidationSchema = (signInUpStep: SignInUpStep) => export type Form = z.infer>; export const useSignInUpForm = () => { - const signInUpStep = useRecoilValue(signInUpStepState); + const signInUpStep = useRecoilValueV2(signInUpStepState); const validationSchema = makeValidationSchema(signInUpStep); // Create schema based on the current step - const isDeveloperDefaultSignInPrefilled = useRecoilValue( + const isDeveloperDefaultSignInPrefilled = useRecoilValueV2( isDeveloperDefaultSignInPrefilledState, ); const [searchParams] = useSearchParams(); diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSignInWithMicrosoft.ts b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSignInWithMicrosoft.ts index 7cd359ea81..b310de5b9e 100644 --- a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSignInWithMicrosoft.ts +++ b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSignInWithMicrosoft.ts @@ -2,15 +2,15 @@ import { useParams, useSearchParams } from 'react-router-dom'; import { useAuth } from '@/auth/hooks/useAuth'; import { billingCheckoutSessionState } from '@/auth/states/billingCheckoutSessionState'; -import { useRecoilValue } from 'recoil'; import { type SocialSSOSignInUpActionType } from '@/auth/types/socialSSOSignInUp.type'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; export const useSignInWithMicrosoft = () => { const workspaceInviteHash = useParams().workspaceInviteHash; const [searchParams] = useSearchParams(); const workspacePersonalInviteToken = searchParams.get('inviteToken') ?? undefined; - const billingCheckoutSession = useRecoilValue(billingCheckoutSessionState); + const billingCheckoutSession = useRecoilValueV2(billingCheckoutSessionState); const { signInWithMicrosoft } = useAuth(); return { diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useWorkspaceBypass.ts b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useWorkspaceBypass.ts index 66fcc2ddfe..1d569d03a8 100644 --- a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useWorkspaceBypass.ts +++ b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useWorkspaceBypass.ts @@ -1,16 +1,16 @@ -import { useRecoilState, useRecoilValue } from 'recoil'; - import { useIsCurrentLocationOnAWorkspace } from '@/domain-manager/hooks/useIsCurrentLocationOnAWorkspace'; import { workspaceAuthBypassProvidersState } from '@/workspace/states/workspaceAuthBypassProvidersState'; import { workspaceAuthProvidersState } from '@/workspace/states/workspaceAuthProvidersState'; import { workspaceBypassModeState } from '@/workspace/states/workspaceBypassModeState'; +import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; export const useWorkspaceBypass = () => { - const workspaceAuthProviders = useRecoilValue(workspaceAuthProvidersState); - const workspaceAuthBypassProviders = useRecoilValue( + const workspaceAuthProviders = useRecoilValueV2(workspaceAuthProvidersState); + const workspaceAuthBypassProviders = useRecoilValueV2( workspaceAuthBypassProvidersState, ); - const [workspaceBypassMode, setWorkspaceBypassMode] = useRecoilState( + const [workspaceBypassMode, setWorkspaceBypassMode] = useRecoilStateV2( workspaceBypassModeState, ); diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useWorkspaceFromInviteHash.ts b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useWorkspaceFromInviteHash.ts index 79850a1ce1..3060aacdd4 100644 --- a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useWorkspaceFromInviteHash.ts +++ b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useWorkspaceFromInviteHash.ts @@ -1,8 +1,8 @@ import { useState } from 'react'; import { useParams } from 'react-router-dom'; -import { useRecoilValue } from 'recoil'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; @@ -16,7 +16,7 @@ export const useWorkspaceFromInviteHash = () => { const { enqueueErrorSnackBar, enqueueInfoSnackBar } = useSnackBar(); const navigate = useNavigateApp(); const workspaceInviteHash = useParams().workspaceInviteHash; - const currentWorkspace = useRecoilValue(currentWorkspaceState); + const currentWorkspace = useRecoilValueV2(currentWorkspaceState); const [initiallyLoggedIn] = useState(isDefined(currentWorkspace)); const { data: workspaceFromInviteHash, loading } = diff --git a/packages/twenty-front/src/modules/auth/states/availableWorkspacesState.ts b/packages/twenty-front/src/modules/auth/states/availableWorkspacesState.ts index 121ad19952..7092ad0aee 100644 --- a/packages/twenty-front/src/modules/auth/states/availableWorkspacesState.ts +++ b/packages/twenty-front/src/modules/auth/states/availableWorkspacesState.ts @@ -1,7 +1,7 @@ -import { createState } from '@/ui/utilities/state/utils/createState'; +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; import { type AvailableWorkspaces } from '~/generated-metadata/graphql'; -export const availableWorkspacesState = createState({ +export const availableWorkspacesState = createStateV2({ key: 'availableWorkspacesState', defaultValue: { availableWorkspacesForSignIn: [], diff --git a/packages/twenty-front/src/modules/auth/states/billingCheckoutSessionState.ts b/packages/twenty-front/src/modules/auth/states/billingCheckoutSessionState.ts index dae2a13e79..597bc166c0 100644 --- a/packages/twenty-front/src/modules/auth/states/billingCheckoutSessionState.ts +++ b/packages/twenty-front/src/modules/auth/states/billingCheckoutSessionState.ts @@ -1,8 +1,9 @@ import { type BillingCheckoutSession } from '@/auth/types/billingCheckoutSession.type'; import { BILLING_CHECKOUT_SESSION_DEFAULT_VALUE } from '@/billing/constants/BillingCheckoutSessionDefaultValue'; -import { createState } from '@/ui/utilities/state/utils/createState'; +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; -export const billingCheckoutSessionState = createState({ - key: 'billingCheckoutSessionState', - defaultValue: BILLING_CHECKOUT_SESSION_DEFAULT_VALUE, -}); +export const billingCheckoutSessionState = + createStateV2({ + key: 'billingCheckoutSessionState', + defaultValue: BILLING_CHECKOUT_SESSION_DEFAULT_VALUE, + }); diff --git a/packages/twenty-front/src/modules/auth/states/currentUserState.ts b/packages/twenty-front/src/modules/auth/states/currentUserState.ts index e4fa9d0750..6cae816adf 100644 --- a/packages/twenty-front/src/modules/auth/states/currentUserState.ts +++ b/packages/twenty-front/src/modules/auth/states/currentUserState.ts @@ -1,4 +1,4 @@ -import { createState } from '@/ui/utilities/state/utils/createState'; +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; import { type User } from '~/generated-metadata/graphql'; export type CurrentUser = Pick< @@ -15,7 +15,7 @@ export type CurrentUser = Pick< | 'hasPassword' >; -export const currentUserState = createState({ +export const currentUserState = createStateV2({ key: 'currentUserState', defaultValue: null, }); diff --git a/packages/twenty-front/src/modules/auth/states/currentUserWorkspaceState.ts b/packages/twenty-front/src/modules/auth/states/currentUserWorkspaceState.ts index a35aac2e08..386c34682c 100644 --- a/packages/twenty-front/src/modules/auth/states/currentUserWorkspaceState.ts +++ b/packages/twenty-front/src/modules/auth/states/currentUserWorkspaceState.ts @@ -1,5 +1,5 @@ import { type ObjectPermissions } from 'twenty-shared/types'; -import { createState } from '@/ui/utilities/state/utils/createState'; +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; import { type UserWorkspace } from '~/generated-metadata/graphql'; export type CurrentUserWorkspace = Pick< @@ -10,7 +10,7 @@ export type CurrentUserWorkspace = Pick< }; export const currentUserWorkspaceState = - createState({ + createStateV2({ key: 'currentUserWorkspaceState', defaultValue: null, }); diff --git a/packages/twenty-front/src/modules/auth/states/currentWorkspaceDeletedMembersState.ts b/packages/twenty-front/src/modules/auth/states/currentWorkspaceDeletedMembersState.ts index fbb11f438f..bea088e3ec 100644 --- a/packages/twenty-front/src/modules/auth/states/currentWorkspaceDeletedMembersState.ts +++ b/packages/twenty-front/src/modules/auth/states/currentWorkspaceDeletedMembersState.ts @@ -1,7 +1,7 @@ -import { createState } from '@/ui/utilities/state/utils/createState'; +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; import { type DeletedWorkspaceMember } from '~/generated-metadata/graphql'; -export const currentWorkspaceDeletedMembersState = createState< +export const currentWorkspaceDeletedMembersState = createStateV2< DeletedWorkspaceMember[] >({ key: 'currentWorkspaceDeletedMembersState', diff --git a/packages/twenty-front/src/modules/auth/states/currentWorkspaceMemberState.ts b/packages/twenty-front/src/modules/auth/states/currentWorkspaceMemberState.ts index 9c61bed5f2..7b918ddc07 100644 --- a/packages/twenty-front/src/modules/auth/states/currentWorkspaceMemberState.ts +++ b/packages/twenty-front/src/modules/auth/states/currentWorkspaceMemberState.ts @@ -1,5 +1,5 @@ import { type WorkspaceMember } from '@/workspace-member/types/WorkspaceMember'; -import { createState } from '@/ui/utilities/state/utils/createState'; +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; export type CurrentWorkspaceMember = Omit< WorkspaceMember, @@ -7,7 +7,7 @@ export type CurrentWorkspaceMember = Omit< >; export const currentWorkspaceMemberState = - createState({ + createStateV2({ key: 'currentWorkspaceMemberState', defaultValue: null, }); diff --git a/packages/twenty-front/src/modules/auth/states/currentWorkspaceMembersState.ts b/packages/twenty-front/src/modules/auth/states/currentWorkspaceMembersState.ts index 00a773d4f5..ffce77caaa 100644 --- a/packages/twenty-front/src/modules/auth/states/currentWorkspaceMembersState.ts +++ b/packages/twenty-front/src/modules/auth/states/currentWorkspaceMembersState.ts @@ -1,7 +1,7 @@ import { type PartialWorkspaceMember } from '@/settings/roles/types/RoleWithPartialMembers'; -import { createState } from '@/ui/utilities/state/utils/createState'; +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; -export const currentWorkspaceMembersState = createState< +export const currentWorkspaceMembersState = createStateV2< PartialWorkspaceMember[] >({ key: 'currentWorkspaceMembersState', diff --git a/packages/twenty-front/src/modules/auth/states/currentWorkspaceState.ts b/packages/twenty-front/src/modules/auth/states/currentWorkspaceState.ts index de66fd0b3a..379458a686 100644 --- a/packages/twenty-front/src/modules/auth/states/currentWorkspaceState.ts +++ b/packages/twenty-front/src/modules/auth/states/currentWorkspaceState.ts @@ -1,4 +1,4 @@ -import { createState } from '@/ui/utilities/state/utils/createState'; +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; import { type Application, type Role, @@ -43,7 +43,7 @@ export type CurrentWorkspace = Pick< workspaceCustomApplication: Pick | null; }; -export const currentWorkspaceState = createState({ +export const currentWorkspaceState = createStateV2({ key: 'currentWorkspaceState', defaultValue: null, }); diff --git a/packages/twenty-front/src/modules/auth/states/isCurrentUserLoadedState.ts b/packages/twenty-front/src/modules/auth/states/isCurrentUserLoadedState.ts index 30073f0bae..fd2a6c80e9 100644 --- a/packages/twenty-front/src/modules/auth/states/isCurrentUserLoadedState.ts +++ b/packages/twenty-front/src/modules/auth/states/isCurrentUserLoadedState.ts @@ -1,5 +1,5 @@ -import { createState } from '@/ui/utilities/state/utils/createState'; -export const isCurrentUserLoadedState = createState({ +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; +export const isCurrentUserLoadedState = createStateV2({ key: 'isCurrentUserLoadedState', defaultValue: false, }); diff --git a/packages/twenty-front/src/modules/auth/states/isImpersonatingState.ts b/packages/twenty-front/src/modules/auth/states/isImpersonatingState.ts index ae898bed59..9ec3219bf2 100644 --- a/packages/twenty-front/src/modules/auth/states/isImpersonatingState.ts +++ b/packages/twenty-front/src/modules/auth/states/isImpersonatingState.ts @@ -1,9 +1,10 @@ import { jwtDecode } from 'jwt-decode'; -import { selector } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; -import { tokenPairState } from './tokenPairState'; -export const isImpersonatingState = selector({ +import { tokenPairState } from '@/auth/states/tokenPairState'; +import { createSelectorV2 } from '@/ui/utilities/state/jotai/utils/createSelectorV2'; + +export const isImpersonatingState = createSelectorV2({ key: 'isImpersonatingState', get: ({ get }) => { const tokenPair = get(tokenPairState); diff --git a/packages/twenty-front/src/modules/auth/states/lastAuthenticatedMethodState.ts b/packages/twenty-front/src/modules/auth/states/lastAuthenticatedMethodState.ts index 4c12ca408d..f6ec95fe15 100644 --- a/packages/twenty-front/src/modules/auth/states/lastAuthenticatedMethodState.ts +++ b/packages/twenty-front/src/modules/auth/states/lastAuthenticatedMethodState.ts @@ -1,12 +1,9 @@ -import { atom } from 'recoil'; - import { type AuthenticatedMethod } from '@/auth/types/AuthenticatedMethod.enum'; -import { localStorageEffect } from '~/utils/recoil/localStorageEffect'; +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; -const LAST_AUTHENTICATED_METHOD_STORAGE_KEY = 'lastAuthenticatedMethodState'; - -export const lastAuthenticatedMethodState = atom({ - key: LAST_AUTHENTICATED_METHOD_STORAGE_KEY, - default: null, - effects: [localStorageEffect()], -}); +export const lastAuthenticatedMethodState = + createStateV2({ + key: 'lastAuthenticatedMethodState', + defaultValue: null, + useLocalStorage: true, + }); diff --git a/packages/twenty-front/src/modules/auth/states/loginTokenState.ts b/packages/twenty-front/src/modules/auth/states/loginTokenState.ts index deae18afe7..07063e81d0 100644 --- a/packages/twenty-front/src/modules/auth/states/loginTokenState.ts +++ b/packages/twenty-front/src/modules/auth/states/loginTokenState.ts @@ -1,7 +1,7 @@ -import { createState } from '@/ui/utilities/state/utils/createState'; +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; import { type AuthToken } from '~/generated-metadata/graphql'; -export const loginTokenState = createState({ +export const loginTokenState = createStateV2({ key: 'loginTokenState', defaultValue: null, }); diff --git a/packages/twenty-front/src/modules/auth/states/objectPermissionsFamilySelector.ts b/packages/twenty-front/src/modules/auth/states/objectPermissionsFamilySelector.ts index cb921b2365..72c794f0fe 100644 --- a/packages/twenty-front/src/modules/auth/states/objectPermissionsFamilySelector.ts +++ b/packages/twenty-front/src/modules/auth/states/objectPermissionsFamilySelector.ts @@ -1,8 +1,8 @@ import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; -import { selectorFamily } from 'recoil'; +import { createFamilySelectorV2 } from '@/ui/utilities/state/jotai/utils/createFamilySelectorV2'; -export const objectPermissionsFamilySelector = selectorFamily< +export const objectPermissionsFamilySelector = createFamilySelectorV2< { canRead: boolean; canUpdate: boolean; diff --git a/packages/twenty-front/src/modules/auth/states/signInUpStepState.ts b/packages/twenty-front/src/modules/auth/states/signInUpStepState.ts index 8d8535e0f1..15f5a87185 100644 --- a/packages/twenty-front/src/modules/auth/states/signInUpStepState.ts +++ b/packages/twenty-front/src/modules/auth/states/signInUpStepState.ts @@ -1,4 +1,4 @@ -import { createState } from '@/ui/utilities/state/utils/createState'; +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; export enum SignInUpStep { Init = 'init', Email = 'email', @@ -10,7 +10,7 @@ export enum SignInUpStep { TwoFactorAuthenticationProvision = 'TwoFactorAuthenticationProvision', } -export const signInUpStepState = createState({ +export const signInUpStepState = createStateV2({ key: 'signInUpStepState', defaultValue: SignInUpStep.Init, }); diff --git a/packages/twenty-front/src/modules/auth/states/tokenPairState.ts b/packages/twenty-front/src/modules/auth/states/tokenPairState.ts index 7d302a655f..d4f52c8c92 100644 --- a/packages/twenty-front/src/modules/auth/states/tokenPairState.ts +++ b/packages/twenty-front/src/modules/auth/states/tokenPairState.ts @@ -1,18 +1,12 @@ -import { createState } from '@/ui/utilities/state/utils/createState'; +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; import { type AuthTokenPair } from '~/generated-metadata/graphql'; -import { cookieStorageEffect } from '~/utils/recoil/cookieStorageEffect'; -export const tokenPairState = createState({ +export const tokenPairState = createStateV2({ key: 'tokenPairState', defaultValue: null, - effects: [ - cookieStorageEffect( - 'tokenPair', - {}, - { - validateInitFn: (payload: AuthTokenPair) => - Boolean(payload['accessOrWorkspaceAgnosticToken']), - }, - ), - ], + useCookieStorage: { + cookieKey: 'tokenPair', + validateInitFn: (payload: AuthTokenPair) => + Boolean(payload['accessOrWorkspaceAgnosticToken']), + }, }); diff --git a/packages/twenty-front/src/modules/auth/states/workspacePublicDataState.ts b/packages/twenty-front/src/modules/auth/states/workspacePublicDataState.ts index 9cbf8a42be..2a33949b94 100644 --- a/packages/twenty-front/src/modules/auth/states/workspacePublicDataState.ts +++ b/packages/twenty-front/src/modules/auth/states/workspacePublicDataState.ts @@ -1,8 +1,8 @@ import { type PublicWorkspaceDataOutput } from '~/generated-metadata/graphql'; -import { createState } from '@/ui/utilities/state/utils/createState'; +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; export const workspacePublicDataState = - createState({ + createStateV2({ key: 'workspacePublicDataState', defaultValue: null, }); diff --git a/packages/twenty-front/src/modules/billing/components/SettingsBillingContent.tsx b/packages/twenty-front/src/modules/billing/components/SettingsBillingContent.tsx index 0b366029c0..b0f76c3201 100644 --- a/packages/twenty-front/src/modules/billing/components/SettingsBillingContent.tsx +++ b/packages/twenty-front/src/modules/billing/components/SettingsBillingContent.tsx @@ -1,5 +1,4 @@ import { useLingui } from '@lingui/react/macro'; -import { useRecoilValue } from 'recoil'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { SettingsBillingCreditsSection } from '@/billing/components/SettingsBillingCreditsSection'; @@ -16,13 +15,14 @@ import { useBillingPortalSessionQuery, } from '~/generated-metadata/graphql'; import { useGetWorkflowNodeExecutionUsage } from '@/billing/hooks/useGetWorkflowNodeExecutionUsage'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; export const SettingsBillingContent = () => { const { t } = useLingui(); const { redirect } = useRedirect(); - const currentWorkspace = useRecoilValue(currentWorkspaceState); + const currentWorkspace = useRecoilValueV2(currentWorkspaceState); const subscriptions = currentWorkspace?.billingSubscriptions; diff --git a/packages/twenty-front/src/modules/billing/components/SettingsBillingSubscriptionInfo.tsx b/packages/twenty-front/src/modules/billing/components/SettingsBillingSubscriptionInfo.tsx index e4a7f35080..d98ae59cdd 100644 --- a/packages/twenty-front/src/modules/billing/components/SettingsBillingSubscriptionInfo.tsx +++ b/packages/twenty-front/src/modules/billing/components/SettingsBillingSubscriptionInfo.tsx @@ -29,8 +29,8 @@ import { useModal } from '@/ui/layout/modal/hooks/useModal'; import { useSubscriptionStatus } from '@/workspace/hooks/useSubscriptionStatus'; import styled from '@emotion/styled'; import { useLingui } from '@lingui/react/macro'; +import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; import { useMemo, useState } from 'react'; -import { useSetRecoilState } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { H2Title, @@ -144,7 +144,7 @@ export const SettingsBillingSubscriptionInfo = ({ const [cancelSwitchMeteredPrice] = useCancelSwitchMeteredPriceMutation(); - const setCurrentWorkspace = useSetRecoilState(currentWorkspaceState); + const setCurrentWorkspace = useSetRecoilStateV2(currentWorkspaceState); const isTrialPeriod = subscriptionStatus === SubscriptionStatus.Trialing; diff --git a/packages/twenty-front/src/modules/billing/components/internal/MeteredPriceSelector.tsx b/packages/twenty-front/src/modules/billing/components/internal/MeteredPriceSelector.tsx index bcbb10b174..3366b435c0 100644 --- a/packages/twenty-front/src/modules/billing/components/internal/MeteredPriceSelector.tsx +++ b/packages/twenty-front/src/modules/billing/components/internal/MeteredPriceSelector.tsx @@ -7,6 +7,7 @@ import { type MeteredBillingPrice, } from '@/billing/types/billing-price-tiers.type'; import { useNumberFormat } from '@/localization/hooks/useNumberFormat'; +import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { Select } from '@/ui/input/components/Select'; import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal'; @@ -14,7 +15,6 @@ import { useModal } from '@/ui/layout/modal/hooks/useModal'; import styled from '@emotion/styled'; import { t } from '@lingui/core/macro'; import { useState } from 'react'; -import { useRecoilState } from 'recoil'; import { findOrThrow, isDefined } from 'twenty-shared/utils'; import { H2Title } from 'twenty-ui/display'; import { Button } from 'twenty-ui/input'; @@ -48,7 +48,7 @@ export const MeteredPriceSelector = ({ const { currentMeteredBillingPrice } = useCurrentMetered(); const { formatNumber } = useNumberFormat(); - const [currentWorkspace, setCurrentWorkspace] = useRecoilState( + const [currentWorkspace, setCurrentWorkspace] = useRecoilStateV2( currentWorkspaceState, ); diff --git a/packages/twenty-front/src/modules/billing/hooks/useBillingWording.ts b/packages/twenty-front/src/modules/billing/hooks/useBillingWording.ts index 741559ee16..d90a37c8ae 100644 --- a/packages/twenty-front/src/modules/billing/hooks/useBillingWording.ts +++ b/packages/twenty-front/src/modules/billing/hooks/useBillingWording.ts @@ -12,12 +12,12 @@ import { beautifyExactDate } from '~/utils/date-utils'; import { useCurrentPlan } from '@/billing/hooks/useCurrentPlan'; import { useCurrentMetered } from '@/billing/hooks/useCurrentMetered'; import { useCurrentBillingFlags } from '@/billing/hooks/useCurrentBillingFlags'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; export const useBillingWording = () => { const { t } = useLingui(); - const currentWorkspace = useRecoilValue(currentWorkspaceState); + const currentWorkspace = useRecoilValueV2(currentWorkspaceState); assertIsDefinedOrThrow(currentWorkspace); diff --git a/packages/twenty-front/src/modules/billing/hooks/useCurrentBillingFlags.ts b/packages/twenty-front/src/modules/billing/hooks/useCurrentBillingFlags.ts index 0417fdb34c..17561b562c 100644 --- a/packages/twenty-front/src/modules/billing/hooks/useCurrentBillingFlags.ts +++ b/packages/twenty-front/src/modules/billing/hooks/useCurrentBillingFlags.ts @@ -3,11 +3,11 @@ import { BillingPlanKey, } from '~/generated-metadata/graphql'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { assertIsDefinedOrThrow } from 'twenty-shared/utils'; export const useCurrentBillingFlags = () => { - const currentWorkspace = useRecoilValue(currentWorkspaceState); + const currentWorkspace = useRecoilValueV2(currentWorkspaceState); assertIsDefinedOrThrow(currentWorkspace); diff --git a/packages/twenty-front/src/modules/billing/hooks/useCurrentMetered.ts b/packages/twenty-front/src/modules/billing/hooks/useCurrentMetered.ts index b2f868bdd2..a19eced0fb 100644 --- a/packages/twenty-front/src/modules/billing/hooks/useCurrentMetered.ts +++ b/packages/twenty-front/src/modules/billing/hooks/useCurrentMetered.ts @@ -1,7 +1,7 @@ import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { useCurrentPlan } from '@/billing/hooks/useCurrentPlan'; import type { MeteredBillingPrice } from '@/billing/types/billing-price-tiers.type'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { assertIsDefinedOrThrow, findOrThrow } from 'twenty-shared/utils'; import { BillingProductKey, @@ -11,7 +11,7 @@ import { export const useCurrentMetered = () => { const { currentPlan } = useCurrentPlan(); - const currentWorkspace = useRecoilValue(currentWorkspaceState); + const currentWorkspace = useRecoilValueV2(currentWorkspaceState); assertIsDefinedOrThrow(currentWorkspace); diff --git a/packages/twenty-front/src/modules/billing/hooks/useCurrentPlan.ts b/packages/twenty-front/src/modules/billing/hooks/useCurrentPlan.ts index 9bb08280f9..a8f65220cb 100644 --- a/packages/twenty-front/src/modules/billing/hooks/useCurrentPlan.ts +++ b/packages/twenty-front/src/modules/billing/hooks/useCurrentPlan.ts @@ -2,12 +2,12 @@ import { BillingPlanKey } from '~/generated-metadata/graphql'; import { assertIsDefinedOrThrow, findOrThrow } from 'twenty-shared/utils'; import { usePlans } from './usePlans'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; export const useCurrentPlan = () => { const { listPlans } = usePlans(); - const currentWorkspace = useRecoilValue(currentWorkspaceState); + const currentWorkspace = useRecoilValueV2(currentWorkspaceState); assertIsDefinedOrThrow(currentWorkspace); diff --git a/packages/twenty-front/src/modules/billing/hooks/useEndSubscriptionTrialPeriod.ts b/packages/twenty-front/src/modules/billing/hooks/useEndSubscriptionTrialPeriod.ts index de9493caf7..ec2c0b7dea 100644 --- a/packages/twenty-front/src/modules/billing/hooks/useEndSubscriptionTrialPeriod.ts +++ b/packages/twenty-front/src/modules/billing/hooks/useEndSubscriptionTrialPeriod.ts @@ -1,16 +1,16 @@ import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { useRedirect } from '@/domain-manager/hooks/useRedirect'; +import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { t } from '@lingui/core/macro'; import { useState } from 'react'; -import { useRecoilState } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { useEndSubscriptionTrialPeriodMutation } from '~/generated-metadata/graphql'; export const useEndSubscriptionTrialPeriod = () => { const { enqueueSuccessSnackBar, enqueueErrorSnackBar } = useSnackBar(); const [endSubscriptionTrialPeriod] = useEndSubscriptionTrialPeriodMutation(); - const [currentWorkspace, setCurrentWorkspace] = useRecoilState( + const [currentWorkspace, setCurrentWorkspace] = useRecoilStateV2( currentWorkspaceState, ); const [isLoading, setIsLoading] = useState(false); diff --git a/packages/twenty-front/src/modules/billing/hooks/useHasNextBillingPhase.ts b/packages/twenty-front/src/modules/billing/hooks/useHasNextBillingPhase.ts index b123b19665..9c6f575821 100644 --- a/packages/twenty-front/src/modules/billing/hooks/useHasNextBillingPhase.ts +++ b/packages/twenty-front/src/modules/billing/hooks/useHasNextBillingPhase.ts @@ -1,8 +1,8 @@ -import { useRecoilValue } from 'recoil'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; export const useHasNextBillingPhase = () => { - const currentWorkspace = useRecoilValue(currentWorkspaceState); + const currentWorkspace = useRecoilValueV2(currentWorkspaceState); const hasNextBillingPhase = currentWorkspace?.currentBillingSubscription?.phases.length === 2; diff --git a/packages/twenty-front/src/modules/billing/hooks/useNextBillingPhase.ts b/packages/twenty-front/src/modules/billing/hooks/useNextBillingPhase.ts index f631caf76e..5035413b34 100644 --- a/packages/twenty-front/src/modules/billing/hooks/useNextBillingPhase.ts +++ b/packages/twenty-front/src/modules/billing/hooks/useNextBillingPhase.ts @@ -1,8 +1,8 @@ -import { useRecoilValue } from 'recoil'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; export const useNextBillingPhase = () => { - const currentWorkspace = useRecoilValue(currentWorkspaceState); + const currentWorkspace = useRecoilValueV2(currentWorkspaceState); const nextBillingPhase = currentWorkspace?.currentBillingSubscription?.phases[1]; diff --git a/packages/twenty-front/src/modules/blocknote-editor/blocks/MentionInlineContent.tsx b/packages/twenty-front/src/modules/blocknote-editor/blocks/MentionInlineContent.tsx index d4474dbc26..d9da511c0c 100644 --- a/packages/twenty-front/src/modules/blocknote-editor/blocks/MentionInlineContent.tsx +++ b/packages/twenty-front/src/modules/blocknote-editor/blocks/MentionInlineContent.tsx @@ -6,9 +6,9 @@ import { createReactInlineContentSpec } from '@blocknote/react'; import styled from '@emotion/styled'; import { t } from '@lingui/core/macro'; import { isNonEmptyString } from '@sniptt/guards'; -import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { Chip, ChipVariant } from 'twenty-ui/components'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; const StyledRecordChip = styled(RecordChip)` height: auto; @@ -30,7 +30,7 @@ const LegacyMentionRenderer = ({ recordId: string; objectMetadataId: string; }) => { - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); const objectMetadataItem = objectMetadataItems.find( (item) => item.id === objectMetadataId, diff --git a/packages/twenty-front/src/modules/captcha/components/CaptchaProviderScriptLoaderEffect.tsx b/packages/twenty-front/src/modules/captcha/components/CaptchaProviderScriptLoaderEffect.tsx index e58972d07b..0a29998d5a 100644 --- a/packages/twenty-front/src/modules/captcha/components/CaptchaProviderScriptLoaderEffect.tsx +++ b/packages/twenty-front/src/modules/captcha/components/CaptchaProviderScriptLoaderEffect.tsx @@ -1,19 +1,19 @@ -import { useEffect } from 'react'; -import { useLocation } from 'react-router-dom'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; - import { useRequestFreshCaptchaToken } from '@/captcha/hooks/useRequestFreshCaptchaToken'; import { isCaptchaScriptLoadedState } from '@/captcha/states/isCaptchaScriptLoadedState'; import { getCaptchaUrlByProvider } from '@/captcha/utils/getCaptchaUrlByProvider'; import { isCaptchaRequiredForPath } from '@/captcha/utils/isCaptchaRequiredForPath'; import { useCaptcha } from '@/client-config/hooks/useCaptcha'; import { captchaState } from '@/client-config/states/captchaState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; +import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; +import { useEffect } from 'react'; +import { useLocation } from 'react-router-dom'; import { assertIsDefinedOrThrow } from 'twenty-shared/utils'; import { CaptchaDriverType } from '~/generated-metadata/graphql'; export const CaptchaProviderScriptLoaderEffect = () => { - const captcha = useRecoilValue(captchaState); - const setIsCaptchaScriptLoaded = useSetRecoilState( + const captcha = useRecoilValueV2(captchaState); + const setIsCaptchaScriptLoaded = useSetRecoilStateV2( isCaptchaScriptLoadedState, ); const { isCaptchaScriptLoaded, isCaptchaConfigured } = useCaptcha(); diff --git a/packages/twenty-front/src/modules/captcha/hooks/__tests__/useReadCaptchaToken.test.tsx b/packages/twenty-front/src/modules/captcha/hooks/__tests__/useReadCaptchaToken.test.tsx index 3043c81ac9..f486904aa2 100644 --- a/packages/twenty-front/src/modules/captcha/hooks/__tests__/useReadCaptchaToken.test.tsx +++ b/packages/twenty-front/src/modules/captcha/hooks/__tests__/useReadCaptchaToken.test.tsx @@ -1,13 +1,26 @@ import { act, renderHook } from '@testing-library/react'; +import { type ReactNode } from 'react'; +import { Provider as JotaiProvider } from 'jotai'; import { RecoilRoot } from 'recoil'; import { captchaTokenState } from '@/captcha/states/captchaTokenState'; import { useReadCaptchaToken } from '@/captcha/hooks/useReadCaptchaToken'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; + +const Wrapper = ({ children }: { children: ReactNode }) => ( + + {children} + +); describe('useReadCaptchaToken', () => { + beforeEach(() => { + jotaiStore.set(captchaTokenState.atom, undefined); + }); + it('should return undefined when no token exists', async () => { const { result } = renderHook(() => useReadCaptchaToken(), { - wrapper: RecoilRoot, + wrapper: Wrapper, }); await act(async () => { @@ -17,23 +30,11 @@ describe('useReadCaptchaToken', () => { }); it('should return the token when it exists', async () => { - const { result } = renderHook( - () => { - const hook = useReadCaptchaToken(); - return hook; - }, - { - wrapper: ({ children }) => ( - { - set(captchaTokenState, 'test-token'); - }} - > - {children} - - ), - }, - ); + jotaiStore.set(captchaTokenState.atom, 'test-token'); + + const { result } = renderHook(() => useReadCaptchaToken(), { + wrapper: Wrapper, + }); await act(async () => { const token = await result.current.readCaptchaToken(); diff --git a/packages/twenty-front/src/modules/captcha/hooks/__tests__/useRequestFreshCaptchaToken.test.tsx b/packages/twenty-front/src/modules/captcha/hooks/__tests__/useRequestFreshCaptchaToken.test.tsx index e44de06154..b3c07ecb9d 100644 --- a/packages/twenty-front/src/modules/captcha/hooks/__tests__/useRequestFreshCaptchaToken.test.tsx +++ b/packages/twenty-front/src/modules/captcha/hooks/__tests__/useRequestFreshCaptchaToken.test.tsx @@ -1,14 +1,23 @@ import { act, renderHook, waitFor } from '@testing-library/react'; +import { type ReactNode } from 'react'; import { RecoilRoot } from 'recoil'; +import { Provider as JotaiProvider } from 'jotai'; import { useRequestFreshCaptchaToken } from '@/captcha/hooks/useRequestFreshCaptchaToken'; import { isRequestingCaptchaTokenState } from '@/captcha/states/isRequestingCaptchaTokenState'; import { isCaptchaRequiredForPath } from '@/captcha/utils/isCaptchaRequiredForPath'; import { captchaState } from '@/client-config/states/captchaState'; -import { CaptchaDriverType } from '~/generated-metadata/graphql'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; +import { type Captcha, CaptchaDriverType } from '~/generated-metadata/graphql'; jest.mock('@/captcha/utils/isCaptchaRequiredForPath'); +const createWrapper = ({ children }: { children: ReactNode }) => ( + + {children} + +); + describe('useRequestFreshCaptchaToken', () => { const mockGrecaptchaExecute = jest.fn(); const mockTurnstileRender = jest.fn(); @@ -47,7 +56,7 @@ describe('useRequestFreshCaptchaToken', () => { (isCaptchaRequiredForPath as jest.Mock).mockReturnValue(false); const { result } = renderHook(() => useRequestFreshCaptchaToken(), { - wrapper: RecoilRoot, + wrapper: createWrapper, }); await act(async () => { @@ -61,7 +70,7 @@ describe('useRequestFreshCaptchaToken', () => { it('should not request a token if captcha provider is undefined', async () => { const { result } = renderHook(() => useRequestFreshCaptchaToken(), { - wrapper: RecoilRoot, + wrapper: createWrapper, }); await act(async () => { @@ -74,20 +83,14 @@ describe('useRequestFreshCaptchaToken', () => { }); it('should request a token from Google reCAPTCHA when provider is GOOGLE_RECAPTCHA', async () => { + jotaiStore.set(isRequestingCaptchaTokenState.atom, false); + jotaiStore.set(captchaState.atom, { + provider: CaptchaDriverType.GOOGLE_RECAPTCHA, + siteKey: 'google-site-key', + } as Captcha); + const { result } = renderHook(() => useRequestFreshCaptchaToken(), { - wrapper: ({ children }) => ( - { - set(captchaState, { - provider: CaptchaDriverType.GOOGLE_RECAPTCHA, - siteKey: 'google-site-key', - }); - set(isRequestingCaptchaTokenState, false); - }} - > - {children} - - ), + wrapper: createWrapper, }); await act(async () => { @@ -104,20 +107,14 @@ describe('useRequestFreshCaptchaToken', () => { }); it('should request a token from Turnstile when provider is TURNSTILE', async () => { + jotaiStore.set(isRequestingCaptchaTokenState.atom, false); + jotaiStore.set(captchaState.atom, { + provider: CaptchaDriverType.TURNSTILE, + siteKey: 'turnstile-site-key', + } as Captcha); + const { result } = renderHook(() => useRequestFreshCaptchaToken(), { - wrapper: ({ children }) => ( - { - set(captchaState, { - provider: CaptchaDriverType.TURNSTILE, - siteKey: 'turnstile-site-key', - }); - set(isRequestingCaptchaTokenState, false); - }} - > - {children} - - ), + wrapper: createWrapper, }); await act(async () => { diff --git a/packages/twenty-front/src/modules/captcha/hooks/useReadCaptchaToken.ts b/packages/twenty-front/src/modules/captcha/hooks/useReadCaptchaToken.ts index 347a84297a..64488e359f 100644 --- a/packages/twenty-front/src/modules/captcha/hooks/useReadCaptchaToken.ts +++ b/packages/twenty-front/src/modules/captcha/hooks/useReadCaptchaToken.ts @@ -1,22 +1,16 @@ -import { useRecoilCallback } from 'recoil'; - import { captchaTokenState } from '@/captcha/states/captchaTokenState'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; +import { useCallback } from 'react'; import { isDefined } from 'twenty-shared/utils'; export const useReadCaptchaToken = () => { - const readCaptchaToken = useRecoilCallback( - ({ snapshot }) => - () => { - const existingCaptchaToken = snapshot - .getLoadable(captchaTokenState) - .getValue(); + const readCaptchaToken = useCallback(() => { + const existingCaptchaToken = jotaiStore.get(captchaTokenState.atom); - if (isDefined(existingCaptchaToken)) { - return existingCaptchaToken; - } - }, - [], - ); + if (isDefined(existingCaptchaToken)) { + return existingCaptchaToken; + } + }, []); return { readCaptchaToken }; }; diff --git a/packages/twenty-front/src/modules/captcha/hooks/useRequestFreshCaptchaToken.ts b/packages/twenty-front/src/modules/captcha/hooks/useRequestFreshCaptchaToken.ts index 959b60c832..51eed388b9 100644 --- a/packages/twenty-front/src/modules/captcha/hooks/useRequestFreshCaptchaToken.ts +++ b/packages/twenty-front/src/modules/captcha/hooks/useRequestFreshCaptchaToken.ts @@ -1,61 +1,58 @@ -import { useRecoilCallback, useSetRecoilState } from 'recoil'; - import { captchaTokenState } from '@/captcha/states/captchaTokenState'; import { isRequestingCaptchaTokenState } from '@/captcha/states/isRequestingCaptchaTokenState'; import { isCaptchaRequiredForPath } from '@/captcha/utils/isCaptchaRequiredForPath'; import { captchaState } from '@/client-config/states/captchaState'; +import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; +import { useCallback } from 'react'; import { assertIsDefinedOrThrow, isDefined } from 'twenty-shared/utils'; import { CaptchaDriverType } from '~/generated-metadata/graphql'; export const useRequestFreshCaptchaToken = () => { - const setCaptchaToken = useSetRecoilState(captchaTokenState); - const setIsRequestingCaptchaToken = useSetRecoilState( + const setCaptchaToken = useSetRecoilStateV2(captchaTokenState); + const setIsRequestingCaptchaToken = useSetRecoilStateV2( isRequestingCaptchaTokenState, ); - const requestFreshCaptchaToken = useRecoilCallback( - ({ snapshot }) => - async () => { - if (!isCaptchaRequiredForPath(window.location.pathname)) { - return; - } + const requestFreshCaptchaToken = useCallback(async () => { + if (!isCaptchaRequiredForPath(window.location.pathname)) { + return; + } - const captcha = snapshot.getLoadable(captchaState).getValue(); + const captcha = jotaiStore.get(captchaState.atom); - if (!isDefined(captcha)) { - return; - } + if (!isDefined(captcha)) { + return; + } - assertIsDefinedOrThrow(captcha); + assertIsDefinedOrThrow(captcha); - setIsRequestingCaptchaToken(true); + setIsRequestingCaptchaToken(true); - let captchaWidget: any; - switch (captcha.provider) { - case CaptchaDriverType.GOOGLE_RECAPTCHA: - window.grecaptcha - .execute(captcha.siteKey, { - action: 'submit', - }) - .then((token: string) => { - setCaptchaToken(token); - setIsRequestingCaptchaToken(false); - }); - break; - case CaptchaDriverType.TURNSTILE: - captchaWidget = window.turnstile.render('#captcha-widget', { - sitekey: captcha.siteKey, - }); - window.turnstile.execute(captchaWidget, { - callback: (token: string) => { - setCaptchaToken(token); - setIsRequestingCaptchaToken(false); - }, - }); - } - }, - [setCaptchaToken, setIsRequestingCaptchaToken], - ); + let captchaWidget: any; + switch (captcha.provider) { + case CaptchaDriverType.GOOGLE_RECAPTCHA: + window.grecaptcha + .execute(captcha.siteKey, { + action: 'submit', + }) + .then((token: string) => { + setCaptchaToken(token); + setIsRequestingCaptchaToken(false); + }); + break; + case CaptchaDriverType.TURNSTILE: + captchaWidget = window.turnstile.render('#captcha-widget', { + sitekey: captcha.siteKey, + }); + window.turnstile.execute(captchaWidget, { + callback: (token: string) => { + setCaptchaToken(token); + setIsRequestingCaptchaToken(false); + }, + }); + } + }, [setCaptchaToken, setIsRequestingCaptchaToken]); return { requestFreshCaptchaToken }; }; diff --git a/packages/twenty-front/src/modules/captcha/states/captchaTokenState.ts b/packages/twenty-front/src/modules/captcha/states/captchaTokenState.ts index f8857d1c35..132a698a16 100644 --- a/packages/twenty-front/src/modules/captcha/states/captchaTokenState.ts +++ b/packages/twenty-front/src/modules/captcha/states/captchaTokenState.ts @@ -1,5 +1,5 @@ -import { createState } from '@/ui/utilities/state/utils/createState'; -export const captchaTokenState = createState({ +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; +export const captchaTokenState = createStateV2({ key: 'captchaTokenState', defaultValue: undefined, }); diff --git a/packages/twenty-front/src/modules/captcha/states/isCaptchaScriptLoadedState.ts b/packages/twenty-front/src/modules/captcha/states/isCaptchaScriptLoadedState.ts index d33524e934..399fb0d8f5 100644 --- a/packages/twenty-front/src/modules/captcha/states/isCaptchaScriptLoadedState.ts +++ b/packages/twenty-front/src/modules/captcha/states/isCaptchaScriptLoadedState.ts @@ -1,5 +1,5 @@ -import { createState } from '@/ui/utilities/state/utils/createState'; -export const isCaptchaScriptLoadedState = createState({ +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; +export const isCaptchaScriptLoadedState = createStateV2({ key: 'isCaptchaScriptLoadedState', defaultValue: false, }); diff --git a/packages/twenty-front/src/modules/captcha/states/isRequestingCaptchaTokenState.ts b/packages/twenty-front/src/modules/captcha/states/isRequestingCaptchaTokenState.ts index c22d921ed3..8e3647574c 100644 --- a/packages/twenty-front/src/modules/captcha/states/isRequestingCaptchaTokenState.ts +++ b/packages/twenty-front/src/modules/captcha/states/isRequestingCaptchaTokenState.ts @@ -1,5 +1,5 @@ -import { createState } from '@/ui/utilities/state/utils/createState'; -export const isRequestingCaptchaTokenState = createState({ +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; +export const isRequestingCaptchaTokenState = createStateV2({ key: 'isRequestingCaptchaTokenState', defaultValue: false, }); diff --git a/packages/twenty-front/src/modules/chrome-extension-sidecar/components/ChromeExtensionSidecarEffect.tsx b/packages/twenty-front/src/modules/chrome-extension-sidecar/components/ChromeExtensionSidecarEffect.tsx index b5b1a67ba2..16dd32196c 100644 --- a/packages/twenty-front/src/modules/chrome-extension-sidecar/components/ChromeExtensionSidecarEffect.tsx +++ b/packages/twenty-front/src/modules/chrome-extension-sidecar/components/ChromeExtensionSidecarEffect.tsx @@ -1,19 +1,18 @@ -import { useEffect } from 'react'; -import { useNavigate } from 'react-router-dom'; -import { useSetRecoilState } from 'recoil'; - import { tokenPairState } from '@/auth/states/tokenPairState'; import { isLoadingTokensFromExtensionState } from '@/chrome-extension-sidecar/states/isLoadingTokensFromExtensionState'; import { chromeExtensionIdState } from '@/client-config/states/chromeExtensionIdState'; -import { isInFrame } from '~/utils/isInIframe'; -import { isDefined } from 'twenty-shared/utils'; import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; +import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; +import { useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { isDefined } from 'twenty-shared/utils'; +import { isInFrame } from '~/utils/isInIframe'; export const ChromeExtensionSidecarEffect = () => { const navigate = useNavigate(); - const setTokenPair = useSetRecoilState(tokenPairState); + const setTokenPair = useSetRecoilStateV2(tokenPairState); const chromeExtensionId = useRecoilValueV2(chromeExtensionIdState); - const setIsLoadingTokensFromExtension = useSetRecoilState( + const setIsLoadingTokensFromExtension = useSetRecoilStateV2( isLoadingTokensFromExtensionState, ); diff --git a/packages/twenty-front/src/modules/chrome-extension-sidecar/states/isLoadingTokensFromExtensionState.ts b/packages/twenty-front/src/modules/chrome-extension-sidecar/states/isLoadingTokensFromExtensionState.ts index 5eff733b9a..957dffb63f 100644 --- a/packages/twenty-front/src/modules/chrome-extension-sidecar/states/isLoadingTokensFromExtensionState.ts +++ b/packages/twenty-front/src/modules/chrome-extension-sidecar/states/isLoadingTokensFromExtensionState.ts @@ -1,5 +1,5 @@ -import { createState } from '@/ui/utilities/state/utils/createState'; -export const isLoadingTokensFromExtensionState = createState({ +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; +export const isLoadingTokensFromExtensionState = createStateV2({ key: 'isLoadingTokensFromExtensionState', defaultValue: null, }); diff --git a/packages/twenty-front/src/modules/client-config/components/ClientConfigProvider.tsx b/packages/twenty-front/src/modules/client-config/components/ClientConfigProvider.tsx index 3b5d0ea57e..ddb99146da 100644 --- a/packages/twenty-front/src/modules/client-config/components/ClientConfigProvider.tsx +++ b/packages/twenty-front/src/modules/client-config/components/ClientConfigProvider.tsx @@ -1,13 +1,12 @@ -import { useRecoilValue } from 'recoil'; - import { clientConfigApiStatusState } from '@/client-config/states/clientConfigApiStatusState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { AppFullScreenErrorFallback } from '@/error-handler/components/AppFullScreenErrorFallback'; import { useLingui } from '@lingui/react/macro'; export const ClientConfigProvider: React.FC = ({ children, }) => { - const { isErrored, error } = useRecoilValue(clientConfigApiStatusState); + const { isErrored, error } = useRecoilValueV2(clientConfigApiStatusState); const { t } = useLingui(); return isErrored && error instanceof Error ? ( diff --git a/packages/twenty-front/src/modules/client-config/components/ClientConfigProviderEffect.tsx b/packages/twenty-front/src/modules/client-config/components/ClientConfigProviderEffect.tsx index a596d27c26..e456113784 100644 --- a/packages/twenty-front/src/modules/client-config/components/ClientConfigProviderEffect.tsx +++ b/packages/twenty-front/src/modules/client-config/components/ClientConfigProviderEffect.tsx @@ -1,11 +1,11 @@ import { useClientConfig } from '@/client-config/hooks/useClientConfig'; import { clientConfigApiStatusState } from '@/client-config/states/clientConfigApiStatusState'; +import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2'; import { useEffect } from 'react'; -import { useRecoilState } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; export const ClientConfigProviderEffect = () => { - const [clientConfigApiStatus, setClientConfigApiStatus] = useRecoilState( + const [clientConfigApiStatus, setClientConfigApiStatus] = useRecoilStateV2( clientConfigApiStatusState, ); diff --git a/packages/twenty-front/src/modules/client-config/hooks/useCaptcha.ts b/packages/twenty-front/src/modules/client-config/hooks/useCaptcha.ts index bff40df477..09b09a9d1b 100644 --- a/packages/twenty-front/src/modules/client-config/hooks/useCaptcha.ts +++ b/packages/twenty-front/src/modules/client-config/hooks/useCaptcha.ts @@ -2,15 +2,15 @@ import { captchaTokenState } from '@/captcha/states/captchaTokenState'; import { isCaptchaScriptLoadedState } from '@/captcha/states/isCaptchaScriptLoadedState'; import { captchaState } from '@/client-config/states/captchaState'; import { clientConfigApiStatusState } from '@/client-config/states/clientConfigApiStatusState'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { isDefined } from 'twenty-shared/utils'; import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull'; export const useCaptcha = () => { - const captcha = useRecoilValue(captchaState); - const captchaToken = useRecoilValue(captchaTokenState); - const clientConfigApiStatus = useRecoilValue(clientConfigApiStatusState); - const isCaptchaScriptLoaded = useRecoilValue(isCaptchaScriptLoadedState); + const captcha = useRecoilValueV2(captchaState); + const captchaToken = useRecoilValueV2(captchaTokenState); + const clientConfigApiStatus = useRecoilValueV2(clientConfigApiStatusState); + const isCaptchaScriptLoaded = useRecoilValueV2(isCaptchaScriptLoadedState); const isClientConfigLoaded = clientConfigApiStatus.isLoadedOnce; const isSiteKeyDefined = isDefined(captcha?.siteKey); 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 2014335947..df79dc97f6 100644 --- a/packages/twenty-front/src/modules/client-config/hooks/useClientConfig.ts +++ b/packages/twenty-front/src/modules/client-config/hooks/useClientConfig.ts @@ -26,14 +26,14 @@ 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 { useStore } from 'jotai'; 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'; import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; +import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; type UseClientConfigResult = { data: { clientConfig: ClientConfig } | undefined; @@ -45,35 +45,35 @@ type UseClientConfigResult = { export const useClientConfig = (): UseClientConfigResult => { const setIsAnalyticsEnabled = useSetRecoilStateV2(isAnalyticsEnabledState); - const setDomainConfiguration = useSetRecoilState(domainConfigurationState); + const setDomainConfiguration = useSetRecoilStateV2(domainConfigurationState); const setAuthProviders = useSetRecoilStateV2(authProvidersState); const setAiModels = useSetRecoilStateV2(aiModelsState); - const setIsDeveloperDefaultSignInPrefilled = useSetRecoilState( + const setIsDeveloperDefaultSignInPrefilled = useSetRecoilStateV2( isDeveloperDefaultSignInPrefilledState, ); - const setIsMultiWorkspaceEnabled = useSetRecoilState( + const setIsMultiWorkspaceEnabled = useSetRecoilStateV2( isMultiWorkspaceEnabledState, ); - const setIsEmailVerificationRequired = useSetRecoilState( + const setIsEmailVerificationRequired = useSetRecoilStateV2( isEmailVerificationRequiredState, ); - const setBilling = useSetRecoilState(billingState); - const setSupportChat = useSetRecoilState(supportChatState); + const setBilling = useSetRecoilStateV2(billingState); + const setSupportChat = useSetRecoilStateV2(supportChatState); - const setSentryConfig = useSetRecoilState(sentryConfigState); - const [clientConfigApiStatus, setClientConfigApiStatus] = useRecoilState( + const setSentryConfig = useSetRecoilStateV2(sentryConfigState); + const [clientConfigApiStatus, setClientConfigApiStatus] = useRecoilStateV2( clientConfigApiStatusState, ); - const setCaptcha = useSetRecoilState(captchaState); + const setCaptcha = useSetRecoilStateV2(captchaState); const setChromeExtensionId = useSetRecoilStateV2(chromeExtensionIdState); - const setApiConfig = useSetRecoilState(apiConfigState); + const setApiConfig = useSetRecoilStateV2(apiConfigState); - const setCanManageFeatureFlags = useSetRecoilState( + const setCanManageFeatureFlags = useSetRecoilStateV2( canManageFeatureFlagsState, ); @@ -81,57 +81,55 @@ export const useClientConfig = (): UseClientConfigResult => { labPublicFeatureFlagsStateV2, ); - const setMicrosoftMessagingEnabled = useSetRecoilState( + const setMicrosoftMessagingEnabled = useSetRecoilStateV2( isMicrosoftMessagingEnabledState, ); - const setMicrosoftCalendarEnabled = useSetRecoilState( + const setMicrosoftCalendarEnabled = useSetRecoilStateV2( isMicrosoftCalendarEnabledState, ); - const setGoogleMessagingEnabled = useSetRecoilState( + const setGoogleMessagingEnabled = useSetRecoilStateV2( isGoogleMessagingEnabledState, ); - const setGoogleCalendarEnabled = useSetRecoilState( + const setGoogleCalendarEnabled = useSetRecoilStateV2( isGoogleCalendarEnabledState, ); - const setIsAttachmentPreviewEnabled = useSetRecoilState( + const setIsAttachmentPreviewEnabled = useSetRecoilStateV2( isAttachmentPreviewEnabledState, ); - const setIsConfigVariablesInDbEnabled = useSetRecoilState( + const setIsConfigVariablesInDbEnabled = useSetRecoilStateV2( isConfigVariablesInDbEnabledState, ); - const setCalendarBookingPageId = useSetRecoilState( + const setCalendarBookingPageId = useSetRecoilStateV2( calendarBookingPageIdState, ); - const setIsImapSmtpCaldavEnabled = useSetRecoilState( + const setIsImapSmtpCaldavEnabled = useSetRecoilStateV2( isImapSmtpCaldavEnabledState, ); - const setIsEmailingDomainsEnabled = useSetRecoilState( + const setIsEmailingDomainsEnabled = useSetRecoilStateV2( isEmailingDomainsEnabledState, ); - const setAllowRequestsToTwentyIcons = useSetRecoilState( + const setAllowRequestsToTwentyIcons = useSetRecoilStateV2( allowRequestsToTwentyIconsState, ); - const setIsCloudflareIntegrationEnabled = useSetRecoilState( + const setIsCloudflareIntegrationEnabled = useSetRecoilStateV2( isCloudflareIntegrationEnabledState, ); - const setIsClickHouseConfigured = useSetRecoilState( + const setIsClickHouseConfigured = useSetRecoilStateV2( isClickHouseConfiguredState, ); const setAppVersion = useSetRecoilStateV2(appVersionState); - const store = useStore(); - const fetchClientConfig = useCallback(async () => { setClientConfigApiStatus((prev) => ({ ...prev, @@ -193,7 +191,7 @@ export const useClientConfig = (): UseClientConfigResult => { setGoogleMessagingEnabled(clientConfig?.isGoogleMessagingEnabled); setGoogleCalendarEnabled(clientConfig?.isGoogleCalendarEnabled); setIsAttachmentPreviewEnabled(clientConfig?.isAttachmentPreviewEnabled); - store.set( + jotaiStore.set( isAttachmentPreviewEnabledStateV2.atom, clientConfig?.isAttachmentPreviewEnabled, ); @@ -254,7 +252,6 @@ export const useClientConfig = (): UseClientConfigResult => { setSentryConfig, setSupportChat, setAllowRequestsToTwentyIcons, - store, ]); return { diff --git a/packages/twenty-front/src/modules/client-config/states/allowRequestsToTwentyIcons.ts b/packages/twenty-front/src/modules/client-config/states/allowRequestsToTwentyIcons.ts index efd8c33301..20f41530b3 100644 --- a/packages/twenty-front/src/modules/client-config/states/allowRequestsToTwentyIcons.ts +++ b/packages/twenty-front/src/modules/client-config/states/allowRequestsToTwentyIcons.ts @@ -1,6 +1,6 @@ -import { createState } from '@/ui/utilities/state/utils/createState'; +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; -export const allowRequestsToTwentyIconsState = createState({ +export const allowRequestsToTwentyIconsState = createStateV2({ key: 'allowRequestsToTwentyIcons', defaultValue: true, }); diff --git a/packages/twenty-front/src/modules/client-config/states/apiConfigState.ts b/packages/twenty-front/src/modules/client-config/states/apiConfigState.ts index 14d29ec31d..f01e72648e 100644 --- a/packages/twenty-front/src/modules/client-config/states/apiConfigState.ts +++ b/packages/twenty-front/src/modules/client-config/states/apiConfigState.ts @@ -1,7 +1,7 @@ import { type ApiConfig } from '~/generated-metadata/graphql'; -import { createState } from '@/ui/utilities/state/utils/createState'; +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; -export const apiConfigState = createState({ +export const apiConfigState = createStateV2({ key: 'apiConfigState', defaultValue: null, }); diff --git a/packages/twenty-front/src/modules/client-config/states/billingState.ts b/packages/twenty-front/src/modules/client-config/states/billingState.ts index 0dbe4f377a..aaffbf0ef3 100644 --- a/packages/twenty-front/src/modules/client-config/states/billingState.ts +++ b/packages/twenty-front/src/modules/client-config/states/billingState.ts @@ -1,7 +1,7 @@ import { type Billing } from '~/generated-metadata/graphql'; -import { createState } from '@/ui/utilities/state/utils/createState'; +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; -export const billingState = createState({ +export const billingState = createStateV2({ key: 'billingState', defaultValue: null, }); diff --git a/packages/twenty-front/src/modules/client-config/states/calendarBookingPageIdState.ts b/packages/twenty-front/src/modules/client-config/states/calendarBookingPageIdState.ts index e694271ef1..2ac982e5fd 100644 --- a/packages/twenty-front/src/modules/client-config/states/calendarBookingPageIdState.ts +++ b/packages/twenty-front/src/modules/client-config/states/calendarBookingPageIdState.ts @@ -1,6 +1,6 @@ -import { createState } from '@/ui/utilities/state/utils/createState'; +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; -export const calendarBookingPageIdState = createState({ +export const calendarBookingPageIdState = createStateV2({ key: 'calendarBookingPageIdState', defaultValue: null, }); diff --git a/packages/twenty-front/src/modules/client-config/states/canManageFeatureFlagsState.ts b/packages/twenty-front/src/modules/client-config/states/canManageFeatureFlagsState.ts index bf9a7b0f9b..7dbb351cd2 100644 --- a/packages/twenty-front/src/modules/client-config/states/canManageFeatureFlagsState.ts +++ b/packages/twenty-front/src/modules/client-config/states/canManageFeatureFlagsState.ts @@ -1,5 +1,5 @@ -import { createState } from '@/ui/utilities/state/utils/createState'; -export const canManageFeatureFlagsState = createState({ +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; +export const canManageFeatureFlagsState = createStateV2({ key: 'canManageFeatureFlagsState', defaultValue: false, }); diff --git a/packages/twenty-front/src/modules/client-config/states/captchaState.ts b/packages/twenty-front/src/modules/client-config/states/captchaState.ts index 721aa53d14..ccf8ac2b7f 100644 --- a/packages/twenty-front/src/modules/client-config/states/captchaState.ts +++ b/packages/twenty-front/src/modules/client-config/states/captchaState.ts @@ -1,7 +1,7 @@ import { type Captcha } from '~/generated-metadata/graphql'; -import { createState } from '@/ui/utilities/state/utils/createState'; +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; -export const captchaState = createState({ +export const captchaState = createStateV2({ key: 'captchaState', defaultValue: null, }); diff --git a/packages/twenty-front/src/modules/client-config/states/clientConfigApiStatusState.ts b/packages/twenty-front/src/modules/client-config/states/clientConfigApiStatusState.ts index 08cee90932..ffc6929d3c 100644 --- a/packages/twenty-front/src/modules/client-config/states/clientConfigApiStatusState.ts +++ b/packages/twenty-front/src/modules/client-config/states/clientConfigApiStatusState.ts @@ -1,5 +1,5 @@ import { type ClientConfig } from '@/client-config/types/ClientConfig'; -import { createState } from '@/ui/utilities/state/utils/createState'; +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; type ClientConfigApiStatus = { isLoadedOnce: boolean; @@ -10,7 +10,7 @@ type ClientConfigApiStatus = { data?: { clientConfig: ClientConfig }; }; -export const clientConfigApiStatusState = createState({ +export const clientConfigApiStatusState = createStateV2({ key: 'clientConfigApiStatus', defaultValue: { isLoadedOnce: false, diff --git a/packages/twenty-front/src/modules/client-config/states/isAttachmentPreviewEnabledState.ts b/packages/twenty-front/src/modules/client-config/states/isAttachmentPreviewEnabledState.ts index c75b4f37fb..9f96f9a0e9 100644 --- a/packages/twenty-front/src/modules/client-config/states/isAttachmentPreviewEnabledState.ts +++ b/packages/twenty-front/src/modules/client-config/states/isAttachmentPreviewEnabledState.ts @@ -1,5 +1,5 @@ -import { createState } from '@/ui/utilities/state/utils/createState'; -export const isAttachmentPreviewEnabledState = createState({ +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; +export const isAttachmentPreviewEnabledState = createStateV2({ key: 'isAttachmentPreviewEnabled', defaultValue: false, }); diff --git a/packages/twenty-front/src/modules/client-config/states/isClickHouseConfiguredState.ts b/packages/twenty-front/src/modules/client-config/states/isClickHouseConfiguredState.ts index 374e4a33b1..ea2dbbc34f 100644 --- a/packages/twenty-front/src/modules/client-config/states/isClickHouseConfiguredState.ts +++ b/packages/twenty-front/src/modules/client-config/states/isClickHouseConfiguredState.ts @@ -1,6 +1,6 @@ -import { createState } from '@/ui/utilities/state/utils/createState'; +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; -export const isClickHouseConfiguredState = createState({ +export const isClickHouseConfiguredState = createStateV2({ key: 'isClickHouseConfigured', defaultValue: false, }); diff --git a/packages/twenty-front/src/modules/client-config/states/isCloudflareIntegrationEnabledState.ts b/packages/twenty-front/src/modules/client-config/states/isCloudflareIntegrationEnabledState.ts index a0d5826096..c07582f7d3 100644 --- a/packages/twenty-front/src/modules/client-config/states/isCloudflareIntegrationEnabledState.ts +++ b/packages/twenty-front/src/modules/client-config/states/isCloudflareIntegrationEnabledState.ts @@ -1,6 +1,6 @@ -import { createState } from '@/ui/utilities/state/utils/createState'; +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; -export const isCloudflareIntegrationEnabledState = createState({ +export const isCloudflareIntegrationEnabledState = createStateV2({ key: 'isCloudflareIntegrationEnabled', defaultValue: false, }); diff --git a/packages/twenty-front/src/modules/client-config/states/isConfigVariablesInDbEnabledState.ts b/packages/twenty-front/src/modules/client-config/states/isConfigVariablesInDbEnabledState.ts index 23c09bc0ec..dd264e3a6c 100644 --- a/packages/twenty-front/src/modules/client-config/states/isConfigVariablesInDbEnabledState.ts +++ b/packages/twenty-front/src/modules/client-config/states/isConfigVariablesInDbEnabledState.ts @@ -1,5 +1,5 @@ -import { createState } from '@/ui/utilities/state/utils/createState'; -export const isConfigVariablesInDbEnabledState = createState({ +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; +export const isConfigVariablesInDbEnabledState = createStateV2({ key: 'isConfigVariablesInDbEnabled', defaultValue: false, }); diff --git a/packages/twenty-front/src/modules/client-config/states/isDeveloperDefaultSignInPrefilledState.ts b/packages/twenty-front/src/modules/client-config/states/isDeveloperDefaultSignInPrefilledState.ts index 8d92abd304..8e7873c389 100644 --- a/packages/twenty-front/src/modules/client-config/states/isDeveloperDefaultSignInPrefilledState.ts +++ b/packages/twenty-front/src/modules/client-config/states/isDeveloperDefaultSignInPrefilledState.ts @@ -1,5 +1,5 @@ -import { createState } from '@/ui/utilities/state/utils/createState'; -export const isDeveloperDefaultSignInPrefilledState = createState({ +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; +export const isDeveloperDefaultSignInPrefilledState = createStateV2({ key: 'isDeveloperDefaultSignInPrefilledState', defaultValue: false, }); diff --git a/packages/twenty-front/src/modules/client-config/states/isEmailVerificationRequiredState.ts b/packages/twenty-front/src/modules/client-config/states/isEmailVerificationRequiredState.ts index 3dd1c5e920..c6d5a3a500 100644 --- a/packages/twenty-front/src/modules/client-config/states/isEmailVerificationRequiredState.ts +++ b/packages/twenty-front/src/modules/client-config/states/isEmailVerificationRequiredState.ts @@ -1,5 +1,5 @@ -import { createState } from '@/ui/utilities/state/utils/createState'; -export const isEmailVerificationRequiredState = createState({ +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; +export const isEmailVerificationRequiredState = createStateV2({ key: 'isEmailVerificationRequired', defaultValue: false, }); diff --git a/packages/twenty-front/src/modules/client-config/states/isEmailingDomainsEnabledState.ts b/packages/twenty-front/src/modules/client-config/states/isEmailingDomainsEnabledState.ts index bec0fa5c93..3b551210f9 100644 --- a/packages/twenty-front/src/modules/client-config/states/isEmailingDomainsEnabledState.ts +++ b/packages/twenty-front/src/modules/client-config/states/isEmailingDomainsEnabledState.ts @@ -1,6 +1,6 @@ -import { createState } from '@/ui/utilities/state/utils/createState'; +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; -export const isEmailingDomainsEnabledState = createState({ +export const isEmailingDomainsEnabledState = createStateV2({ key: 'isEmailingDomainsEnabled', defaultValue: false, }); diff --git a/packages/twenty-front/src/modules/client-config/states/isGoogleCalendarEnabledState.ts b/packages/twenty-front/src/modules/client-config/states/isGoogleCalendarEnabledState.ts index 1cb56cf7f5..9d0844d91d 100644 --- a/packages/twenty-front/src/modules/client-config/states/isGoogleCalendarEnabledState.ts +++ b/packages/twenty-front/src/modules/client-config/states/isGoogleCalendarEnabledState.ts @@ -1,5 +1,5 @@ -import { createState } from '@/ui/utilities/state/utils/createState'; -export const isGoogleCalendarEnabledState = createState({ +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; +export const isGoogleCalendarEnabledState = createStateV2({ key: 'isGoogleCalendarEnabled', defaultValue: false, }); diff --git a/packages/twenty-front/src/modules/client-config/states/isGoogleMessagingEnabledState.ts b/packages/twenty-front/src/modules/client-config/states/isGoogleMessagingEnabledState.ts index 1197516a0b..0d132e064c 100644 --- a/packages/twenty-front/src/modules/client-config/states/isGoogleMessagingEnabledState.ts +++ b/packages/twenty-front/src/modules/client-config/states/isGoogleMessagingEnabledState.ts @@ -1,5 +1,5 @@ -import { createState } from '@/ui/utilities/state/utils/createState'; -export const isGoogleMessagingEnabledState = createState({ +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; +export const isGoogleMessagingEnabledState = createStateV2({ key: 'isGoogleMessagingEnabled', defaultValue: false, }); diff --git a/packages/twenty-front/src/modules/client-config/states/isImapSmtpCaldavEnabledState.ts b/packages/twenty-front/src/modules/client-config/states/isImapSmtpCaldavEnabledState.ts index 263ac6a0f4..4664ff1169 100644 --- a/packages/twenty-front/src/modules/client-config/states/isImapSmtpCaldavEnabledState.ts +++ b/packages/twenty-front/src/modules/client-config/states/isImapSmtpCaldavEnabledState.ts @@ -1,6 +1,6 @@ -import { createState } from '@/ui/utilities/state/utils/createState'; +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; -export const isImapSmtpCaldavEnabledState = createState({ +export const isImapSmtpCaldavEnabledState = createStateV2({ key: 'isImapSmtpCaldavEnabled', defaultValue: false, }); diff --git a/packages/twenty-front/src/modules/client-config/states/isMicrosoftCalendarEnabledState.ts b/packages/twenty-front/src/modules/client-config/states/isMicrosoftCalendarEnabledState.ts index 0f32f654ab..7718ace109 100644 --- a/packages/twenty-front/src/modules/client-config/states/isMicrosoftCalendarEnabledState.ts +++ b/packages/twenty-front/src/modules/client-config/states/isMicrosoftCalendarEnabledState.ts @@ -1,5 +1,5 @@ -import { createState } from '@/ui/utilities/state/utils/createState'; -export const isMicrosoftCalendarEnabledState = createState({ +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; +export const isMicrosoftCalendarEnabledState = createStateV2({ key: 'isMicrosoftCalendarEnabled', defaultValue: false, }); diff --git a/packages/twenty-front/src/modules/client-config/states/isMicrosoftMessagingEnabledState.ts b/packages/twenty-front/src/modules/client-config/states/isMicrosoftMessagingEnabledState.ts index c9a1f10eb6..dd666aaadd 100644 --- a/packages/twenty-front/src/modules/client-config/states/isMicrosoftMessagingEnabledState.ts +++ b/packages/twenty-front/src/modules/client-config/states/isMicrosoftMessagingEnabledState.ts @@ -1,5 +1,5 @@ -import { createState } from '@/ui/utilities/state/utils/createState'; -export const isMicrosoftMessagingEnabledState = createState({ +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; +export const isMicrosoftMessagingEnabledState = createStateV2({ key: 'isMicrosoftMessagingEnabled', defaultValue: false, }); diff --git a/packages/twenty-front/src/modules/client-config/states/isMultiWorkspaceEnabledState.ts b/packages/twenty-front/src/modules/client-config/states/isMultiWorkspaceEnabledState.ts index ce79841360..6a671ba123 100644 --- a/packages/twenty-front/src/modules/client-config/states/isMultiWorkspaceEnabledState.ts +++ b/packages/twenty-front/src/modules/client-config/states/isMultiWorkspaceEnabledState.ts @@ -1,5 +1,5 @@ -import { createState } from '@/ui/utilities/state/utils/createState'; -export const isMultiWorkspaceEnabledState = createState({ +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; +export const isMultiWorkspaceEnabledState = createStateV2({ key: 'isMultiWorkspaceEnabled', defaultValue: false, }); diff --git a/packages/twenty-front/src/modules/client-config/states/sentryConfigState.ts b/packages/twenty-front/src/modules/client-config/states/sentryConfigState.ts index 05277574bb..1577edffc7 100644 --- a/packages/twenty-front/src/modules/client-config/states/sentryConfigState.ts +++ b/packages/twenty-front/src/modules/client-config/states/sentryConfigState.ts @@ -1,7 +1,7 @@ import { type Sentry } from '~/generated-metadata/graphql'; -import { createState } from '@/ui/utilities/state/utils/createState'; +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; -export const sentryConfigState = createState({ +export const sentryConfigState = createStateV2({ key: 'sentryConfigState', defaultValue: null, }); diff --git a/packages/twenty-front/src/modules/client-config/states/supportChatState.ts b/packages/twenty-front/src/modules/client-config/states/supportChatState.ts index 506de2311c..880bc8293d 100644 --- a/packages/twenty-front/src/modules/client-config/states/supportChatState.ts +++ b/packages/twenty-front/src/modules/client-config/states/supportChatState.ts @@ -1,7 +1,7 @@ -import { createState } from '@/ui/utilities/state/utils/createState'; +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; import { type Support, SupportDriver } from '~/generated-metadata/graphql'; -export const supportChatState = createState({ +export const supportChatState = createStateV2({ key: 'supportChatState', defaultValue: { supportDriver: SupportDriver.NONE, diff --git a/packages/twenty-front/src/modules/command-menu/components/CommandMenuContainer.tsx b/packages/twenty-front/src/modules/command-menu/components/CommandMenuContainer.tsx index 3dea9c62b8..2b8520d197 100644 --- a/packages/twenty-front/src/modules/command-menu/components/CommandMenuContainer.tsx +++ b/packages/twenty-front/src/modules/command-menu/components/CommandMenuContainer.tsx @@ -9,7 +9,7 @@ import { RecordComponentInstanceContextsWrapper } from '@/object-record/componen import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/utils/getRecordIndexIdFromObjectNamePluralAndViewId'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; const StyledCommandMenuContainer = styled.div<{ isMobile: boolean }>` max-height: ${({ theme, isMobile }) => { @@ -35,7 +35,7 @@ export const CommandMenuContainer = ({ ); const isMobile = useIsMobile(); - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); const objectMetadataItem = objectMetadataItems.find( (objectMetadataItem) => objectMetadataItem.id === objectMetadataItemId, diff --git a/packages/twenty-front/src/modules/command-menu/components/CommandMenuContextRecordsChip.tsx b/packages/twenty-front/src/modules/command-menu/components/CommandMenuContextRecordsChip.tsx index c681f98ec8..97d5114372 100644 --- a/packages/twenty-front/src/modules/command-menu/components/CommandMenuContextRecordsChip.tsx +++ b/packages/twenty-front/src/modules/command-menu/components/CommandMenuContextRecordsChip.tsx @@ -3,8 +3,8 @@ import { CommandMenuContextRecordChipAvatars } from '@/command-menu/components/C import { getSelectedRecordsContextText } from '@/command-menu/utils/getRecordContextText'; import { useFindManyRecordsSelectedInContextStore } from '@/context-store/hooks/useFindManyRecordsSelectedInContextStore'; import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById'; -import { useRecoilValue } from 'recoil'; import { allowRequestsToTwentyIconsState } from '@/client-config/states/allowRequestsToTwentyIcons'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; export const CommandMenuContextRecordsChip = ({ objectMetadataItemId, @@ -16,7 +16,7 @@ export const CommandMenuContextRecordsChip = ({ const { objectMetadataItem } = useObjectMetadataItemById({ objectId: objectMetadataItemId, }); - const allowRequestsToTwentyIcons = useRecoilValue( + const allowRequestsToTwentyIcons = useRecoilValueV2( allowRequestsToTwentyIconsState, ); diff --git a/packages/twenty-front/src/modules/command-menu/components/CommandMenuRecordInfo.tsx b/packages/twenty-front/src/modules/command-menu/components/CommandMenuRecordInfo.tsx index 96375595fd..308227b6bc 100644 --- a/packages/twenty-front/src/modules/command-menu/components/CommandMenuRecordInfo.tsx +++ b/packages/twenty-front/src/modules/command-menu/components/CommandMenuRecordInfo.tsx @@ -16,6 +16,8 @@ import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { Trans } from '@lingui/react/macro'; import { isNonEmptyString } from '@sniptt/guards'; import { useRecoilValue } from 'recoil'; + +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { Avatar } from 'twenty-ui/display'; import { FeatureFlagKey, @@ -34,7 +36,7 @@ export const CommandMenuRecordInfo = ({ viewableRecordNameSingularComponentState, commandMenuPageInstanceId, ); - const allowRequestsToTwentyIcons = useRecoilValue( + const allowRequestsToTwentyIcons = useRecoilValueV2( allowRequestsToTwentyIconsState, ); diff --git a/packages/twenty-front/src/modules/command-menu/components/ResetContextToSelectionCommandButton.tsx b/packages/twenty-front/src/modules/command-menu/components/ResetContextToSelectionCommandButton.tsx index 19b99b6e20..34b8d61b26 100644 --- a/packages/twenty-front/src/modules/command-menu/components/ResetContextToSelectionCommandButton.tsx +++ b/packages/twenty-front/src/modules/command-menu/components/ResetContextToSelectionCommandButton.tsx @@ -8,8 +8,8 @@ import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/s import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { t } from '@lingui/core/macro'; -import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { IconArrowBackUp } from 'twenty-ui/display'; @@ -24,7 +24,7 @@ export const ResetContextToSelectionCommandButton = () => { 'command-menu-previous', ); - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); const objectMetadataItem = objectMetadataItems.find( (objectMetadataItem) => diff --git a/packages/twenty-front/src/modules/command-menu/components/__stories__/CommandMenu.stories.tsx b/packages/twenty-front/src/modules/command-menu/components/__stories__/CommandMenu.stories.tsx index 65dc4ae5cf..1274cccf22 100644 --- a/packages/twenty-front/src/modules/command-menu/components/__stories__/CommandMenu.stories.tsx +++ b/packages/twenty-front/src/modules/command-menu/components/__stories__/CommandMenu.stories.tsx @@ -8,6 +8,7 @@ import { expect, userEvent, within } from 'storybook/test'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; +import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; import { ComponentWithRouterDecorator } from '~/testing/decorators/ComponentWithRouterDecorator'; import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator'; import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator'; @@ -72,11 +73,11 @@ const meta: Meta = { component: CommandMenuRouter, decorators: [ (Story) => { - const setCurrentWorkspace = useSetRecoilState(currentWorkspaceState); - const setCurrentUserWorkspace = useSetRecoilState( + const setCurrentWorkspace = useSetRecoilStateV2(currentWorkspaceState); + const setCurrentUserWorkspace = useSetRecoilStateV2( currentUserWorkspaceState, ); - const setCurrentWorkspaceMember = useSetRecoilState( + const setCurrentWorkspaceMember = useSetRecoilStateV2( currentWorkspaceMemberState, ); const setIsCommandMenuOpened = useSetRecoilState( @@ -138,7 +139,7 @@ export const LimitedPermissions: Story = { }, decorators: [ (Story) => { - const setCurrentUserWorkspace = useSetRecoilState( + const setCurrentUserWorkspace = useSetRecoilStateV2( currentUserWorkspaceState, ); setCurrentUserWorkspace( diff --git a/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuContextChips.tsx b/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuContextChips.tsx index 883fcdff18..0c96227aa0 100644 --- a/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuContextChips.tsx +++ b/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuContextChips.tsx @@ -12,6 +12,8 @@ import { useMemo } from 'react'; import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { allowRequestsToTwentyIconsState } from '@/client-config/states/allowRequestsToTwentyIcons'; +import { useFamilySelectorValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilySelectorValueV2'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; const StyledIconWrapper = styled.div` background: ${({ theme }) => theme.background.primary}; @@ -26,11 +28,11 @@ export const useCommandMenuContextChips = () => { commandMenuNavigationStackState, ); - const allowRequestsToTwentyIcons = useRecoilValue( + const allowRequestsToTwentyIcons = useRecoilValueV2( allowRequestsToTwentyIconsState, ); - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); const { navigateCommandMenuHistory } = useCommandMenuHistory(); @@ -46,11 +48,12 @@ export const useCommandMenuContextChips = () => { morphItems.map((morphItem) => morphItem.recordId), ); - const recordIdentifiers = useRecoilValue( - recordStoreIdentifiersFamilySelector({ + const recordIdentifiers = useFamilySelectorValueV2( + recordStoreIdentifiersFamilySelector, + { recordIds: allRecordIds, allowRequestsToTwentyIcons, - }), + }, ); const records = useRecoilValue( recordStoreRecordsSelector({ diff --git a/packages/twenty-front/src/modules/command-menu/hooks/useOpenRecordInCommandMenu.ts b/packages/twenty-front/src/modules/command-menu/hooks/useOpenRecordInCommandMenu.ts index 5f5135d492..c394bc072d 100644 --- a/packages/twenty-front/src/modules/command-menu/hooks/useOpenRecordInCommandMenu.ts +++ b/packages/twenty-front/src/modules/command-menu/hooks/useOpenRecordInCommandMenu.ts @@ -16,6 +16,7 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi import { getIconColorForObjectType } from '@/object-metadata/utils/getIconColorForObjectType'; import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState'; import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { CommandMenuPages } from 'twenty-shared/types'; import { useRunWorkflowRunOpeningInCommandMenuSideEffects } from '@/workflow/hooks/useRunWorkflowRunOpeningInCommandMenuSideEffects'; @@ -83,14 +84,12 @@ export const useOpenRecordInCommandMenu = () => { ); set(viewableRecordIdState, recordId); - const objectMetadataItem = snapshot - .getLoadable( - objectMetadataItemFamilySelector({ - objectName: objectNameSingular, - objectNameType: 'singular', - }), - ) - .getValue(); + const objectMetadataItem = jotaiStore.get( + objectMetadataItemFamilySelector.selectorFamily({ + objectName: objectNameSingular, + objectNameType: 'singular', + }), + ); if (!objectMetadataItem) { throw new Error( diff --git a/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuObjectMenuItem.tsx b/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuObjectMenuItem.tsx index a966f1f82f..9185a417cb 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuObjectMenuItem.tsx +++ b/packages/twenty-front/src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuObjectMenuItem.tsx @@ -1,5 +1,4 @@ import { useTheme } from '@emotion/react'; -import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { useIcons } from 'twenty-ui/display'; @@ -10,6 +9,7 @@ import { NavigationMenuItemType } from '@/navigation-menu-item/constants/Navigat import { getNavigationMenuItemIconColors } from '@/navigation-menu-item/utils/getNavigationMenuItemIconColors'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem'; +import { useFamilySelectorValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilySelectorValueV2'; import { coreIndexViewIdFromObjectMetadataItemFamilySelector } from '@/views/states/selectors/coreIndexViewIdFromObjectMetadataItemFamilySelector'; type CommandMenuObjectMenuItemProps = { @@ -31,10 +31,9 @@ export const CommandMenuObjectMenuItem = ({ const theme = useTheme(); const { getIcon } = useIcons(); const iconColors = getNavigationMenuItemIconColors(theme); - const defaultViewId = useRecoilValue( - coreIndexViewIdFromObjectMetadataItemFamilySelector({ - objectMetadataItemId: objectMetadataItem.id, - }), + const defaultViewId = useFamilySelectorValueV2( + coreIndexViewIdFromObjectMetadataItemFamilySelector, + { objectMetadataItemId: objectMetadataItem.id }, ); const Icon = getIcon(objectMetadataItem.icon); const isDisabled = !isDefined(defaultViewId); diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/CommandMenuPageLayoutGraphFilter.tsx b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/CommandMenuPageLayoutGraphFilter.tsx index 353941563a..e50bc7a210 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/CommandMenuPageLayoutGraphFilter.tsx +++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/CommandMenuPageLayoutGraphFilter.tsx @@ -5,7 +5,7 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadat import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState'; import { pageLayoutEditingWidgetIdComponentState } from '@/page-layout/states/pageLayoutEditingWidgetIdComponentState'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { isDefined } from 'twenty-shared/utils'; export const CommandMenuPageLayoutGraphFilter = () => { @@ -25,7 +25,7 @@ export const CommandMenuPageLayoutGraphFilter = () => { .flatMap((tab) => tab.widgets) .find((widget) => widget.id === pageLayoutEditingWidgetId); - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); if ( !isDefined(widgetInEditMode) || diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/hooks/__tests__/useChartSettingsValues.test.tsx b/packages/twenty-front/src/modules/command-menu/pages/page-layout/hooks/__tests__/useChartSettingsValues.test.tsx index 00906c8f64..4afd2f53a1 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/hooks/__tests__/useChartSettingsValues.test.tsx +++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/hooks/__tests__/useChartSettingsValues.test.tsx @@ -1,4 +1,5 @@ import { renderHook } from '@testing-library/react'; +import { Provider as JotaiProvider } from 'jotai'; import { RecoilRoot } from 'recoil'; import { type ChartConfiguration } from '@/command-menu/pages/page-layout/types/ChartConfiguration'; @@ -16,6 +17,7 @@ import { WidgetConfigurationType, } from '~/generated-metadata/graphql'; import { useChartSettingsValues } from '@/command-menu/pages/page-layout/hooks/useChartSettingsValues'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; const mockObjectMetadataItem: ObjectMetadataItem = { id: 'obj-1', @@ -79,6 +81,8 @@ const buildBarChartConfiguration = ( }) as TypedBarChartConfiguration; const renderUseChartSettingsValues = (configuration: ChartConfiguration) => { + jotaiStore.set(objectMetadataItemsState.atom, [mockObjectMetadataItem]); + return renderHook( () => useChartSettingsValues({ @@ -87,13 +91,9 @@ const renderUseChartSettingsValues = (configuration: ChartConfiguration) => { }), { wrapper: ({ children }) => ( - { - set(objectMetadataItemsState, [mockObjectMetadataItem]); - }} - > - {children} - + + {children} + ), }, ); @@ -414,6 +414,8 @@ describe('useChartSettingsValues', () => { it('should handle missing objectMetadataItem gracefully', () => { const config = buildBarChartConfiguration({}); + jotaiStore.set(objectMetadataItemsState.atom, [mockObjectMetadataItem]); + const { result } = renderHook( () => useChartSettingsValues({ @@ -422,13 +424,9 @@ describe('useChartSettingsValues', () => { }), { wrapper: ({ children }) => ( - { - set(objectMetadataItemsState, [mockObjectMetadataItem]); - }} - > - {children} - + + {children} + ), }, ); diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts b/packages/twenty-front/src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts index 8d7973d6cf..757b702e85 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts +++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts @@ -10,7 +10,7 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadat import { getAggregateOperationLabel } from '@/object-record/record-board/record-board-column/utils/getAggregateOperationLabel'; import { convertAggregateOperationToExtendedAggregateOperation } from '@/object-record/utils/convertAggregateOperationToExtendedAggregateOperation'; import { plural, t } from '@lingui/core/macro'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { type CompositeFieldSubFieldName } from 'twenty-shared/types'; import { capitalize, isDefined } from 'twenty-shared/utils'; import { type GraphOrderBy } from '~/generated-metadata/graphql'; @@ -22,7 +22,7 @@ export const useChartSettingsValues = ({ objectMetadataId: string; configuration?: ChartConfiguration; }) => { - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); const objectMetadataItem = objectMetadataItems.find( (objectMetadataItem) => objectMetadataItem.id === objectMetadataId, diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/hooks/useGraphGroupBySortOptionLabels.ts b/packages/twenty-front/src/modules/command-menu/pages/page-layout/hooks/useGraphGroupBySortOptionLabels.ts index 1e43f39d2f..48719aec90 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/hooks/useGraphGroupBySortOptionLabels.ts +++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/hooks/useGraphGroupBySortOptionLabels.ts @@ -2,7 +2,7 @@ import { getFieldLabelWithSubField } from '@/command-menu/pages/page-layout/util import { getSortLabelSuffixForFieldType } from '@/command-menu/pages/page-layout/utils/getSortLabelSuffixForFieldType'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { t } from '@lingui/core/macro'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { type CompositeFieldSubFieldName } from 'twenty-shared/types'; import { assertUnreachable } from 'twenty-shared/utils'; import { GraphOrderBy } from '~/generated-metadata/graphql'; @@ -12,7 +12,7 @@ export const useGraphGroupBySortOptionLabels = ({ }: { objectMetadataId: string; }) => { - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); const objectMetadataItem = objectMetadataItems.find( (objectMetadataItem) => objectMetadataItem.id === objectMetadataId, diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/hooks/useGraphXSortOptionLabels.ts b/packages/twenty-front/src/modules/command-menu/pages/page-layout/hooks/useGraphXSortOptionLabels.ts index 28674c34ee..d716295f7e 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/hooks/useGraphXSortOptionLabels.ts +++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/hooks/useGraphXSortOptionLabels.ts @@ -4,7 +4,7 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadat import { getAggregateOperationLabel } from '@/object-record/record-board/record-board-column/utils/getAggregateOperationLabel'; import { type ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations'; import { t } from '@lingui/core/macro'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { type CompositeFieldSubFieldName } from 'twenty-shared/types'; import { assertUnreachable, isDefined } from 'twenty-shared/utils'; import { GraphOrderBy } from '~/generated-metadata/graphql'; @@ -14,7 +14,7 @@ export const useGraphXSortOptionLabels = ({ }: { objectMetadataId: string; }) => { - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); const objectMetadataItem = objectMetadataItems.find( (objectMetadataItem) => objectMetadataItem.id === objectMetadataId, diff --git a/packages/twenty-front/src/modules/context-store/components/MainContextStoreProvider.tsx b/packages/twenty-front/src/modules/context-store/components/MainContextStoreProvider.tsx index a735597211..b7222dc6e0 100644 --- a/packages/twenty-front/src/modules/context-store/components/MainContextStoreProvider.tsx +++ b/packages/twenty-front/src/modules/context-store/components/MainContextStoreProvider.tsx @@ -1,13 +1,16 @@ +import { metadataStoreState } from '@/app/states/metadataStoreState'; import { MainContextStoreProviderEffect } from '@/context-store/components/MainContextStoreProviderEffect'; import { useIsSettingsPage } from '@/navigation/hooks/useIsSettingsPage'; import { useLastVisitedView } from '@/navigation/hooks/useLastVisitedView'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { useShowAuthModal } from '@/ui/layout/hooks/useShowAuthModal'; -import { coreIndexViewIdFromObjectMetadataItemFamilySelector } from '@/views/states/selectors/coreIndexViewIdFromObjectMetadataItemFamilySelector'; +import { useFamilyRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilyRecoilValueV2'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; +import { coreViewsState } from '@/views/states/coreViewState'; import { useLocation, useParams, useSearchParams } from 'react-router-dom'; -import { useRecoilValue } from 'recoil'; import { AppPath } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; +import { ViewKey } from '~/generated-metadata/graphql'; import { isMatchingLocation } from '~/utils/isMatchingLocation'; const getViewId = ( @@ -45,7 +48,9 @@ export const MainContextStoreProvider = () => { const [searchParams] = useSearchParams(); const viewIdQueryParam = searchParams.get('viewId'); - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); + const viewsEntry = useFamilyRecoilValueV2(metadataStoreState, 'views'); + const coreViews = useRecoilValueV2(coreViewsState); const objectMetadataItem = objectMetadataItems.find( (objectMetadataItem) => @@ -59,17 +64,19 @@ export const MainContextStoreProvider = () => { objectMetadataItem?.namePlural ?? '', ); - const indexViewId = useRecoilValue( - coreIndexViewIdFromObjectMetadataItemFamilySelector({ - objectMetadataItemId: objectMetadataItem?.id ?? '', - }), - ); + const indexViewId = coreViews.find( + (view) => + view.objectMetadataId === objectMetadataItem?.id && + view.key === ViewKey.INDEX, + )?.id; const viewId = getViewId(viewIdQueryParam, indexViewId, lastVisitedViewId); const showAuthModal = useShowAuthModal(); const shouldComputeContextStore = - (isRecordIndexPage || isRecordShowPage || isSettingsPage) && !showAuthModal; + (isRecordIndexPage || isRecordShowPage || isSettingsPage) && + !showAuthModal && + viewsEntry.status === 'loaded'; if (!shouldComputeContextStore) { return null; diff --git a/packages/twenty-front/src/modules/context-store/components/MainContextStoreProviderEffect.tsx b/packages/twenty-front/src/modules/context-store/components/MainContextStoreProviderEffect.tsx index 7c3c5d5f2e..48afd4b430 100644 --- a/packages/twenty-front/src/modules/context-store/components/MainContextStoreProviderEffect.tsx +++ b/packages/twenty-front/src/modules/context-store/components/MainContextStoreProviderEffect.tsx @@ -7,9 +7,9 @@ import { useSetLastVisitedObjectMetadataId } from '@/navigation/hooks/useSetLast import { useSetLastVisitedViewForObjectMetadataNamePlural } from '@/navigation/hooks/useSetLastVisitedViewForObjectMetadataNamePlural'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState'; +import { useFamilySelectorValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilySelectorValueV2'; import { coreViewFromViewIdFamilySelector } from '@/views/states/selectors/coreViewFromViewIdFamilySelector'; import { useEffect } from 'react'; -import { useRecoilValue } from 'recoil'; type MainContextStoreProviderEffectProps = { viewId?: string; @@ -52,11 +52,9 @@ export const MainContextStoreProviderEffect = ({ MAIN_CONTEXT_STORE_INSTANCE_ID, ); - const view = useRecoilValue( - coreViewFromViewIdFamilySelector({ - viewId: viewId ?? '', - }), - ); + const view = useFamilySelectorValueV2(coreViewFromViewIdFamilySelector, { + viewId: viewId ?? '', + }); useEffect(() => { if (contextStoreCurrentObjectMetadataItemId !== objectMetadataItem?.id) { diff --git a/packages/twenty-front/src/modules/context-store/hooks/useContextStoreObjectMetadataItem.ts b/packages/twenty-front/src/modules/context-store/hooks/useContextStoreObjectMetadataItem.ts index c02ee5cee5..8a79f68290 100644 --- a/packages/twenty-front/src/modules/context-store/hooks/useContextStoreObjectMetadataItem.ts +++ b/packages/twenty-front/src/modules/context-store/hooks/useContextStoreObjectMetadataItem.ts @@ -1,7 +1,7 @@ import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; export const useContextStoreObjectMetadataItem = ( contextStoreInstanceId?: string, @@ -11,7 +11,7 @@ export const useContextStoreObjectMetadataItem = ( contextStoreInstanceId, ); - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); const objectMetadataItem = objectMetadataItems.find( (objectMetadataItemToFind) => diff --git a/packages/twenty-front/src/modules/context-store/hooks/useFindManyRecordsSelectedInContextStore.ts b/packages/twenty-front/src/modules/context-store/hooks/useFindManyRecordsSelectedInContextStore.ts index 62a3fa4510..8c0e3426f3 100644 --- a/packages/twenty-front/src/modules/context-store/hooks/useFindManyRecordsSelectedInContextStore.ts +++ b/packages/twenty-front/src/modules/context-store/hooks/useFindManyRecordsSelectedInContextStore.ts @@ -10,7 +10,7 @@ import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies'; import { RecordFilterOperand } from '@/object-record/record-filter/types/RecordFilterOperand'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; export const useFindManyRecordsSelectedInContextStore = ({ instanceId, @@ -50,7 +50,7 @@ export const useFindManyRecordsSelectedInContextStore = ({ const { filterValueDependencies } = useFilterValueDependencies(); - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); const allFieldMetadataItems = objectMetadataItems.flatMap( (objectMetadataItem) => objectMetadataItem.fields, diff --git a/packages/twenty-front/src/modules/domain-manager/hooks/useBuildSearchParamsFromUrlSyncedStates.ts b/packages/twenty-front/src/modules/domain-manager/hooks/useBuildSearchParamsFromUrlSyncedStates.ts index b4fea3aa17..43626e6188 100644 --- a/packages/twenty-front/src/modules/domain-manager/hooks/useBuildSearchParamsFromUrlSyncedStates.ts +++ b/packages/twenty-front/src/modules/domain-manager/hooks/useBuildSearchParamsFromUrlSyncedStates.ts @@ -1,27 +1,25 @@ +import { useCallback } from 'react'; + import { billingCheckoutSessionState } from '@/auth/states/billingCheckoutSessionState'; import { BILLING_CHECKOUT_SESSION_DEFAULT_VALUE } from '@/billing/constants/BillingCheckoutSessionDefaultValue'; -import { useRecoilCallback } from 'recoil'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; export const useBuildSearchParamsFromUrlSyncedStates = () => { - const buildSearchParamsFromUrlSyncedStates = useRecoilCallback( - ({ snapshot }) => - async () => { - const billingCheckoutSession = snapshot - .getLoadable(billingCheckoutSessionState) - .getValue(); + const buildSearchParamsFromUrlSyncedStates = useCallback(async () => { + const billingCheckoutSession = jotaiStore.get( + billingCheckoutSessionState.atom, + ); - const output = { - ...(billingCheckoutSession !== BILLING_CHECKOUT_SESSION_DEFAULT_VALUE - ? { - billingCheckoutSession: JSON.stringify(billingCheckoutSession), - } - : {}), - }; + const output = { + ...(billingCheckoutSession !== BILLING_CHECKOUT_SESSION_DEFAULT_VALUE + ? { + billingCheckoutSession: JSON.stringify(billingCheckoutSession), + } + : {}), + }; - return output; - }, - [], - ); + return output; + }, []); return { buildSearchParamsFromUrlSyncedStates, diff --git a/packages/twenty-front/src/modules/domain-manager/hooks/useGetPublicWorkspaceDataByDomain.ts b/packages/twenty-front/src/modules/domain-manager/hooks/useGetPublicWorkspaceDataByDomain.ts index 2a23dd3dfa..89cf908b68 100644 --- a/packages/twenty-front/src/modules/domain-manager/hooks/useGetPublicWorkspaceDataByDomain.ts +++ b/packages/twenty-front/src/modules/domain-manager/hooks/useGetPublicWorkspaceDataByDomain.ts @@ -4,28 +4,31 @@ import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWork import { useIsCurrentLocationOnDefaultDomain } from '@/domain-manager/hooks/useIsCurrentLocationOnDefaultDomain'; import { useOrigin } from '@/domain-manager/hooks/useOrigin'; import { useRedirectToDefaultDomain } from '@/domain-manager/hooks/useRedirectToDefaultDomain'; -import { workspaceAuthProvidersState } from '@/workspace/states/workspaceAuthProvidersState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; +import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; import { workspaceAuthBypassProvidersState } from '@/workspace/states/workspaceAuthBypassProvidersState'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; +import { workspaceAuthProvidersState } from '@/workspace/states/workspaceAuthProvidersState'; import { isDefined } from 'twenty-shared/utils'; import { useGetPublicWorkspaceDataByDomainQuery } from '~/generated-metadata/graphql'; export const useGetPublicWorkspaceDataByDomain = () => { const { isDefaultDomain } = useIsCurrentLocationOnDefaultDomain(); - const isMultiWorkspaceEnabled = useRecoilValue(isMultiWorkspaceEnabledState); + const isMultiWorkspaceEnabled = useRecoilValueV2( + isMultiWorkspaceEnabledState, + ); const { origin } = useOrigin(); - const setWorkspaceAuthProviders = useSetRecoilState( + const setWorkspaceAuthProviders = useSetRecoilStateV2( workspaceAuthProvidersState, ); - const setWorkspaceAuthBypassProviders = useSetRecoilState( + const setWorkspaceAuthBypassProviders = useSetRecoilStateV2( workspaceAuthBypassProvidersState, ); - const workspacePublicData = useRecoilValue(workspacePublicDataState); + const workspacePublicData = useRecoilValueV2(workspacePublicDataState); const { redirectToDefaultDomain } = useRedirectToDefaultDomain(); - const setWorkspacePublicDataState = useSetRecoilState( + const setWorkspacePublicDataState = useSetRecoilStateV2( workspacePublicDataState, ); - const clientConfigApiStatus = useRecoilValue(clientConfigApiStatusState); + const clientConfigApiStatus = useRecoilValueV2(clientConfigApiStatusState); const { loading, data, error } = useGetPublicWorkspaceDataByDomainQuery({ variables: { diff --git a/packages/twenty-front/src/modules/domain-manager/hooks/useIsCurrentLocationOnAWorkspace.ts b/packages/twenty-front/src/modules/domain-manager/hooks/useIsCurrentLocationOnAWorkspace.ts index 25590b27f5..1e707f2f7b 100644 --- a/packages/twenty-front/src/modules/domain-manager/hooks/useIsCurrentLocationOnAWorkspace.ts +++ b/packages/twenty-front/src/modules/domain-manager/hooks/useIsCurrentLocationOnAWorkspace.ts @@ -1,14 +1,16 @@ import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState'; import { useReadDefaultDomainFromConfiguration } from '@/domain-manager/hooks/useReadDefaultDomainFromConfiguration'; import { domainConfigurationState } from '@/domain-manager/states/domainConfigurationState'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { isDefined } from 'twenty-shared/utils'; export const useIsCurrentLocationOnAWorkspace = () => { const { defaultDomain } = useReadDefaultDomainFromConfiguration(); - const isMultiWorkspaceEnabled = useRecoilValue(isMultiWorkspaceEnabledState); - const domainConfiguration = useRecoilValue(domainConfigurationState); + const isMultiWorkspaceEnabled = useRecoilValueV2( + isMultiWorkspaceEnabledState, + ); + const domainConfiguration = useRecoilValueV2(domainConfigurationState); if ( isMultiWorkspaceEnabled && diff --git a/packages/twenty-front/src/modules/domain-manager/hooks/useIsCurrentLocationOnDefaultDomain.ts b/packages/twenty-front/src/modules/domain-manager/hooks/useIsCurrentLocationOnDefaultDomain.ts index 43ccc460f5..1cad029fac 100644 --- a/packages/twenty-front/src/modules/domain-manager/hooks/useIsCurrentLocationOnDefaultDomain.ts +++ b/packages/twenty-front/src/modules/domain-manager/hooks/useIsCurrentLocationOnDefaultDomain.ts @@ -1,9 +1,11 @@ import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { useReadDefaultDomainFromConfiguration } from '@/domain-manager/hooks/useReadDefaultDomainFromConfiguration'; export const useIsCurrentLocationOnDefaultDomain = () => { - const isMultiWorkspaceEnabled = useRecoilValue(isMultiWorkspaceEnabledState); + const isMultiWorkspaceEnabled = useRecoilValueV2( + isMultiWorkspaceEnabledState, + ); const { defaultDomain } = useReadDefaultDomainFromConfiguration(); const isDefaultDomain = isMultiWorkspaceEnabled ? window.location.hostname === defaultDomain diff --git a/packages/twenty-front/src/modules/domain-manager/hooks/useLastAuthenticatedWorkspaceDomain.ts b/packages/twenty-front/src/modules/domain-manager/hooks/useLastAuthenticatedWorkspaceDomain.ts index 8c04d9680b..2f7fa20a63 100644 --- a/packages/twenty-front/src/modules/domain-manager/hooks/useLastAuthenticatedWorkspaceDomain.ts +++ b/packages/twenty-front/src/modules/domain-manager/hooks/useLastAuthenticatedWorkspaceDomain.ts @@ -1,10 +1,11 @@ import { domainConfigurationState } from '@/domain-manager/states/domainConfigurationState'; import { lastAuthenticatedWorkspaceDomainState } from '@/domain-manager/states/lastAuthenticatedWorkspaceDomainState'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; +import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; export const useLastAuthenticatedWorkspaceDomain = () => { - const domainConfiguration = useRecoilValue(domainConfigurationState); - const setLastAuthenticatedWorkspaceDomain = useSetRecoilState( + const domainConfiguration = useRecoilValueV2(domainConfigurationState); + const setLastAuthenticatedWorkspaceDomain = useSetRecoilStateV2( lastAuthenticatedWorkspaceDomainState, ); const setLastAuthenticateWorkspaceDomainWithCookieAttributes = ( diff --git a/packages/twenty-front/src/modules/domain-manager/hooks/useReadDefaultDomainFromConfiguration.ts b/packages/twenty-front/src/modules/domain-manager/hooks/useReadDefaultDomainFromConfiguration.ts index 7b46360c14..33e5f3be1d 100644 --- a/packages/twenty-front/src/modules/domain-manager/hooks/useReadDefaultDomainFromConfiguration.ts +++ b/packages/twenty-front/src/modules/domain-manager/hooks/useReadDefaultDomainFromConfiguration.ts @@ -1,10 +1,12 @@ import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState'; import { domainConfigurationState } from '@/domain-manager/states/domainConfigurationState'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; export const useReadDefaultDomainFromConfiguration = () => { - const domainConfiguration = useRecoilValue(domainConfigurationState); - const isMultiWorkspaceEnabled = useRecoilValue(isMultiWorkspaceEnabledState); + const domainConfiguration = useRecoilValueV2(domainConfigurationState); + const isMultiWorkspaceEnabled = useRecoilValueV2( + isMultiWorkspaceEnabledState, + ); const defaultDomain = isMultiWorkspaceEnabled ? `${domainConfiguration.defaultSubdomain}.${domainConfiguration.frontDomain}` diff --git a/packages/twenty-front/src/modules/domain-manager/hooks/useRedirectToWorkspaceDomain.ts b/packages/twenty-front/src/modules/domain-manager/hooks/useRedirectToWorkspaceDomain.ts index 922202cae1..9c87cb9818 100644 --- a/packages/twenty-front/src/modules/domain-manager/hooks/useRedirectToWorkspaceDomain.ts +++ b/packages/twenty-front/src/modules/domain-manager/hooks/useRedirectToWorkspaceDomain.ts @@ -2,10 +2,12 @@ import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWork import { useBuildSearchParamsFromUrlSyncedStates } from '@/domain-manager/hooks/useBuildSearchParamsFromUrlSyncedStates'; import { useBuildWorkspaceUrl } from '@/domain-manager/hooks/useBuildWorkspaceUrl'; import { useRedirect } from '@/domain-manager/hooks/useRedirect'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; export const useRedirectToWorkspaceDomain = () => { - const isMultiWorkspaceEnabled = useRecoilValue(isMultiWorkspaceEnabledState); + const isMultiWorkspaceEnabled = useRecoilValueV2( + isMultiWorkspaceEnabledState, + ); const { buildWorkspaceUrl } = useBuildWorkspaceUrl(); const { redirect } = useRedirect(); diff --git a/packages/twenty-front/src/modules/domain-manager/states/domainConfigurationState.ts b/packages/twenty-front/src/modules/domain-manager/states/domainConfigurationState.ts index d7895dc525..8ec2bc4712 100644 --- a/packages/twenty-front/src/modules/domain-manager/states/domainConfigurationState.ts +++ b/packages/twenty-front/src/modules/domain-manager/states/domainConfigurationState.ts @@ -1,7 +1,7 @@ import { type ClientConfig } from '@/client-config/types/ClientConfig'; -import { createState } from '@/ui/utilities/state/utils/createState'; +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; -export const domainConfigurationState = createState< +export const domainConfigurationState = createStateV2< Pick >({ key: 'domainConfiguration', diff --git a/packages/twenty-front/src/modules/domain-manager/states/lastAuthenticatedWorkspaceDomainState.ts b/packages/twenty-front/src/modules/domain-manager/states/lastAuthenticatedWorkspaceDomainState.ts index bc37b96423..c41bef3ea1 100644 --- a/packages/twenty-front/src/modules/domain-manager/states/lastAuthenticatedWorkspaceDomainState.ts +++ b/packages/twenty-front/src/modules/domain-manager/states/lastAuthenticatedWorkspaceDomainState.ts @@ -1,7 +1,6 @@ -import { cookieStorageEffect } from '~/utils/recoil/cookieStorageEffect'; -import { createState } from '@/ui/utilities/state/utils/createState'; +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; -export const lastAuthenticatedWorkspaceDomainState = createState< +export const lastAuthenticatedWorkspaceDomainState = createStateV2< | { workspaceUrl: string; workspaceId: string; @@ -13,9 +12,10 @@ export const lastAuthenticatedWorkspaceDomainState = createState< >({ key: 'lastAuthenticateWorkspaceDomain', defaultValue: null, - effects: [ - cookieStorageEffect('lastAuthenticateWorkspaceDomain', { - expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365), // 1 year - }), - ], + useCookieStorage: { + cookieKey: 'lastAuthenticateWorkspaceDomain', + attributes: { + expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365), + }, + }, }); diff --git a/packages/twenty-front/src/modules/error-handler/components/SentryInitEffect.tsx b/packages/twenty-front/src/modules/error-handler/components/SentryInitEffect.tsx index e68b5e5183..2191d991a8 100644 --- a/packages/twenty-front/src/modules/error-handler/components/SentryInitEffect.tsx +++ b/packages/twenty-front/src/modules/error-handler/components/SentryInitEffect.tsx @@ -1,20 +1,19 @@ -import { useEffect, useState } from 'react'; -import { useRecoilValue } from 'recoil'; - import { currentUserState } from '@/auth/states/currentUserState'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { sentryConfigState } from '@/client-config/states/sentryConfigState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; +import { useEffect, useState } from 'react'; import { isNonEmptyString } from '@sniptt/guards'; import { isDefined } from 'twenty-shared/utils'; import { REACT_APP_SERVER_BASE_URL } from '~/config'; export const SentryInitEffect = () => { - const sentryConfig = useRecoilValue(sentryConfigState); + const sentryConfig = useRecoilValueV2(sentryConfigState); - const currentUser = useRecoilValue(currentUserState); - const currentWorkspace = useRecoilValue(currentWorkspaceState); - const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); + const currentUser = useRecoilValueV2(currentUserState); + const currentWorkspace = useRecoilValueV2(currentWorkspaceState); + const currentWorkspaceMember = useRecoilValueV2(currentWorkspaceMemberState); const [isSentryInitialized, setIsSentryInitialized] = useState(false); const [isSentryInitializing, setIsSentryInitializing] = useState(false); diff --git a/packages/twenty-front/src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx b/packages/twenty-front/src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx index 88d6bf0481..4aa478c541 100644 --- a/packages/twenty-front/src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx +++ b/packages/twenty-front/src/modules/favorites/components/CurrentWorkspaceMemberFavorites.tsx @@ -12,6 +12,7 @@ import { getFavoriteSecondaryLabel } from '@/favorites/utils/getFavoriteSecondar import { isLocationMatchingFavorite } from '@/favorites/utils/isLocationMatchingFavorite'; import { type ProcessedFavorite } from '@/favorites/utils/sortFavorites'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { DraggableItem } from '@/ui/layout/draggable-list/components/DraggableItem'; import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown'; import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState'; @@ -31,7 +32,6 @@ import { useLingui } from '@lingui/react/macro'; import { useContext, useState } from 'react'; import { createPortal } from 'react-dom'; import { useLocation } from 'react-router-dom'; -import { useRecoilValue } from 'recoil'; import { IconFolder, IconFolderOpen, IconHeartOff } from 'twenty-ui/display'; import { LightIconButton } from 'twenty-ui/input'; import { AnimatedExpandableContainer } from 'twenty-ui/layout'; @@ -51,7 +51,7 @@ export const CurrentWorkspaceMemberFavorites = ({ isGroup, }: CurrentWorkspaceMemberFavoritesProps) => { const { t } = useLingui(); - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); const currentPath = useLocation().pathname; const currentViewPath = useLocation().pathname + useLocation().search; const { isDragging } = useContext(FavoritesDragContext); diff --git a/packages/twenty-front/src/modules/favorites/components/CurrentWorkspaceMemberFavoritesFolders.tsx b/packages/twenty-front/src/modules/favorites/components/CurrentWorkspaceMemberFavoritesFolders.tsx index 7a5d12617e..1f269add32 100644 --- a/packages/twenty-front/src/modules/favorites/components/CurrentWorkspaceMemberFavoritesFolders.tsx +++ b/packages/twenty-front/src/modules/favorites/components/CurrentWorkspaceMemberFavoritesFolders.tsx @@ -13,14 +13,14 @@ import { NavigationDrawerSectionTitle } from '@/ui/navigation/navigation-drawer/ import { useNavigationSection } from '@/ui/navigation/navigation-drawer/hooks/useNavigationSection'; import { isNavigationSectionOpenFamilyState } from '@/ui/navigation/navigation-drawer/states/isNavigationSectionOpenFamilyState'; import { useFamilyRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilyRecoilValueV2'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { useLingui } from '@lingui/react/macro'; -import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { IconFolderPlus } from 'twenty-ui/display'; import { LightIconButton } from 'twenty-ui/input'; export const CurrentWorkspaceMemberFavoritesFolders = () => { - const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); + const currentWorkspaceMember = useRecoilValueV2(currentWorkspaceMemberState); const { sortedFavorites: favorites } = useFavorites(); const { favoritesByFolder } = useFavoritesByFolder(); diff --git a/packages/twenty-front/src/modules/favorites/components/CurrentWorkspaceMemberOrphanFavorites.tsx b/packages/twenty-front/src/modules/favorites/components/CurrentWorkspaceMemberOrphanFavorites.tsx index 8d3e30db57..930b20043d 100644 --- a/packages/twenty-front/src/modules/favorites/components/CurrentWorkspaceMemberOrphanFavorites.tsx +++ b/packages/twenty-front/src/modules/favorites/components/CurrentWorkspaceMemberOrphanFavorites.tsx @@ -8,10 +8,10 @@ import { isLocationMatchingFavorite } from '@/favorites/utils/isLocationMatching import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { DraggableItem } from '@/ui/layout/draggable-list/components/DraggableItem'; import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import styled from '@emotion/styled'; import { useContext } from 'react'; import { useLocation } from 'react-router-dom'; -import { useRecoilValue } from 'recoil'; import { IconHeartOff } from 'twenty-ui/display'; import { LightIconButton } from 'twenty-ui/input'; @@ -24,7 +24,7 @@ const StyledOrphanFavoritesContainer = styled.div` `; export const CurrentWorkspaceMemberOrphanFavorites = () => { - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); const { sortedFavorites: favorites } = useFavorites(); const { deleteFavorite } = useDeleteFavorite(); const currentPath = useLocation().pathname; diff --git a/packages/twenty-front/src/modules/favorites/components/FavoritesFolderContent.tsx b/packages/twenty-front/src/modules/favorites/components/FavoritesFolderContent.tsx index d07c73d3e3..0533000023 100644 --- a/packages/twenty-front/src/modules/favorites/components/FavoritesFolderContent.tsx +++ b/packages/twenty-front/src/modules/favorites/components/FavoritesFolderContent.tsx @@ -7,8 +7,8 @@ import { type ProcessedFavorite } from '@/favorites/utils/sortFavorites'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { DraggableItem } from '@/ui/layout/draggable-list/components/DraggableItem'; import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { Droppable } from '@hello-pangea/dnd'; -import { useRecoilValue } from 'recoil'; import { IconHeartOff } from 'twenty-ui/display'; import { LightIconButton } from 'twenty-ui/input'; @@ -23,7 +23,7 @@ export const FavoritesFolderContent = ({ folderId, favorites, }: FavoritesFolderContentProps) => { - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); const { deleteFavorite } = useDeleteFavorite(); return ( diff --git a/packages/twenty-front/src/modules/favorites/hooks/__tests__/useCreateFavorite.test.tsx b/packages/twenty-front/src/modules/favorites/hooks/__tests__/useCreateFavorite.test.tsx index 6001c148aa..fdf2298655 100644 --- a/packages/twenty-front/src/modules/favorites/hooks/__tests__/useCreateFavorite.test.tsx +++ b/packages/twenty-front/src/modules/favorites/hooks/__tests__/useCreateFavorite.test.tsx @@ -1,10 +1,10 @@ import { renderHook, waitFor } from '@testing-library/react'; -import { useSetRecoilState } from 'recoil'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { useCreateFavorite } from '@/favorites/hooks/useCreateFavorite'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems'; import { @@ -45,21 +45,19 @@ const Wrapper = getJestMetadataAndApolloMocksWrapper({ }); describe('useCreateFavorite', () => { - it('should create favorite successfully', async () => { - const { result } = renderHook( - () => { - const setCurrentWorkspaceMember = useSetRecoilState( - currentWorkspaceMemberState, - ); - setCurrentWorkspaceMember(mockWorkspaceMember); - - const setMetadataItems = useSetRecoilState(objectMetadataItemsState); - setMetadataItems(generatedMockObjectMetadataItems); - - return useCreateFavorite(); - }, - { wrapper: Wrapper }, + beforeEach(() => { + jotaiStore.set( + objectMetadataItemsState.atom, + generatedMockObjectMetadataItems, ); + }); + + it('should create favorite successfully', async () => { + jotaiStore.set(currentWorkspaceMemberState.atom, mockWorkspaceMember); + + const { result } = renderHook(() => useCreateFavorite(), { + wrapper: Wrapper, + }); result.current.createFavorite( favoriteTargetObjectRecord, diff --git a/packages/twenty-front/src/modules/favorites/hooks/__tests__/useDeleteFavorite.test.tsx b/packages/twenty-front/src/modules/favorites/hooks/__tests__/useDeleteFavorite.test.tsx index 8d949fdf06..58484f4214 100644 --- a/packages/twenty-front/src/modules/favorites/hooks/__tests__/useDeleteFavorite.test.tsx +++ b/packages/twenty-front/src/modules/favorites/hooks/__tests__/useDeleteFavorite.test.tsx @@ -1,9 +1,9 @@ import { renderHook, waitFor } from '@testing-library/react'; -import { useSetRecoilState } from 'recoil'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { useDeleteFavorite } from '@/favorites/hooks/useDeleteFavorite'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems'; import { @@ -22,21 +22,19 @@ const Wrapper = getJestMetadataAndApolloMocksWrapper({ }); describe('useDeleteFavorite', () => { - it('should delete favorite successfully', async () => { - const { result } = renderHook( - () => { - const setCurrentWorkspaceMember = useSetRecoilState( - currentWorkspaceMemberState, - ); - setCurrentWorkspaceMember(mockWorkspaceMember); - - const setMetadataItems = useSetRecoilState(objectMetadataItemsState); - setMetadataItems(generatedMockObjectMetadataItems); - - return useDeleteFavorite(); - }, - { wrapper: Wrapper }, + beforeEach(() => { + jotaiStore.set( + objectMetadataItemsState.atom, + generatedMockObjectMetadataItems, ); + }); + + it('should delete favorite successfully', async () => { + jotaiStore.set(currentWorkspaceMemberState.atom, mockWorkspaceMember); + + const { result } = renderHook(() => useDeleteFavorite(), { + wrapper: Wrapper, + }); result.current.deleteFavorite(favoriteId); diff --git a/packages/twenty-front/src/modules/favorites/hooks/__tests__/useFavorites.test.tsx b/packages/twenty-front/src/modules/favorites/hooks/__tests__/useFavorites.test.tsx index 2ac9a44ba2..2b1bf5c848 100644 --- a/packages/twenty-front/src/modules/favorites/hooks/__tests__/useFavorites.test.tsx +++ b/packages/twenty-front/src/modules/favorites/hooks/__tests__/useFavorites.test.tsx @@ -1,11 +1,11 @@ import { renderHook } from '@testing-library/react'; -import { useSetRecoilState } from 'recoil'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { useFavorites } from '@/favorites/hooks/useFavorites'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { prefetchFavoritesState } from '@/prefetch/states/prefetchFavoritesState'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems'; import { @@ -19,24 +19,20 @@ const Wrapper = getJestMetadataAndApolloMocksWrapper({ }); describe('useFavorites', () => { - it('should fetch and sort favorites successfully', () => { - const { result } = renderHook( - () => { - const setPrefetchFavorites = useSetRecoilState(prefetchFavoritesState); - setPrefetchFavorites(initialFavorites); - - const setCurrentWorkspaceMember = useSetRecoilState( - currentWorkspaceMemberState, - ); - setCurrentWorkspaceMember(mockWorkspaceMember); - - const setMetadataItems = useSetRecoilState(objectMetadataItemsState); - setMetadataItems(generatedMockObjectMetadataItems); - - return useFavorites(); - }, - { wrapper: Wrapper }, + beforeEach(() => { + jotaiStore.set( + objectMetadataItemsState.atom, + generatedMockObjectMetadataItems, ); + jotaiStore.set(prefetchFavoritesState.atom, initialFavorites); + }); + + it('should fetch and sort favorites successfully', () => { + jotaiStore.set(currentWorkspaceMemberState.atom, mockWorkspaceMember); + + const { result } = renderHook(() => useFavorites(), { + wrapper: Wrapper, + }); expect(result.current.sortedFavorites).toEqual(sortedFavorites); }); diff --git a/packages/twenty-front/src/modules/favorites/hooks/__tests__/useHandleFavoriteDragAndDrop.test.tsx b/packages/twenty-front/src/modules/favorites/hooks/__tests__/useHandleFavoriteDragAndDrop.test.tsx index b25543044c..3baf6190dd 100644 --- a/packages/twenty-front/src/modules/favorites/hooks/__tests__/useHandleFavoriteDragAndDrop.test.tsx +++ b/packages/twenty-front/src/modules/favorites/hooks/__tests__/useHandleFavoriteDragAndDrop.test.tsx @@ -1,7 +1,6 @@ import { type DropResult, type ResponderProvided } from '@hello-pangea/dnd'; import { renderHook, waitFor } from '@testing-library/react'; import { act } from 'react'; -import { useSetRecoilState } from 'recoil'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { FAVORITE_DROPPABLE_IDS } from '@/favorites/constants/FavoriteDroppableIds'; @@ -11,6 +10,7 @@ import { createFolderDroppableId } from '@/favorites/utils/createFolderDroppable import { createFolderHeaderDroppableId } from '@/favorites/utils/createFolderHeaderDroppableId'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { prefetchFavoritesState } from '@/prefetch/states/prefetchFavoritesState'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems'; import { @@ -32,33 +32,27 @@ describe('useHandleFavoriteDragAndDrop', () => { announce: jest.fn(), }; - const setupHook = () => { - return renderHook( - () => { - const setPrefetchFavorites = useSetRecoilState(prefetchFavoritesState); - setPrefetchFavorites(initialFavorites as Favorite[]); - - const setCurrentWorkspaceMember = useSetRecoilState( - currentWorkspaceMemberState, - ); - const setMetadataItems = useSetRecoilState(objectMetadataItemsState); - - setCurrentWorkspaceMember(mockWorkspaceMember); - setMetadataItems(generatedMockObjectMetadataItems); - - return { - hook: useHandleFavoriteDragAndDrop(), - }; - }, - { wrapper: Wrapper }, + beforeEach(() => { + jotaiStore.set( + objectMetadataItemsState.atom, + generatedMockObjectMetadataItems, ); + jotaiStore.set(prefetchFavoritesState.atom, initialFavorites as Favorite[]); + }); + + const setupHook = () => { + jotaiStore.set(currentWorkspaceMemberState.atom, mockWorkspaceMember); + + return renderHook(() => useHandleFavoriteDragAndDrop(), { + wrapper: Wrapper, + }); }; it('should not update when destination is null', () => { const { result } = setupHook(); act(() => { - result.current.hook.handleFavoriteDragAndDrop( + result.current.handleFavoriteDragAndDrop( { source: { index: 0, droppableId: createFolderDroppableId('1') }, destination: null, @@ -78,7 +72,7 @@ describe('useHandleFavoriteDragAndDrop', () => { const folderOneId = createFolderDroppableId('1'); act(() => { - result.current.hook.handleFavoriteDragAndDrop( + result.current.handleFavoriteDragAndDrop( { source: { index: 0, droppableId: folderOneId }, destination: { index: 0, droppableId: folderOneId }, @@ -98,7 +92,7 @@ describe('useHandleFavoriteDragAndDrop', () => { const folderOneId = createFolderDroppableId('1'); act(() => { - result.current.hook.handleFavoriteDragAndDrop( + result.current.handleFavoriteDragAndDrop( { source: { index: 0, droppableId: folderOneId }, destination: { index: 2, droppableId: folderOneId }, @@ -117,7 +111,7 @@ describe('useHandleFavoriteDragAndDrop', () => { const { result } = setupHook(); act(() => { - result.current.hook.handleFavoriteDragAndDrop( + result.current.handleFavoriteDragAndDrop( { source: { index: 0, droppableId: createFolderDroppableId('1') }, destination: { index: 0, droppableId: createFolderDroppableId('2') }, @@ -136,7 +130,7 @@ describe('useHandleFavoriteDragAndDrop', () => { const { result } = setupHook(); act(() => { - result.current.hook.handleFavoriteDragAndDrop( + result.current.handleFavoriteDragAndDrop( { source: { index: 0, droppableId: createFolderDroppableId('1') }, destination: { @@ -158,7 +152,7 @@ describe('useHandleFavoriteDragAndDrop', () => { const { result } = setupHook(); act(() => { - result.current.hook.handleFavoriteDragAndDrop( + result.current.handleFavoriteDragAndDrop( { source: { index: 0, droppableId: createFolderDroppableId('1') }, destination: { diff --git a/packages/twenty-front/src/modules/favorites/hooks/useCreateFavorite.ts b/packages/twenty-front/src/modules/favorites/hooks/useCreateFavorite.ts index 09538b5d12..7493296def 100644 --- a/packages/twenty-front/src/modules/favorites/hooks/useCreateFavorite.ts +++ b/packages/twenty-front/src/modules/favorites/hooks/useCreateFavorite.ts @@ -3,10 +3,10 @@ import { isNavigationMenuItemFolder } from '@/navigation-menu-item/utils/isNavig import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord'; import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; -import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { useCreateNavigationMenuItemMutation } from '~/generated-metadata/graphql'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { usePrefetchedFavoritesData } from './usePrefetchedFavoritesData'; import { usePrefetchedFavoritesFoldersData } from './usePrefetchedFavoritesFoldersData'; @@ -14,7 +14,7 @@ export const useCreateFavorite = () => { const { favorites, currentWorkspaceMemberId } = usePrefetchedFavoritesData(); const { favoriteFolders } = usePrefetchedFavoritesFoldersData(); const { navigationMenuItems } = usePrefetchedNavigationMenuItemsData(); - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); const { createOneRecord: createOneFavorite } = useCreateOneRecord({ objectNameSingular: CoreObjectNameSingular.Favorite, diff --git a/packages/twenty-front/src/modules/favorites/hooks/useFavorites.ts b/packages/twenty-front/src/modules/favorites/hooks/useFavorites.ts index 734032d07e..c077f11d22 100644 --- a/packages/twenty-front/src/modules/favorites/hooks/useFavorites.ts +++ b/packages/twenty-front/src/modules/favorites/hooks/useFavorites.ts @@ -5,22 +5,22 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useMemo } from 'react'; -import { useRecoilValue } from 'recoil'; import { FieldMetadataType } from '~/generated-metadata/graphql'; import { usePrefetchedFavoritesData } from './usePrefetchedFavoritesData'; import { allowRequestsToTwentyIconsState } from '@/client-config/states/allowRequestsToTwentyIcons'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; export const useFavorites = () => { const { favorites } = usePrefetchedFavoritesData(); - const favoriteViewsWithMinimalData = useRecoilValue( + const favoriteViewsWithMinimalData = useRecoilValueV2( favoriteViewsWithMinimalDataSelector, ); - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); const { objectMetadataItem: favoriteObjectMetadataItem } = useObjectMetadataItem({ objectNameSingular: CoreObjectNameSingular.Favorite, }); - const allowRequestsToTwentyIcons = useRecoilValue( + const allowRequestsToTwentyIcons = useRecoilValueV2( allowRequestsToTwentyIconsState, ); const getObjectRecordIdentifierByNameSingular = diff --git a/packages/twenty-front/src/modules/favorites/hooks/useFavoritesByFolder.ts b/packages/twenty-front/src/modules/favorites/hooks/useFavoritesByFolder.ts index 2d432ed002..e352cce640 100644 --- a/packages/twenty-front/src/modules/favorites/hooks/useFavoritesByFolder.ts +++ b/packages/twenty-front/src/modules/favorites/hooks/useFavoritesByFolder.ts @@ -1,7 +1,7 @@ import { sortFavorites } from '@/favorites/utils/sortFavorites'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { coreViewsState } from '@/views/states/coreViewState'; import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView'; -import { useRecoilValue } from 'recoil'; import { useFavoritesMetadata } from './useFavoritesMetadata'; import { usePrefetchedFavoritesData } from './usePrefetchedFavoritesData'; import { usePrefetchedFavoritesFoldersData } from './usePrefetchedFavoritesFoldersData'; @@ -15,7 +15,7 @@ export const useFavoritesByFolder = () => { favoriteRelationFields, } = useFavoritesMetadata(); - const coreViews = useRecoilValue(coreViewsState); + const coreViews = useRecoilValueV2(coreViewsState); const views = coreViews.map(convertCoreViewToView); diff --git a/packages/twenty-front/src/modules/favorites/hooks/useFavoritesMetadata.ts b/packages/twenty-front/src/modules/favorites/hooks/useFavoritesMetadata.ts index 4ccdb8cc26..67bdaced51 100644 --- a/packages/twenty-front/src/modules/favorites/hooks/useFavoritesMetadata.ts +++ b/packages/twenty-front/src/modules/favorites/hooks/useFavoritesMetadata.ts @@ -2,13 +2,13 @@ import { useGetObjectRecordIdentifierByNameSingular } from '@/object-metadata/ho import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; -import { useRecoilValue } from 'recoil'; import { FieldMetadataType } from '~/generated-metadata/graphql'; import { allowRequestsToTwentyIconsState } from '@/client-config/states/allowRequestsToTwentyIcons'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; export const useFavoritesMetadata = () => { - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); - const allowRequestsToTwentyIcons = useRecoilValue( + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); + const allowRequestsToTwentyIcons = useRecoilValueV2( allowRequestsToTwentyIconsState, ); const getObjectRecordIdentifierByNameSingular = diff --git a/packages/twenty-front/src/modules/favorites/hooks/usePrefetchedFavoritesData.ts b/packages/twenty-front/src/modules/favorites/hooks/usePrefetchedFavoritesData.ts index 47caaa8d48..8a751721a4 100644 --- a/packages/twenty-front/src/modules/favorites/hooks/usePrefetchedFavoritesData.ts +++ b/packages/twenty-front/src/modules/favorites/hooks/usePrefetchedFavoritesData.ts @@ -1,7 +1,7 @@ import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { type Favorite } from '@/favorites/types/Favorite'; import { prefetchFavoritesState } from '@/prefetch/states/prefetchFavoritesState'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; type PrefetchedFavoritesData = { favorites: Favorite[]; @@ -10,9 +10,9 @@ type PrefetchedFavoritesData = { }; export const usePrefetchedFavoritesData = (): PrefetchedFavoritesData => { - const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); + const currentWorkspaceMember = useRecoilValueV2(currentWorkspaceMemberState); const currentWorkspaceMemberId = currentWorkspaceMember?.id; - const prefetchFavorites = useRecoilValue(prefetchFavoritesState); + const prefetchFavorites = useRecoilValueV2(prefetchFavoritesState); const favorites = prefetchFavorites.filter( (favorite) => favorite.forWorkspaceMemberId === currentWorkspaceMemberId, diff --git a/packages/twenty-front/src/modules/favorites/hooks/usePrefetchedFavoritesFoldersData.ts b/packages/twenty-front/src/modules/favorites/hooks/usePrefetchedFavoritesFoldersData.ts index 432d94ff30..22c44d7a53 100644 --- a/packages/twenty-front/src/modules/favorites/hooks/usePrefetchedFavoritesFoldersData.ts +++ b/packages/twenty-front/src/modules/favorites/hooks/usePrefetchedFavoritesFoldersData.ts @@ -1,7 +1,7 @@ import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { type FavoriteFolder } from '@/favorites/types/FavoriteFolder'; import { prefetchFavoriteFoldersState } from '@/prefetch/states/prefetchFavoriteFoldersState'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; type PrefetchedFavoritesFoldersData = { favoriteFolders: FavoriteFolder[]; @@ -10,10 +10,12 @@ type PrefetchedFavoritesFoldersData = { export const usePrefetchedFavoritesFoldersData = (): PrefetchedFavoritesFoldersData => { - const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); + const currentWorkspaceMember = useRecoilValueV2( + currentWorkspaceMemberState, + ); const currentWorkspaceMemberId = currentWorkspaceMember?.id; - const prefetchFavoriteFolders = useRecoilValue( + const prefetchFavoriteFolders = useRecoilValueV2( prefetchFavoriteFoldersState, ); diff --git a/packages/twenty-front/src/modules/favorites/hooks/useSortedFavorites.ts b/packages/twenty-front/src/modules/favorites/hooks/useSortedFavorites.ts index 234f2f25cb..6da17b3c75 100644 --- a/packages/twenty-front/src/modules/favorites/hooks/useSortedFavorites.ts +++ b/packages/twenty-front/src/modules/favorites/hooks/useSortedFavorites.ts @@ -1,8 +1,8 @@ import { sortFavorites } from '@/favorites/utils/sortFavorites'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { coreViewsState } from '@/views/states/coreViewState'; import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView'; import { useMemo } from 'react'; -import { useRecoilValue } from 'recoil'; import { useFavoritesMetadata } from './useFavoritesMetadata'; import { usePrefetchedFavoritesData } from './usePrefetchedFavoritesData'; @@ -14,7 +14,7 @@ export const useSortedFavorites = () => { favoriteRelationFields, } = useFavoritesMetadata(); - const coreViews = useRecoilValue(coreViewsState).map(convertCoreViewToView); + const coreViews = useRecoilValueV2(coreViewsState).map(convertCoreViewToView); const favoritesSorted = useMemo(() => { return sortFavorites( diff --git a/packages/twenty-front/src/modules/favorites/hooks/useWorkspaceFavorites.ts b/packages/twenty-front/src/modules/favorites/hooks/useWorkspaceFavorites.ts index 407209b253..c4d22b2015 100644 --- a/packages/twenty-front/src/modules/favorites/hooks/useWorkspaceFavorites.ts +++ b/packages/twenty-front/src/modules/favorites/hooks/useWorkspaceFavorites.ts @@ -1,26 +1,26 @@ +import { allowRequestsToTwentyIconsState } from '@/client-config/states/allowRequestsToTwentyIcons'; import { sortFavorites } from '@/favorites/utils/sortFavorites'; import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems'; import { useGetObjectRecordIdentifierByNameSingular } from '@/object-metadata/hooks/useGetObjectRecordIdentifierByNameSingular'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { coreViewsState } from '@/views/states/coreViewState'; import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView'; import { useMemo } from 'react'; -import { useRecoilValue } from 'recoil'; import { FieldMetadataType } from '~/generated-metadata/graphql'; import { usePrefetchedFavoritesData } from './usePrefetchedFavoritesData'; -import { allowRequestsToTwentyIconsState } from '@/client-config/states/allowRequestsToTwentyIcons'; export const useWorkspaceFavorites = () => { const { workspaceFavorites } = usePrefetchedFavoritesData(); - const coreViews = useRecoilValue(coreViewsState); - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const coreViews = useRecoilValueV2(coreViewsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); const { objectMetadataItem: favoriteObjectMetadataItem } = useObjectMetadataItem({ objectNameSingular: CoreObjectNameSingular.Favorite, }); - const allowRequestsToTwentyIcons = useRecoilValue( + const allowRequestsToTwentyIcons = useRecoilValueV2( allowRequestsToTwentyIconsState, ); const getObjectRecordIdentifierByNameSingular = diff --git a/packages/twenty-front/src/modules/favorites/states/selectors/favoriteViewsWithMinimalDataSelector.ts b/packages/twenty-front/src/modules/favorites/states/selectors/favoriteViewsWithMinimalDataSelector.ts index 64ef7bd914..19f7f17f63 100644 --- a/packages/twenty-front/src/modules/favorites/states/selectors/favoriteViewsWithMinimalDataSelector.ts +++ b/packages/twenty-front/src/modules/favorites/states/selectors/favoriteViewsWithMinimalDataSelector.ts @@ -1,15 +1,14 @@ import { coreViewsState } from '@/views/states/coreViewState'; import { type View } from '@/views/types/View'; import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView'; -import { selector } from 'recoil'; +import { createSelectorV2 } from '@/ui/utilities/state/jotai/utils/createSelectorV2'; -export const favoriteViewsWithMinimalDataSelector = selector< +export const favoriteViewsWithMinimalDataSelector = createSelectorV2< Pick[] >({ key: 'favoriteViewsWithMinimalDataSelector', get: ({ get }) => { const coreViews = get(coreViewsState); - const views = coreViews.map(convertCoreViewToView); return views.map((view) => ({ id: view.id, diff --git a/packages/twenty-front/src/modules/front-components/hooks/useFrontComponentExecutionContext.ts b/packages/twenty-front/src/modules/front-components/hooks/useFrontComponentExecutionContext.ts index ec1a3b2947..ad9062ba97 100644 --- a/packages/twenty-front/src/modules/front-components/hooks/useFrontComponentExecutionContext.ts +++ b/packages/twenty-front/src/modules/front-components/hooks/useFrontComponentExecutionContext.ts @@ -1,4 +1,4 @@ -import { useRecoilValue, useSetRecoilState } from 'recoil'; +import { useSetRecoilState } from 'recoil'; import { type FrontComponentExecutionContext, type FrontComponentHostCommunicationApi, @@ -10,6 +10,7 @@ import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu'; import { useNavigateCommandMenu } from '@/command-menu/hooks/useNavigateCommandMenu'; import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState'; import { useUnmountHeadlessFrontComponent } from '@/front-components/hooks/useUnmountHeadlessFrontComponent'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { useIcons } from 'twenty-ui/display'; import { useNavigateApp } from '~/hooks/useNavigateApp'; @@ -21,7 +22,7 @@ export const useFrontComponentExecutionContext = ({ executionContext: FrontComponentExecutionContext; frontComponentHostCommunicationApi: FrontComponentHostCommunicationApi; } => { - const currentUser = useRecoilValue(currentUserState); + const currentUser = useRecoilValueV2(currentUserState); const navigateApp = useNavigateApp(); const { navigateCommandMenu } = useNavigateCommandMenu(); const setCommandMenuSearchState = useSetRecoilState(commandMenuSearchState); diff --git a/packages/twenty-front/src/modules/information-banner/components/impersonate/InformationBannerIsImpersonating.tsx b/packages/twenty-front/src/modules/information-banner/components/impersonate/InformationBannerIsImpersonating.tsx index fe0137afa5..fa2dd208d7 100644 --- a/packages/twenty-front/src/modules/information-banner/components/impersonate/InformationBannerIsImpersonating.tsx +++ b/packages/twenty-front/src/modules/information-banner/components/impersonate/InformationBannerIsImpersonating.tsx @@ -3,13 +3,13 @@ import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMembe import { isImpersonatingState } from '@/auth/states/isImpersonatingState'; import { InformationBanner } from '@/information-banner/components/InformationBanner'; import { t } from '@lingui/core/macro'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { isDefined } from 'twenty-shared/utils'; import { IconLogout } from 'twenty-ui/display'; export const InformationBannerIsImpersonating = () => { - const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); - const isImpersonating = useRecoilValue(isImpersonatingState); + const currentWorkspaceMember = useRecoilValueV2(currentWorkspaceMemberState); + const isImpersonating = useRecoilValueV2(isImpersonatingState); const { signOut } = useAuth(); diff --git a/packages/twenty-front/src/modules/information-banner/hooks/useAccountToReconnect.ts b/packages/twenty-front/src/modules/information-banner/hooks/useAccountToReconnect.ts index 5aafd71460..9387c8b423 100644 --- a/packages/twenty-front/src/modules/information-banner/hooks/useAccountToReconnect.ts +++ b/packages/twenty-front/src/modules/information-banner/hooks/useAccountToReconnect.ts @@ -3,10 +3,10 @@ import { currentUserState } from '@/auth/states/currentUserState'; import { type InformationBannerKeys } from '@/information-banner/types/InformationBannerKeys'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; export const useAccountToReconnect = (key: InformationBannerKeys) => { - const currentUser = useRecoilValue(currentUserState); + const currentUser = useRecoilValueV2(currentUserState); const userVars = currentUser?.userVars; diff --git a/packages/twenty-front/src/modules/localization/hooks/__tests__/useDateTimeFormat.test.tsx b/packages/twenty-front/src/modules/localization/hooks/__tests__/useDateTimeFormat.test.tsx index eee042944b..3469852d94 100644 --- a/packages/twenty-front/src/modules/localization/hooks/__tests__/useDateTimeFormat.test.tsx +++ b/packages/twenty-front/src/modules/localization/hooks/__tests__/useDateTimeFormat.test.tsx @@ -1,4 +1,5 @@ import { renderHook } from '@testing-library/react'; +import { Provider as JotaiProvider } from 'jotai'; import { type ReactNode } from 'react'; import { RecoilRoot } from 'recoil'; @@ -6,6 +7,7 @@ import { DateFormat } from '@/localization/constants/DateFormat'; import { TimeFormat } from '@/localization/constants/TimeFormat'; import { useDateTimeFormat } from '@/localization/hooks/useDateTimeFormat'; import { workspaceMemberFormatPreferencesState } from '@/localization/states/workspaceMemberFormatPreferencesState'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { CalendarStartDay } from 'twenty-shared/constants'; const mockPreferences = { @@ -17,16 +19,16 @@ const mockPreferences = { }; const Wrapper = ({ children }: { children: ReactNode }) => ( - { - set(workspaceMemberFormatPreferencesState, mockPreferences); - }} - > - {children} - + + {children} + ); describe('useDateTimeFormat', () => { + beforeEach(() => { + jotaiStore.set(workspaceMemberFormatPreferencesState.atom, mockPreferences); + }); + it('should be a function', () => { expect(typeof useDateTimeFormat).toBe('function'); }); @@ -45,20 +47,14 @@ describe('useDateTimeFormat', () => { }); it('should return updated values when preferences change', () => { + jotaiStore.set(workspaceMemberFormatPreferencesState.atom, { + ...mockPreferences, + timeZone: 'Europe/London', + dateFormat: DateFormat.DAY_FIRST, + }); + const { result } = renderHook(() => useDateTimeFormat(), { - wrapper: ({ children }: { children: ReactNode }) => ( - { - set(workspaceMemberFormatPreferencesState, { - ...mockPreferences, - timeZone: 'Europe/London', - dateFormat: DateFormat.DAY_FIRST, - }); - }} - > - {children} - - ), + wrapper: Wrapper, }); expect(result.current.timeZone).toBe('Europe/London'); diff --git a/packages/twenty-front/src/modules/localization/hooks/__tests__/useFormatPreferences.test.tsx b/packages/twenty-front/src/modules/localization/hooks/__tests__/useFormatPreferences.test.tsx index 1763696ba9..0ee592c5b0 100644 --- a/packages/twenty-front/src/modules/localization/hooks/__tests__/useFormatPreferences.test.tsx +++ b/packages/twenty-front/src/modules/localization/hooks/__tests__/useFormatPreferences.test.tsx @@ -1,6 +1,7 @@ import { act, renderHook } from '@testing-library/react'; import { type ReactNode } from 'react'; -import { RecoilRoot, type MutableSnapshot } from 'recoil'; +import { RecoilRoot } from 'recoil'; +import { Provider as JotaiProvider } from 'jotai'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { DateFormat } from '@/localization/constants/DateFormat'; @@ -8,6 +9,7 @@ import { NumberFormat } from '@/localization/constants/NumberFormat'; import { TimeFormat } from '@/localization/constants/TimeFormat'; import { useFormatPreferences } from '@/localization/hooks/useFormatPreferences'; import { workspaceMemberFormatPreferencesState } from '@/localization/states/workspaceMemberFormatPreferencesState'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { detectCalendarStartDay } from '@/localization/utils/detection/detectCalendarStartDay'; import { detectDateFormat } from '@/localization/utils/detection/detectDateFormat'; import { detectNumberFormat } from '@/localization/utils/detection/detectNumberFormat'; @@ -73,19 +75,30 @@ const mockInitialPreferences = { calendarStartDay: CalendarStartDay.MONDAY, }; -const Wrapper = ({ children }: { children: ReactNode }) => { - const initializeState = ({ set }: MutableSnapshot) => { - set(currentWorkspaceMemberState, mockCurrentWorkspaceMember); - set(workspaceMemberFormatPreferencesState, mockInitialPreferences); +const createWrapper = + () => + ({ children }: { children: ReactNode }) => { + return ( + + {children} + + ); }; - return {children}; -}; - describe('useFormatPreferences', () => { beforeEach(() => { jest.clearAllMocks(); + jotaiStore.set( + workspaceMemberFormatPreferencesState.atom, + mockInitialPreferences, + ); + + jotaiStore.set( + currentWorkspaceMemberState.atom, + mockCurrentWorkspaceMember, + ); + mockUseUpdateOneRecord.mockReturnValue({ updateOneRecord: mockUpdateOneRecord, }); @@ -106,7 +119,7 @@ describe('useFormatPreferences', () => { it('should return format preferences and update functions', () => { const { result } = renderHook(() => useFormatPreferences(), { - wrapper: Wrapper, + wrapper: createWrapper(), }); expect(result.current).toHaveProperty('formatPreferences'); @@ -123,7 +136,7 @@ describe('useFormatPreferences', () => { it('should return current format preferences', () => { const { result } = renderHook(() => useFormatPreferences(), { - wrapper: Wrapper, + wrapper: createWrapper(), }); expect(result.current.formatPreferences).toEqual(mockInitialPreferences); @@ -131,7 +144,7 @@ describe('useFormatPreferences', () => { it('should update single format preference successfully', async () => { const { result } = renderHook(() => useFormatPreferences(), { - wrapper: Wrapper, + wrapper: createWrapper(), }); const newTimeZone = 'Europe/London'; @@ -152,7 +165,7 @@ describe('useFormatPreferences', () => { it('should handle SYSTEM values by detecting actual format', async () => { const { result } = renderHook(() => useFormatPreferences(), { - wrapper: Wrapper, + wrapper: createWrapper(), }); mockDetectTimeZone.mockReturnValue('America/Chicago'); @@ -174,7 +187,7 @@ describe('useFormatPreferences', () => { it('should update multiple format preferences successfully', async () => { const { result } = renderHook(() => useFormatPreferences(), { - wrapper: Wrapper, + wrapper: createWrapper(), }); const updates = { @@ -198,17 +211,10 @@ describe('useFormatPreferences', () => { }); it('should not update preferences when user is not logged in', async () => { - const { result } = renderHook(() => useFormatPreferences(), { - wrapper: ({ children }: { children: ReactNode }) => { - const initializeState = ({ set }: MutableSnapshot) => { - set(currentWorkspaceMemberState, null); - set(workspaceMemberFormatPreferencesState, mockInitialPreferences); - }; + jotaiStore.set(currentWorkspaceMemberState.atom, null); - return ( - {children} - ); - }, + const { result } = renderHook(() => useFormatPreferences(), { + wrapper: createWrapper(), }); await act(async () => { @@ -220,7 +226,7 @@ describe('useFormatPreferences', () => { it('should handle update errors gracefully', async () => { const { result } = renderHook(() => useFormatPreferences(), { - wrapper: Wrapper, + wrapper: createWrapper(), }); const error = new Error('Update failed'); @@ -238,7 +244,7 @@ describe('useFormatPreferences', () => { it('should initialize format preferences from workspace member', () => { const { result } = renderHook(() => useFormatPreferences(), { - wrapper: Wrapper, + wrapper: createWrapper(), }); act(() => { @@ -250,7 +256,7 @@ describe('useFormatPreferences', () => { it('should not initialize format preferences when workspace member is null', () => { const { result } = renderHook(() => useFormatPreferences(), { - wrapper: Wrapper, + wrapper: createWrapper(), }); act(() => { diff --git a/packages/twenty-front/src/modules/localization/hooks/__tests__/useNumberFormat.test.tsx b/packages/twenty-front/src/modules/localization/hooks/__tests__/useNumberFormat.test.tsx index 64093d3cb7..032bfc5874 100644 --- a/packages/twenty-front/src/modules/localization/hooks/__tests__/useNumberFormat.test.tsx +++ b/packages/twenty-front/src/modules/localization/hooks/__tests__/useNumberFormat.test.tsx @@ -1,9 +1,11 @@ import { renderHook } from '@testing-library/react'; +import { Provider as JotaiProvider } from 'jotai'; import { type ReactNode } from 'react'; import { RecoilRoot } from 'recoil'; import { useNumberFormat } from '@/localization/hooks/useNumberFormat'; import { workspaceMemberFormatPreferencesState } from '@/localization/states/workspaceMemberFormatPreferencesState'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { formatNumber as utilFormatNumber } from '~/utils/format/formatNumber'; jest.mock('~/utils/format/formatNumber'); @@ -13,24 +15,21 @@ const mockUtilFormatNumber = utilFormatNumber as jest.MockedFunction< >; const Wrapper = ({ children }: { children: ReactNode }) => ( - { - set(workspaceMemberFormatPreferencesState, { - timeZone: 'UTC', - dateFormat: 'MM/dd/yyyy' as any, - timeFormat: 'HH:mm' as any, - numberFormat: '1,000.00' as any, - calendarStartDay: 'MONDAY' as any, - }); - }} - > - {children} - + + {children} + ); describe('useNumberFormat', () => { beforeEach(() => { jest.clearAllMocks(); + jotaiStore.set(workspaceMemberFormatPreferencesState.atom, { + timeZone: 'UTC', + dateFormat: 'MM/dd/yyyy' as any, + timeFormat: 'HH:mm' as any, + numberFormat: '1,000.00' as any, + calendarStartDay: 'MONDAY' as any, + }); }); it('should be a function', () => { diff --git a/packages/twenty-front/src/modules/localization/hooks/useDateTimeFormat.ts b/packages/twenty-front/src/modules/localization/hooks/useDateTimeFormat.ts index 4f0cef9798..6fd6168747 100644 --- a/packages/twenty-front/src/modules/localization/hooks/useDateTimeFormat.ts +++ b/packages/twenty-front/src/modules/localization/hooks/useDateTimeFormat.ts @@ -1,11 +1,10 @@ -import { useRecoilValue } from 'recoil'; - import { workspaceMemberFormatPreferencesState } from '@/localization/states/workspaceMemberFormatPreferencesState'; import { resolveDateFormat } from '@/localization/utils/resolveDateFormat'; import { resolveTimeFormat } from '@/localization/utils/resolveTimeFormat'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; export const useDateTimeFormat = () => { - const workspaceMemberFormatPreferences = useRecoilValue( + const workspaceMemberFormatPreferences = useRecoilValueV2( workspaceMemberFormatPreferencesState, ); diff --git a/packages/twenty-front/src/modules/localization/hooks/useFormatPreferences.ts b/packages/twenty-front/src/modules/localization/hooks/useFormatPreferences.ts index cf2e0abd5f..f6dad1f035 100644 --- a/packages/twenty-front/src/modules/localization/hooks/useFormatPreferences.ts +++ b/packages/twenty-front/src/modules/localization/hooks/useFormatPreferences.ts @@ -1,5 +1,4 @@ import { useCallback } from 'react'; -import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { DateFormat } from '@/localization/constants/DateFormat'; @@ -18,6 +17,9 @@ import { getFormatPreferencesFromWorkspaceMember } from '@/localization/utils/fo import { getWorkspaceMemberUpdateFromFormatPreferences } from '@/localization/utils/format-preferences/getWorkspaceMemberUpdateFromFormatPreferences'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; +import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; +import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; import { CalendarStartDay } from 'twenty-shared/constants'; import { logError } from '~/utils/logError'; @@ -27,9 +29,9 @@ export const useFormatPreferences = () => { const [ workspaceMemberFormatPreferences, setWorkspaceMemberFormatPreferences, - ] = useRecoilState(workspaceMemberFormatPreferencesState); - const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); - const setCurrentWorkspaceMember = useSetRecoilState( + ] = useRecoilStateV2(workspaceMemberFormatPreferencesState); + const currentWorkspaceMember = useRecoilValueV2(currentWorkspaceMemberState); + const setCurrentWorkspaceMember = useSetRecoilStateV2( currentWorkspaceMemberState, ); diff --git a/packages/twenty-front/src/modules/localization/hooks/useInitializeFormatPreferences.ts b/packages/twenty-front/src/modules/localization/hooks/useInitializeFormatPreferences.ts index bd464f7dc5..d971a0244e 100644 --- a/packages/twenty-front/src/modules/localization/hooks/useInitializeFormatPreferences.ts +++ b/packages/twenty-front/src/modules/localization/hooks/useInitializeFormatPreferences.ts @@ -1,12 +1,12 @@ import { useCallback } from 'react'; -import { useSetRecoilState } from 'recoil'; import { type CurrentWorkspaceMember } from '@/auth/states/currentWorkspaceMemberState'; import { workspaceMemberFormatPreferencesState } from '@/localization/states/workspaceMemberFormatPreferencesState'; import { getFormatPreferencesFromWorkspaceMember } from '@/localization/utils/format-preferences/getFormatPreferencesFromWorkspaceMember'; +import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; export const useInitializeFormatPreferences = () => { - const setWorkspaceMemberFormatPreferences = useSetRecoilState( + const setWorkspaceMemberFormatPreferences = useSetRecoilStateV2( workspaceMemberFormatPreferencesState, ); diff --git a/packages/twenty-front/src/modules/localization/hooks/useNumberFormat.ts b/packages/twenty-front/src/modules/localization/hooks/useNumberFormat.ts index 5b03ad058a..de18f84e97 100644 --- a/packages/twenty-front/src/modules/localization/hooks/useNumberFormat.ts +++ b/packages/twenty-front/src/modules/localization/hooks/useNumberFormat.ts @@ -1,14 +1,14 @@ import { useMemo } from 'react'; -import { useRecoilValue } from 'recoil'; import { workspaceMemberFormatPreferencesState } from '@/localization/states/workspaceMemberFormatPreferencesState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { formatNumber as utilFormatNumber, type FormatNumberOptions, } from '~/utils/format/formatNumber'; export const useNumberFormat = () => { - const workspaceMemberFormatPreferences = useRecoilValue( + const workspaceMemberFormatPreferences = useRecoilValueV2( workspaceMemberFormatPreferencesState, ); diff --git a/packages/twenty-front/src/modules/localization/states/workspaceMemberFormatPreferencesState.ts b/packages/twenty-front/src/modules/localization/states/workspaceMemberFormatPreferencesState.ts index 379f0e3959..59847ad96d 100644 --- a/packages/twenty-front/src/modules/localization/states/workspaceMemberFormatPreferencesState.ts +++ b/packages/twenty-front/src/modules/localization/states/workspaceMemberFormatPreferencesState.ts @@ -6,8 +6,8 @@ import { detectDateFormat } from '@/localization/utils/detection/detectDateForma import { detectNumberFormat } from '@/localization/utils/detection/detectNumberFormat'; import { detectTimeFormat } from '@/localization/utils/detection/detectTimeFormat'; import { detectTimeZone } from '@/localization/utils/detection/detectTimeZone'; +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; import { CalendarStartDay } from 'twenty-shared/constants'; -import { createState } from '@/ui/utilities/state/utils/createState'; export type WorkspaceMemberFormatPreferences = { timeZone: string; @@ -18,7 +18,7 @@ export type WorkspaceMemberFormatPreferences = { }; export const workspaceMemberFormatPreferencesState = - createState({ + createStateV2({ key: 'workspaceMemberFormatPreferencesState', defaultValue: { timeZone: detectTimeZone(), diff --git a/packages/twenty-front/src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItemFolders.tsx b/packages/twenty-front/src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItemFolders.tsx index fc02bab9a0..c0adb04aac 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItemFolders.tsx +++ b/packages/twenty-front/src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItemFolders.tsx @@ -1,10 +1,8 @@ +import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { useLingui } from '@lingui/react/macro'; -import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { IconFolderPlus } from 'twenty-ui/display'; import { LightIconButton } from 'twenty-ui/input'; - -import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { CurrentWorkspaceMemberOrphanNavigationMenuItems } from '@/navigation-menu-item/components/CurrentWorkspaceMemberOrphanNavigationMenuItems'; import { NavigationMenuItemFolders } from '@/navigation-menu-item/components/NavigationMenuItemFolders'; import { NavigationMenuItemSkeletonLoader } from '@/navigation-menu-item/components/NavigationMenuItemSkeletonLoader'; @@ -19,9 +17,10 @@ import { NavigationDrawerSectionTitle } from '@/ui/navigation/navigation-drawer/ import { useNavigationSection } from '@/ui/navigation/navigation-drawer/hooks/useNavigationSection'; import { isNavigationSectionOpenFamilyState } from '@/ui/navigation/navigation-drawer/states/isNavigationSectionOpenFamilyState'; import { useFamilyRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilyRecoilValueV2'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; export const CurrentWorkspaceMemberNavigationMenuItemFolders = () => { - const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); + const currentWorkspaceMember = useRecoilValueV2(currentWorkspaceMemberState); const { navigationMenuItemsSorted } = useSortedNavigationMenuItems(); const { userNavigationMenuItemsByFolder } = useNavigationMenuItemsByFolder(); diff --git a/packages/twenty-front/src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItems.tsx b/packages/twenty-front/src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItems.tsx index b03976e275..75f3221b1e 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItems.tsx +++ b/packages/twenty-front/src/modules/navigation-menu-item/components/CurrentWorkspaceMemberNavigationMenuItems.tsx @@ -7,7 +7,6 @@ import { NavigationSections } from '@/navigation-menu-item/constants/NavigationS import { useIsDropDisabledForSection } from '@/navigation-menu-item/hooks/useIsDropDisabledForSection'; import { createPortal } from 'react-dom'; import { useLocation, useNavigate } from 'react-router-dom'; -import { useRecoilValue } from 'recoil'; import { IconFolder, IconFolderOpen, IconHeartOff } from 'twenty-ui/display'; import { getNavigationMenuItemIconColors } from '@/navigation-menu-item/utils/getNavigationMenuItemIconColors'; @@ -31,6 +30,7 @@ import { getNavigationMenuItemSecondaryLabel } from '@/navigation-menu-item/util import { isLocationMatchingNavigationMenuItem } from '@/navigation-menu-item/utils/isLocationMatchingNavigationMenuItem'; import { type ProcessedNavigationMenuItem } from '@/navigation-menu-item/utils/sortNavigationMenuItems'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { DraggableItem } from '@/ui/layout/draggable-list/components/DraggableItem'; import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown'; import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState'; @@ -66,7 +66,7 @@ export const CurrentWorkspaceMemberNavigationMenuItems = ({ const { t } = useLingui(); const theme = useTheme(); const iconColors = getNavigationMenuItemIconColors(theme); - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); const location = useLocation(); const navigate = useNavigate(); const currentPath = location.pathname; diff --git a/packages/twenty-front/src/modules/navigation-menu-item/components/CurrentWorkspaceMemberOrphanNavigationMenuItems.tsx b/packages/twenty-front/src/modules/navigation-menu-item/components/CurrentWorkspaceMemberOrphanNavigationMenuItems.tsx index 9e29d2a656..efe9ac15f7 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/components/CurrentWorkspaceMemberOrphanNavigationMenuItems.tsx +++ b/packages/twenty-front/src/modules/navigation-menu-item/components/CurrentWorkspaceMemberOrphanNavigationMenuItems.tsx @@ -1,7 +1,6 @@ import styled from '@emotion/styled'; import { useContext } from 'react'; import { useLocation } from 'react-router-dom'; -import { useRecoilValue } from 'recoil'; import { IconHeartOff } from 'twenty-ui/display'; import { LightIconButton } from 'twenty-ui/input'; @@ -16,6 +15,7 @@ import { useSortedNavigationMenuItems } from '@/navigation-menu-item/hooks/useSo import { getNavigationMenuItemSecondaryLabel } from '@/navigation-menu-item/utils/getNavigationMenuItemSecondaryLabel'; import { isLocationMatchingNavigationMenuItem } from '@/navigation-menu-item/utils/isLocationMatchingNavigationMenuItem'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { DraggableItem } from '@/ui/layout/draggable-list/components/DraggableItem'; import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem'; @@ -28,7 +28,7 @@ const StyledOrphanNavigationMenuItemsContainer = styled.div` `; export const CurrentWorkspaceMemberOrphanNavigationMenuItems = () => { - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); const { navigationMenuItemsSorted } = useSortedNavigationMenuItems(); const { deleteNavigationMenuItem } = useDeleteNavigationMenuItem(); const currentPath = useLocation().pathname; diff --git a/packages/twenty-front/src/modules/navigation-menu-item/components/NavigationMenuItemDragProvider.tsx b/packages/twenty-front/src/modules/navigation-menu-item/components/NavigationMenuItemDragProvider.tsx deleted file mode 100644 index 95d1298cd7..0000000000 --- a/packages/twenty-front/src/modules/navigation-menu-item/components/NavigationMenuItemDragProvider.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { - DragDropContext, - type DragStart, - type DropResult, - type ResponderProvided, -} from '@hello-pangea/dnd'; -import { type ReactNode, useState } from 'react'; - -import { NavigationMenuItemDragContext } from '@/navigation-menu-item/contexts/NavigationMenuItemDragContext'; -import { useHandleNavigationMenuItemDragAndDrop } from '@/navigation-menu-item/hooks/useHandleNavigationMenuItemDragAndDrop'; - -type NavigationMenuItemDragProviderProps = { - children: ReactNode; -}; - -export const NavigationMenuItemDragProvider = ({ - children, -}: NavigationMenuItemDragProviderProps) => { - const [isDragging, setIsDragging] = useState(false); - const { handleNavigationMenuItemDragAndDrop } = - useHandleNavigationMenuItemDragAndDrop(); - - const handleDragStart = (_: DragStart) => { - setIsDragging(true); - }; - - const handleDragEnd = (result: DropResult, provided: ResponderProvided) => { - setIsDragging(false); - handleNavigationMenuItemDragAndDrop(result, provided); - }; - - return ( - - - {children} - - - ); -}; diff --git a/packages/twenty-front/src/modules/navigation-menu-item/components/NavigationMenuItemFolderContent.tsx b/packages/twenty-front/src/modules/navigation-menu-item/components/NavigationMenuItemFolderContent.tsx index cd3d316eb6..949cc6ab93 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/components/NavigationMenuItemFolderContent.tsx +++ b/packages/twenty-front/src/modules/navigation-menu-item/components/NavigationMenuItemFolderContent.tsx @@ -1,5 +1,4 @@ import { Droppable } from '@hello-pangea/dnd'; -import { useRecoilValue } from 'recoil'; import { IconHeartOff } from 'twenty-ui/display'; import { LightIconButton } from 'twenty-ui/input'; @@ -9,6 +8,7 @@ import { useDeleteNavigationMenuItem } from '@/navigation-menu-item/hooks/useDel import { getNavigationMenuItemSecondaryLabel } from '@/navigation-menu-item/utils/getNavigationMenuItemSecondaryLabel'; import { type ProcessedNavigationMenuItem } from '@/navigation-menu-item/utils/sortNavigationMenuItems'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { DraggableItem } from '@/ui/layout/draggable-list/components/DraggableItem'; import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem'; @@ -23,7 +23,7 @@ export const NavigationMenuItemFolderContent = ({ folderId, navigationMenuItems, }: NavigationMenuItemFolderContentProps) => { - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); const { deleteNavigationMenuItem } = useDeleteNavigationMenuItem(); return ( diff --git a/packages/twenty-front/src/modules/navigation-menu-item/components/NavigationMenuItemIcon.tsx b/packages/twenty-front/src/modules/navigation-menu-item/components/NavigationMenuItemIcon.tsx index fbf75e8754..e81a2a06aa 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/components/NavigationMenuItemIcon.tsx +++ b/packages/twenty-front/src/modules/navigation-menu-item/components/NavigationMenuItemIcon.tsx @@ -1,5 +1,4 @@ import { useTheme } from '@emotion/react'; -import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { Avatar, useIcons } from 'twenty-ui/display'; import { FeatureFlagKey } from '~/generated-metadata/graphql'; @@ -13,6 +12,7 @@ import { useGetStandardObjectIcon } from '@/object-metadata/hooks/useGetStandard import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { ViewKey } from '@/views/types/ViewKey'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; export const NavigationMenuItemIcon = ({ navigationMenuItem, @@ -24,7 +24,7 @@ export const NavigationMenuItemIcon = ({ const isNavigationMenuItemEditingEnabled = useIsFeatureEnabled( FeatureFlagKey.IS_NAVIGATION_MENU_ITEM_EDITING_ENABLED, ); - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); const { Icon: StandardIcon, IconColor } = useGetStandardObjectIcon( navigationMenuItem.objectNameSingular || '', ); diff --git a/packages/twenty-front/src/modules/navigation-menu-item/components/WorkspaceNavigationMenuItemFolderDragClone.tsx b/packages/twenty-front/src/modules/navigation-menu-item/components/WorkspaceNavigationMenuItemFolderDragClone.tsx index b37745214a..630218d417 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/components/WorkspaceNavigationMenuItemFolderDragClone.tsx +++ b/packages/twenty-front/src/modules/navigation-menu-item/components/WorkspaceNavigationMenuItemFolderDragClone.tsx @@ -4,13 +4,13 @@ import { type DraggableRubric, type DraggableStateSnapshot, } from '@hello-pangea/dnd'; -import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { NavigationMenuItemIcon } from '@/navigation-menu-item/components/NavigationMenuItemIcon'; import { getNavigationMenuItemSecondaryLabel } from '@/navigation-menu-item/utils/getNavigationMenuItemSecondaryLabel'; import { type ProcessedNavigationMenuItem } from '@/navigation-menu-item/utils/sortNavigationMenuItems'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { NavigationDrawerSubItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSubItem'; import { getNavigationSubItemLeftAdornment } from '@/ui/navigation/navigation-drawer/utils/getNavigationSubItemLeftAdornment'; import { ViewKey } from '@/views/types/ViewKey'; @@ -33,7 +33,7 @@ export const WorkspaceNavigationMenuItemFolderDragClone = ({ selectedNavigationMenuItemIndex, }: WorkspaceNavigationMenuItemFolderDragCloneProps) => { const theme = useTheme(); - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); const navigationMenuItem = navigationMenuItems[rubric.source.index]; if (!isDefined(navigationMenuItem)) { diff --git a/packages/twenty-front/src/modules/navigation-menu-item/components/WorkspaceNavigationMenuItems.tsx b/packages/twenty-front/src/modules/navigation-menu-item/components/WorkspaceNavigationMenuItems.tsx index 62031e5bc0..a32751dbf5 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/components/WorkspaceNavigationMenuItems.tsx +++ b/packages/twenty-front/src/modules/navigation-menu-item/components/WorkspaceNavigationMenuItems.tsx @@ -1,7 +1,7 @@ import styled from '@emotion/styled'; import { useLingui } from '@lingui/react/macro'; import { useNavigate } from 'react-router-dom'; -import { useRecoilCallback } from 'recoil'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { IconLink, IconPlus, IconTool, useIcons } from 'twenty-ui/display'; import { LightIconButton } from 'twenty-ui/input'; import { FeatureFlagKey } from '~/generated-metadata/graphql'; @@ -42,23 +42,19 @@ const StyledRightIconsContainer = styled.div` export const WorkspaceNavigationMenuItems = () => { const items = useWorkspaceSectionItems(); const store = useStore(); - const enterEditMode = useRecoilCallback( - ({ snapshot }) => - () => { - const prefetchNavigationMenuItems = snapshot - .getLoadable(prefetchNavigationMenuItemsState) - .getValue(); - const workspaceNavigationMenuItems = filterWorkspaceNavigationMenuItems( - prefetchNavigationMenuItems, - ); - store.set( - navigationMenuItemsDraftStateV2.atom, - workspaceNavigationMenuItems, - ); - store.set(isNavigationMenuInEditModeStateV2.atom, true); - }, - [store], - ); + const enterEditMode = () => { + const prefetchNavigationMenuItems = jotaiStore.get( + prefetchNavigationMenuItemsState.atom, + ); + const workspaceNavigationMenuItems = filterWorkspaceNavigationMenuItems( + prefetchNavigationMenuItems, + ); + store.set( + navigationMenuItemsDraftStateV2.atom, + workspaceNavigationMenuItems, + ); + store.set(isNavigationMenuInEditModeStateV2.atom, true); + }; const isNavigationMenuItemEditingEnabled = useIsFeatureEnabled( FeatureFlagKey.IS_NAVIGATION_MENU_ITEM_EDITING_ENABLED, ); diff --git a/packages/twenty-front/src/modules/navigation-menu-item/components/WorkspaceNavigationMenuItemsFolder.tsx b/packages/twenty-front/src/modules/navigation-menu-item/components/WorkspaceNavigationMenuItemsFolder.tsx index edf3d519aa..4a3932d94b 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/components/WorkspaceNavigationMenuItemsFolder.tsx +++ b/packages/twenty-front/src/modules/navigation-menu-item/components/WorkspaceNavigationMenuItemsFolder.tsx @@ -7,7 +7,6 @@ import { isDefined } from 'twenty-shared/utils'; import { useIsDropDisabledForSection } from '@/navigation-menu-item/hooks/useIsDropDisabledForSection'; import { useLocation, useNavigate } from 'react-router-dom'; -import { useRecoilValue } from 'recoil'; import { IconChevronDown, IconChevronRight, useIcons } from 'twenty-ui/display'; import { AnimatedExpandableContainer } from 'twenty-ui/layout'; import { useIsMobile } from 'twenty-ui/utilities'; @@ -27,6 +26,7 @@ import { getObjectMetadataForNavigationMenuItem } from '@/navigation-menu-item/u import { isLocationMatchingNavigationMenuItem } from '@/navigation-menu-item/utils/isLocationMatchingNavigationMenuItem'; import { type ProcessedNavigationMenuItem } from '@/navigation-menu-item/utils/sortNavigationMenuItems'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { DraggableItem } from '@/ui/layout/draggable-list/components/DraggableItem'; import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem'; import { NavigationDrawerItemsCollapsableContainer } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItemsCollapsableContainer'; @@ -98,8 +98,8 @@ export const WorkspaceNavigationMenuItemsFolder = ({ ); const iconColors = getNavigationMenuItemIconColors(theme); const FolderIcon = getIcon(folderIconKey ?? FOLDER_ICON_DEFAULT); - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); - const coreViews = useRecoilValue(coreViewsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); + const coreViews = useRecoilValueV2(coreViewsState); const views = coreViews.map(convertCoreViewToView); const location = useLocation(); const navigate = useNavigate(); diff --git a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useAddRecordToNavigationMenuDraft.ts b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useAddRecordToNavigationMenuDraft.ts index 35b3ada9d6..7a50fe1801 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useAddRecordToNavigationMenuDraft.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useAddRecordToNavigationMenuDraft.ts @@ -1,4 +1,3 @@ -import { useRecoilValue } from 'recoil'; import { v4 } from 'uuid'; import { isDefined } from 'twenty-shared/utils'; import type { NavigationMenuItem } from '~/generated-metadata/graphql'; @@ -6,6 +5,7 @@ import type { NavigationMenuItem } from '~/generated-metadata/graphql'; import { navigationMenuItemsDraftStateV2 } from '@/navigation-menu-item/states/navigationMenuItemsDraftStateV2'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { computeInsertIndexAndPosition } from '@/navigation-menu-item/utils/computeInsertIndexAndPosition'; type SearchRecord = { @@ -23,7 +23,7 @@ export const useAddRecordToNavigationMenuDraft = () => { const setNavigationMenuItemsDraft = useSetRecoilStateV2( navigationMenuItemsDraftStateV2, ); - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); const addRecordToDraft = ( searchRecord: SearchRecordWithOptionalMetadataId, diff --git a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useCreateNavigationMenuItem.ts b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useCreateNavigationMenuItem.ts index 2b7c2378d0..dafde10510 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useCreateNavigationMenuItem.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useCreateNavigationMenuItem.ts @@ -1,15 +1,15 @@ -import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { useCreateNavigationMenuItemMutation } from '~/generated-metadata/graphql'; import { usePrefetchedNavigationMenuItemsData } from '@/navigation-menu-item/hooks/usePrefetchedNavigationMenuItemsData'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; export const useCreateNavigationMenuItem = () => { const { navigationMenuItems, currentWorkspaceMemberId } = usePrefetchedNavigationMenuItemsData(); - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); const [createNavigationMenuItemMutation] = useCreateNavigationMenuItemMutation({ diff --git a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useHandleAddToNavigationDrop.ts b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useHandleAddToNavigationDrop.ts index fb9598bf33..82268046cd 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useHandleAddToNavigationDrop.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useHandleAddToNavigationDrop.ts @@ -1,6 +1,6 @@ import type { DropResult, ResponderProvided } from '@hello-pangea/dnd'; import { t } from '@lingui/core/macro'; -import { useRecoilCallback, useRecoilValue } from 'recoil'; +import { useRecoilCallback } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { IconFolder, IconLink, useIcons } from 'twenty-ui/display'; @@ -40,7 +40,7 @@ export const useHandleAddToNavigationDrop = () => { const { openNavigationMenuItemInCommandMenu } = useOpenNavigationMenuItemInCommandMenu(); const { objectMetadataItems } = useObjectMetadataItems(); - const coreViews = useRecoilValue(coreViewsState); + const coreViews = useRecoilValueV2(coreViewsState); const { getIcon } = useIcons(); const setSelectedNavigationMenuItemInEditMode = useSetRecoilStateV2( selectedNavigationMenuItemInEditModeStateV2, diff --git a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useNavigationMenuItemsByFolder.ts b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useNavigationMenuItemsByFolder.ts index 4a19c01ae4..a4352211c5 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useNavigationMenuItemsByFolder.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useNavigationMenuItemsByFolder.ts @@ -1,4 +1,3 @@ -import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { type NavigationMenuItem } from '~/generated-metadata/graphql'; @@ -9,6 +8,7 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadat import { type ObjectRecordIdentifier } from '@/object-record/types/ObjectRecordIdentifier'; import { coreViewsState } from '@/views/states/coreViewState'; import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { usePrefetchedNavigationMenuItemsData } from './usePrefetchedNavigationMenuItemsData'; @@ -19,8 +19,8 @@ type NavigationMenuItemFolder = { }; export const useNavigationMenuItemsByFolder = () => { - const coreViews = useRecoilValue(coreViewsState); - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const coreViews = useRecoilValueV2(coreViewsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); const { navigationMenuItems, workspaceNavigationMenuItems } = usePrefetchedNavigationMenuItemsData(); diff --git a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useNavigationMenuItemsDraftState.ts b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useNavigationMenuItemsDraftState.ts index cee52625bf..f5d2125ac2 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useNavigationMenuItemsDraftState.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useNavigationMenuItemsDraftState.ts @@ -1,4 +1,3 @@ -import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; @@ -12,7 +11,7 @@ export const useNavigationMenuItemsDraftState = () => { const isNavigationMenuInEditMode = useRecoilValueV2( isNavigationMenuInEditModeStateV2, ); - const prefetchNavigationMenuItems = useRecoilValue( + const prefetchNavigationMenuItems = useRecoilValueV2( prefetchNavigationMenuItemsState, ); const navigationMenuItemsDraft = useRecoilValueV2( diff --git a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useNavigationMenuObjectMetadataFromDraft.ts b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useNavigationMenuObjectMetadataFromDraft.ts index f2f0d15cc8..5a1945ac0e 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useNavigationMenuObjectMetadataFromDraft.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useNavigationMenuObjectMetadataFromDraft.ts @@ -1,6 +1,6 @@ -import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { coreViewsState } from '@/views/states/coreViewState'; import { ViewKey } from '@/views/types/ViewKey'; import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView'; @@ -14,7 +14,7 @@ type NavigationMenuItemDraft = { export const useNavigationMenuObjectMetadataFromDraft = ( currentDraft: NavigationMenuItemDraft[], ) => { - const coreViews = useRecoilValue(coreViewsState); + const coreViews = useRecoilValueV2(coreViewsState); const views = coreViews.map(convertCoreViewToView); const objectMetadataIdsInWorkspace = currentDraft.reduce>( diff --git a/packages/twenty-front/src/modules/navigation-menu-item/hooks/usePrefetchedNavigationMenuItemsData.ts b/packages/twenty-front/src/modules/navigation-menu-item/hooks/usePrefetchedNavigationMenuItemsData.ts index fdfa0258b2..3dbfe77cc1 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/hooks/usePrefetchedNavigationMenuItemsData.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/hooks/usePrefetchedNavigationMenuItemsData.ts @@ -4,7 +4,7 @@ import { navigationMenuItemsDraftStateV2 } from '@/navigation-menu-item/states/n import { filterWorkspaceNavigationMenuItems } from '@/navigation-menu-item/utils/filterWorkspaceNavigationMenuItems'; import { prefetchNavigationMenuItemsState } from '@/prefetch/states/prefetchNavigationMenuItemsState'; import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; -import { useRecoilValue } from 'recoil'; + import { isDefined } from 'twenty-shared/utils'; import { type NavigationMenuItem } from '~/generated-metadata/graphql'; @@ -16,9 +16,11 @@ type PrefetchedNavigationMenuItemsData = { export const usePrefetchedNavigationMenuItemsData = (): PrefetchedNavigationMenuItemsData => { - const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); + const currentWorkspaceMember = useRecoilValueV2( + currentWorkspaceMemberState, + ); const currentWorkspaceMemberId = currentWorkspaceMember?.id; - const prefetchNavigationMenuItems = useRecoilValue( + const prefetchNavigationMenuItems = useRecoilValueV2( prefetchNavigationMenuItemsState, ); const isNavigationMenuInEditMode = useRecoilValueV2( diff --git a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useRemoveNavigationMenuItemByTargetRecordId.ts b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useRemoveNavigationMenuItemByTargetRecordId.ts index fee4d5713f..1d66044c0a 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useRemoveNavigationMenuItemByTargetRecordId.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useRemoveNavigationMenuItemByTargetRecordId.ts @@ -1,45 +1,47 @@ -import { useRecoilCallback } from 'recoil'; +import { useCallback } from 'react'; import { FIND_MANY_NAVIGATION_MENU_ITEMS } from '@/navigation-menu-item/graphql/queries/findManyNavigationMenuItems'; import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient'; import { prefetchNavigationMenuItemsState } from '@/prefetch/states/prefetchNavigationMenuItemsState'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; +import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; import { isDefined } from 'twenty-shared/utils'; export const useRemoveNavigationMenuItemByTargetRecordId = () => { const apolloCoreClient = useApolloCoreClient(); const cache = apolloCoreClient.cache; - const removeNavigationMenuItemsByTargetRecordIds = useRecoilCallback( - ({ set, snapshot }) => - (targetRecordIds: string[]) => { - const targetRecordIdsSet = new Set(targetRecordIds); - const currentNavigationMenuItems = snapshot - .getLoadable(prefetchNavigationMenuItemsState) - .getValue(); + const setNavigationMenuItemsState = useSetRecoilStateV2( + prefetchNavigationMenuItemsState, + ); - const updatedNavigationMenuItems = currentNavigationMenuItems.filter( - (item) => - !isDefined(item.targetRecordId) || - !targetRecordIdsSet.has(item.targetRecordId), - ); + const removeNavigationMenuItemsByTargetRecordIds = useCallback( + (targetRecordIds: string[]) => { + const targetRecordIdsSet = new Set(targetRecordIds); + const currentNavigationMenuItems = jotaiStore.get( + prefetchNavigationMenuItemsState.atom, + ); - set(prefetchNavigationMenuItemsState, updatedNavigationMenuItems); + const updatedNavigationMenuItems = currentNavigationMenuItems.filter( + (item) => + !isDefined(item.targetRecordId) || + !targetRecordIdsSet.has(item.targetRecordId), + ); - cache.updateQuery( - { query: FIND_MANY_NAVIGATION_MENU_ITEMS }, - (data) => { - if (!isDefined(data?.navigationMenuItems)) { - return data; - } + setNavigationMenuItemsState(updatedNavigationMenuItems); - return { - ...data, - navigationMenuItems: updatedNavigationMenuItems, - }; - }, - ); - }, - [cache], + cache.updateQuery({ query: FIND_MANY_NAVIGATION_MENU_ITEMS }, (data) => { + if (!isDefined(data?.navigationMenuItems)) { + return data; + } + + return { + ...data, + navigationMenuItems: updatedNavigationMenuItems, + }; + }); + }, + [cache, setNavigationMenuItemsState], ); return { diff --git a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useSaveNavigationMenuItemsDraft.ts b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useSaveNavigationMenuItemsDraft.ts index 6e61f20373..2ae1c55e18 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useSaveNavigationMenuItemsDraft.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useSaveNavigationMenuItemsDraft.ts @@ -1,4 +1,4 @@ -import { useRecoilCallback } from 'recoil'; +import { useCallback } from 'react'; import { isDefined } from 'twenty-shared/utils'; import { @@ -25,176 +25,163 @@ export const useSaveNavigationMenuItemsDraft = () => { const store = useStore(); - const saveDraft = useRecoilCallback( - ({ snapshot }) => - async () => { - const draft = store.get(navigationMenuItemsDraftStateV2.atom); - const prefetch = snapshot - .getLoadable(prefetchNavigationMenuItemsState) - .getValue(); + const saveDraft = useCallback(async () => { + const draft = store.get(navigationMenuItemsDraftStateV2.atom); + const prefetch = store.get(prefetchNavigationMenuItemsState.atom); - if (!draft) return; + if (!draft) return; - const workspacePrefetch = filterWorkspaceNavigationMenuItems(prefetch); - const topLevelWorkspace = workspacePrefetch.filter( - (item) => !isDefined(item.folderId), - ); - const draftIds = new Set(draft.map((i) => i.id)); + const workspacePrefetch = filterWorkspaceNavigationMenuItems(prefetch); + const topLevelWorkspace = workspacePrefetch.filter( + (item) => !isDefined(item.folderId), + ); + const draftIds = new Set(draft.map((i) => i.id)); - const topLevelToDelete = topLevelWorkspace.filter( - (item) => !draftIds.has(item.id), - ); - const folderIdsToDelete = new Set( - topLevelToDelete - .filter(isNavigationMenuItemFolder) - .map((item) => item.id), - ); - const folderChildrenToDelete = prefetch.filter( - (item) => - isDefined(item.folderId) && folderIdsToDelete.has(item.folderId), - ); + const topLevelToDelete = topLevelWorkspace.filter( + (item) => !draftIds.has(item.id), + ); + const folderIdsToDelete = new Set( + topLevelToDelete + .filter(isNavigationMenuItemFolder) + .map((item) => item.id), + ); + const folderChildrenToDelete = prefetch.filter( + (item) => + isDefined(item.folderId) && folderIdsToDelete.has(item.folderId), + ); - for (const item of folderChildrenToDelete) { - await deleteNavigationMenuItem(item.id); + for (const item of folderChildrenToDelete) { + await deleteNavigationMenuItem(item.id); + } + for (const item of topLevelToDelete) { + await deleteNavigationMenuItem(item.id); + } + + const prefetchIds = new Set(workspacePrefetch.map((i) => i.id)); + const workspacePrefetchById = new Map( + workspacePrefetch.map((i) => [i.id, i]), + ); + const idsToCreate = draft.filter((item) => !prefetchIds.has(item.id)); + const idsToRecreate = draft.filter((item) => { + const original = workspacePrefetchById.get(item.id); + if (!original) return false; + return ( + original.viewId !== item.viewId || + original.targetObjectMetadataId !== item.targetObjectMetadataId || + original.targetRecordId !== item.targetRecordId + ); + }); + + for (const draftItem of idsToRecreate) { + await deleteNavigationMenuItem(draftItem.id); + } + + const idsToCreateIncludingRecreated = [...idsToCreate, ...idsToRecreate]; + + for (const draftItem of idsToCreateIncludingRecreated) { + const input: CreateNavigationMenuItemInput = { + position: Math.max(0, Math.round(draftItem.position)), + }; + + if (isNavigationMenuItemFolder(draftItem)) { + input.name = draftItem.name ?? undefined; + input.icon = draftItem.icon ?? null; + } else if (isNavigationMenuItemLink(draftItem)) { + input.name = draftItem.name ?? 'Link'; + const linkUrl = (draftItem.link ?? '').trim(); + input.link = + linkUrl.startsWith('http://') || linkUrl.startsWith('https://') + ? linkUrl + : linkUrl + ? `https://${linkUrl}` + : undefined; + } else if (isDefined(draftItem.viewId)) { + input.viewId = draftItem.viewId; + } else if (isDefined(draftItem.targetRecordId)) { + input.targetRecordId = draftItem.targetRecordId; + input.targetObjectMetadataId = + draftItem.targetObjectMetadataId ?? undefined; + } + + if (isDefined(draftItem.folderId)) { + input.folderId = draftItem.folderId; + } + + await createNavigationMenuItemMutation({ + variables: { input }, + }); + } + + const idsToRecreateSet = new Set(idsToRecreate.map((i) => i.id)); + for (const draftItem of draft) { + if (idsToRecreateSet.has(draftItem.id)) continue; + + const original = workspacePrefetchById.get(draftItem.id); + if (!original) continue; + + const positionChanged = original.position !== draftItem.position; + const folderIdChanged = + (original.folderId ?? null) !== (draftItem.folderId ?? null); + const nameChanged = + (isNavigationMenuItemFolder(draftItem) || + isNavigationMenuItemLink(draftItem)) && + (original.name ?? null) !== (draftItem.name ?? null); + const linkChanged = + isNavigationMenuItemLink(draftItem) && + (original.link ?? null) !== (draftItem.link ?? null); + const iconChanged = + isNavigationMenuItemFolder(draftItem) && + (original.icon ?? null) !== (draftItem.icon ?? null); + + if ( + positionChanged || + folderIdChanged || + nameChanged || + linkChanged || + iconChanged + ) { + const updateInput: { + id: string; + position?: number; + folderId?: string | null; + name?: string; + link?: string | null; + icon?: string | null; + } = { id: draftItem.id }; + + if (positionChanged) { + updateInput.position = Math.max(0, Math.round(draftItem.position)); } - for (const item of topLevelToDelete) { - await deleteNavigationMenuItem(item.id); + if (folderIdChanged) { + updateInput.folderId = draftItem.folderId ?? null; + } + if (nameChanged && isNavigationMenuItemFolder(draftItem)) { + updateInput.name = draftItem.name ?? undefined; + } + if (nameChanged && isNavigationMenuItemLink(draftItem)) { + updateInput.name = draftItem.name ?? undefined; + } + if (linkChanged && isNavigationMenuItemLink(draftItem)) { + const linkUrl = (draftItem.link ?? '').trim(); + updateInput.link = linkUrl + ? linkUrl.startsWith('http://') || linkUrl.startsWith('https://') + ? linkUrl + : `https://${linkUrl}` + : null; + } + if (iconChanged && isNavigationMenuItemFolder(draftItem)) { + updateInput.icon = draftItem.icon ?? null; } - const prefetchIds = new Set(workspacePrefetch.map((i) => i.id)); - const workspacePrefetchById = new Map( - workspacePrefetch.map((i) => [i.id, i]), - ); - const idsToCreate = draft.filter((item) => !prefetchIds.has(item.id)); - const idsToRecreate = draft.filter((item) => { - const original = workspacePrefetchById.get(item.id); - if (!original) return false; - return ( - original.viewId !== item.viewId || - original.targetObjectMetadataId !== item.targetObjectMetadataId || - original.targetRecordId !== item.targetRecordId - ); - }); - - for (const draftItem of idsToRecreate) { - await deleteNavigationMenuItem(draftItem.id); - } - - const idsToCreateIncludingRecreated = [ - ...idsToCreate, - ...idsToRecreate, - ]; - - for (const draftItem of idsToCreateIncludingRecreated) { - const input: CreateNavigationMenuItemInput = { - position: Math.max(0, Math.round(draftItem.position)), - }; - - if (isNavigationMenuItemFolder(draftItem)) { - input.name = draftItem.name ?? undefined; - input.icon = draftItem.icon ?? null; - } else if (isNavigationMenuItemLink(draftItem)) { - input.name = draftItem.name ?? 'Link'; - const linkUrl = (draftItem.link ?? '').trim(); - input.link = - linkUrl.startsWith('http://') || linkUrl.startsWith('https://') - ? linkUrl - : linkUrl - ? `https://${linkUrl}` - : undefined; - } else if (isDefined(draftItem.viewId)) { - input.viewId = draftItem.viewId; - } else if (isDefined(draftItem.targetRecordId)) { - input.targetRecordId = draftItem.targetRecordId; - input.targetObjectMetadataId = - draftItem.targetObjectMetadataId ?? undefined; - } - - if (isDefined(draftItem.folderId)) { - input.folderId = draftItem.folderId; - } - - await createNavigationMenuItemMutation({ - variables: { input }, - }); - } - - const idsToRecreateSet = new Set(idsToRecreate.map((i) => i.id)); - for (const draftItem of draft) { - if (idsToRecreateSet.has(draftItem.id)) continue; - - const original = workspacePrefetchById.get(draftItem.id); - if (!original) continue; - - const positionChanged = original.position !== draftItem.position; - const folderIdChanged = - (original.folderId ?? null) !== (draftItem.folderId ?? null); - const nameChanged = - (isNavigationMenuItemFolder(draftItem) || - isNavigationMenuItemLink(draftItem)) && - (original.name ?? null) !== (draftItem.name ?? null); - const linkChanged = - isNavigationMenuItemLink(draftItem) && - (original.link ?? null) !== (draftItem.link ?? null); - const iconChanged = - isNavigationMenuItemFolder(draftItem) && - (original.icon ?? null) !== (draftItem.icon ?? null); - - if ( - positionChanged || - folderIdChanged || - nameChanged || - linkChanged || - iconChanged - ) { - const updateInput: { - id: string; - position?: number; - folderId?: string | null; - name?: string; - link?: string | null; - icon?: string | null; - } = { id: draftItem.id }; - - if (positionChanged) { - updateInput.position = Math.max( - 0, - Math.round(draftItem.position), - ); - } - if (folderIdChanged) { - updateInput.folderId = draftItem.folderId ?? null; - } - if (nameChanged && isNavigationMenuItemFolder(draftItem)) { - updateInput.name = draftItem.name ?? undefined; - } - if (nameChanged && isNavigationMenuItemLink(draftItem)) { - updateInput.name = draftItem.name ?? undefined; - } - if (linkChanged && isNavigationMenuItemLink(draftItem)) { - const linkUrl = (draftItem.link ?? '').trim(); - updateInput.link = linkUrl - ? linkUrl.startsWith('http://') || - linkUrl.startsWith('https://') - ? linkUrl - : `https://${linkUrl}` - : null; - } - if (iconChanged && isNavigationMenuItemFolder(draftItem)) { - updateInput.icon = draftItem.icon ?? null; - } - - await updateNavigationMenuItem(updateInput); - } - } - }, - [ - updateNavigationMenuItem, - deleteNavigationMenuItem, - createNavigationMenuItemMutation, - store, - ], - ); + await updateNavigationMenuItem(updateInput); + } + } + }, [ + updateNavigationMenuItem, + deleteNavigationMenuItem, + createNavigationMenuItemMutation, + store, + ]); return { saveDraft }; }; diff --git a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItemObjectMetadata.ts b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItemObjectMetadata.ts index aabaa5f98b..ae9efc73b8 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItemObjectMetadata.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItemObjectMetadata.ts @@ -1,14 +1,13 @@ -import { useRecoilValue } from 'recoil'; - import { useSelectedNavigationMenuItemEditItem } from '@/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItem'; import { getObjectMetadataForNavigationMenuItem } from '@/navigation-menu-item/utils/getObjectMetadataForNavigationMenuItem'; import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { coreViewsState } from '@/views/states/coreViewState'; import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView'; export const useSelectedNavigationMenuItemEditItemObjectMetadata = () => { const { selectedItem } = useSelectedNavigationMenuItemEditItem(); - const coreViews = useRecoilValue(coreViewsState); + const coreViews = useRecoilValueV2(coreViewsState); const views = coreViews.map(convertCoreViewToView); const { objectMetadataItems } = useObjectMetadataItems(); diff --git a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useSortedNavigationMenuItems.ts b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useSortedNavigationMenuItems.ts index db6869c67c..86703cab1f 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useSortedNavigationMenuItems.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useSortedNavigationMenuItems.ts @@ -1,5 +1,4 @@ import { useMemo } from 'react'; -import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { isNavigationMenuItemFolder } from '@/navigation-menu-item/utils/isNavigationMenuItemFolder'; @@ -10,14 +9,15 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadat import { type ObjectRecordIdentifier } from '@/object-record/types/ObjectRecordIdentifier'; import { coreViewsState } from '@/views/states/coreViewState'; import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { usePrefetchedNavigationMenuItemsData } from './usePrefetchedNavigationMenuItemsData'; export const useSortedNavigationMenuItems = () => { const { navigationMenuItems, workspaceNavigationMenuItems } = usePrefetchedNavigationMenuItemsData(); - const coreViews = useRecoilValue(coreViewsState).map(convertCoreViewToView); - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const coreViews = useRecoilValueV2(coreViewsState).map(convertCoreViewToView); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); const targetRecordIdentifiers = useMemo(() => { const identifiersMap = new Map(); diff --git a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useWorkspaceNavigationMenuItems.ts b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useWorkspaceNavigationMenuItems.ts index 548bc223ad..9a469ade70 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useWorkspaceNavigationMenuItems.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useWorkspaceNavigationMenuItems.ts @@ -1,9 +1,9 @@ import { useMemo } from 'react'; -import { useRecoilValue } from 'recoil'; import { isNavigationMenuItemFolder } from '@/navigation-menu-item/utils/isNavigationMenuItemFolder'; import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { coreViewsState } from '@/views/states/coreViewState'; import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView'; @@ -15,7 +15,7 @@ export const useWorkspaceNavigationMenuItems = (): { } => { const { workspaceNavigationMenuItems: rawWorkspaceNavigationMenuItems } = usePrefetchedNavigationMenuItemsData(); - const coreViews = useRecoilValue(coreViewsState); + const coreViews = useRecoilValueV2(coreViewsState); const views = coreViews.map(convertCoreViewToView); diff --git a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useWorkspaceSectionItems.ts b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useWorkspaceSectionItems.ts index bdda9815e5..cc7ee89ab6 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/hooks/useWorkspaceSectionItems.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/hooks/useWorkspaceSectionItems.ts @@ -1,4 +1,3 @@ -import { useRecoilValue } from 'recoil'; import { type NavigationMenuItem } from '~/generated-metadata/graphql'; import { FOLDER_ICON_DEFAULT } from '@/navigation-menu-item/constants/FolderIconDefault'; @@ -11,6 +10,7 @@ import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataI import { coreViewsState } from '@/views/states/coreViewState'; import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView'; import { isDefined } from 'twenty-shared/utils'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { useNavigationMenuItemsByFolder } from './useNavigationMenuItemsByFolder'; import { usePrefetchedNavigationMenuItemsData } from './usePrefetchedNavigationMenuItemsData'; @@ -34,8 +34,8 @@ export const useWorkspaceSectionItems = (): FlatWorkspaceItem[] => { const { workspaceNavigationMenuItemsSorted } = useSortedNavigationMenuItems(); const { workspaceNavigationMenuItemsByFolder } = useNavigationMenuItemsByFolder(); - const coreViews = useRecoilValue(coreViewsState); - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const coreViews = useRecoilValueV2(coreViewsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); const views = coreViews.map(convertCoreViewToView); diff --git a/packages/twenty-front/src/modules/navigation/components/MainNavigationDrawer.tsx b/packages/twenty-front/src/modules/navigation/components/MainNavigationDrawer.tsx index 8ebdd7b442..2a1779fa11 100644 --- a/packages/twenty-front/src/modules/navigation/components/MainNavigationDrawer.tsx +++ b/packages/twenty-front/src/modules/navigation/components/MainNavigationDrawer.tsx @@ -1,7 +1,5 @@ -import styled from '@emotion/styled'; -import { useRecoilValue } from 'recoil'; - import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; +import styled from '@emotion/styled'; import { useFavoritesByFolder } from '@/favorites/hooks/useFavoritesByFolder'; import { NavigationMenuItemFolderContentDispatcherEffect } from '@/navigation-menu-item/components/NavigationMenuItemFolderContentDispatcher'; import { useNavigationMenuItemsByFolder } from '@/navigation-menu-item/hooks/useNavigationMenuItemsByFolder'; @@ -22,7 +20,7 @@ const StyledScrollableContent = styled.div` `; export const MainNavigationDrawer = ({ className }: { className?: string }) => { - const currentWorkspace = useRecoilValue(currentWorkspaceState); + const currentWorkspace = useRecoilValueV2(currentWorkspaceState); const currentFavoriteFolderId = useRecoilValueV2( currentFavoriteFolderIdStateV2, ); diff --git a/packages/twenty-front/src/modules/navigation/components/MobileNavigationBar.tsx b/packages/twenty-front/src/modules/navigation/components/MobileNavigationBar.tsx index b6b854ce97..6870514065 100644 --- a/packages/twenty-front/src/modules/navigation/components/MobileNavigationBar.tsx +++ b/packages/twenty-front/src/modules/navigation/components/MobileNavigationBar.tsx @@ -11,7 +11,6 @@ import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStat import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState'; import { useNavigate } from 'react-router-dom'; -import { useRecoilState } from 'recoil'; import { type IconComponent, IconList, @@ -34,7 +33,7 @@ export const MobileNavigationBar = () => { const [isNavigationDrawerExpanded, setIsNavigationDrawerExpanded] = useRecoilStateV2(isNavigationDrawerExpandedState); const [currentMobileNavigationDrawer, setCurrentMobileNavigationDrawer] = - useRecoilState(currentMobileNavigationDrawerState); + useRecoilStateV2(currentMobileNavigationDrawerState); const { openSettingsMenu } = useOpenSettingsMenu(); const { alphaSortedActiveNonSystemObjectMetadataItems } = useFilteredObjectMetadataItems(); diff --git a/packages/twenty-front/src/modules/navigation/components/__stories__/AppNavigationDrawer.stories.tsx b/packages/twenty-front/src/modules/navigation/components/__stories__/AppNavigationDrawer.stories.tsx index f2439b5eca..c6ae399654 100644 --- a/packages/twenty-front/src/modules/navigation/components/__stories__/AppNavigationDrawer.stories.tsx +++ b/packages/twenty-front/src/modules/navigation/components/__stories__/AppNavigationDrawer.stories.tsx @@ -1,7 +1,6 @@ import { type Meta, type StoryObj } from '@storybook/react-vite'; import { useEffect } from 'react'; import { MemoryRouter } from 'react-router-dom'; -import { useSetRecoilState } from 'recoil'; import { currentMobileNavigationDrawerState } from '@/navigation/states/currentMobileNavigationDrawerState'; import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded'; @@ -26,7 +25,7 @@ const MobileNavigationDrawerStateSetterEffect = ({ const setIsNavigationDrawerExpanded = useSetRecoilStateV2( isNavigationDrawerExpandedState, ); - const setCurrentMobileNavigationDrawer = useSetRecoilState( + const setCurrentMobileNavigationDrawer = useSetRecoilStateV2( currentMobileNavigationDrawerState, ); diff --git a/packages/twenty-front/src/modules/navigation/hooks/__tests__/useDefaultHomePagePath.test.ts b/packages/twenty-front/src/modules/navigation/hooks/__tests__/useDefaultHomePagePath.test.ts index bc87be22d7..5293538d11 100644 --- a/packages/twenty-front/src/modules/navigation/hooks/__tests__/useDefaultHomePagePath.test.ts +++ b/packages/twenty-front/src/modules/navigation/hooks/__tests__/useDefaultHomePagePath.test.ts @@ -1,11 +1,14 @@ import { renderHook, waitFor } from '@testing-library/react'; -import { useEffect } from 'react'; -import { RecoilRoot, useSetRecoilState } from 'recoil'; +import { createElement, useEffect, type ReactNode } from 'react'; +import { Provider as JotaiProvider } from 'jotai'; +import { RecoilRoot } from 'recoil'; import { currentUserState } from '@/auth/states/currentUserState'; import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState'; import { useDefaultHomePagePath } from '@/navigation/hooks/useDefaultHomePagePath'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations'; import { coreViewsState } from '@/views/states/coreViewState'; import { AppPath } from 'twenty-shared/types'; @@ -18,6 +21,13 @@ import { getMockCompanyObjectMetadataItem } from '~/testing/mock-data/companies' import { mockedUserData } from '~/testing/mock-data/users'; import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems'; +const Wrapper = ({ children }: { children: ReactNode }) => + createElement( + JotaiProvider, + { store: jotaiStore }, + createElement(RecoilRoot, null as any, children), + ); + const renderHooks = ({ withCurrentUser, withExistingView, @@ -25,20 +35,20 @@ const renderHooks = ({ withCurrentUser: boolean; withExistingView: boolean; }) => { + jotaiStore.set( + objectMetadataItemsState.atom, + generatedMockObjectMetadataItems, + ); + const { result } = renderHook( () => { - const setCurrentUser = useSetRecoilState(currentUserState); - const setCurrentUserWorkspace = useSetRecoilState( + const setCurrentUser = useSetRecoilStateV2(currentUserState); + const setCurrentUserWorkspace = useSetRecoilStateV2( currentUserWorkspaceState, ); - const setObjectMetadataItems = useSetRecoilState( - objectMetadataItemsState, - ); - const setCoreViews = useSetRecoilState(coreViewsState); + const setCoreViews = useSetRecoilStateV2(coreViewsState); useEffect(() => { - setObjectMetadataItems(generatedMockObjectMetadataItems); - if (withExistingView) { setCoreViews([ { @@ -72,17 +82,12 @@ const renderHooks = ({ setCurrentUser(mockedUserData); setCurrentUserWorkspace(mockedUserData.currentUserWorkspace); } - }, [ - setCurrentUser, - setCurrentUserWorkspace, - setObjectMetadataItems, - setCoreViews, - ]); + }, [setCurrentUser, setCurrentUserWorkspace, setCoreViews]); return useDefaultHomePagePath(); }, { - wrapper: RecoilRoot, + wrapper: Wrapper, }, ); return { result }; diff --git a/packages/twenty-front/src/modules/navigation/hooks/useDefaultHomePagePath.ts b/packages/twenty-front/src/modules/navigation/hooks/useDefaultHomePagePath.ts index c4ed6db14e..cf3cfa4f4f 100644 --- a/packages/twenty-front/src/modules/navigation/hooks/useDefaultHomePagePath.ts +++ b/packages/twenty-front/src/modules/navigation/hooks/useDefaultHomePagePath.ts @@ -1,19 +1,20 @@ import { currentUserState } from '@/auth/states/currentUserState'; import { lastVisitedObjectMetadataItemIdState } from '@/navigation/states/lastVisitedObjectMetadataItemIdState'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { type ObjectPathInfo } from '@/navigation/types/ObjectPathInfo'; import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems'; import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions'; import { getObjectPermissionsFromMapByObjectMetadataId } from '@/settings/roles/role-permissions/objects-permissions/utils/getObjectPermissionsFromMapByObjectMetadataId'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { coreViewsState } from '@/views/states/coreViewState'; import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView'; import isEmpty from 'lodash.isempty'; import { useCallback, useMemo } from 'react'; -import { useRecoilCallback, useRecoilValue } from 'recoil'; import { AppPath, SettingsPath } from 'twenty-shared/types'; import { getAppPath, getSettingsPath, isDefined } from 'twenty-shared/utils'; export const useDefaultHomePagePath = () => { - const currentUser = useRecoilValue(currentUserState); + const currentUser = useRecoilValueV2(currentUserState); const { objectPermissionsByObjectMetadataId } = useObjectPermissions(); const { alphaSortedActiveNonSystemObjectMetadataItems } = @@ -41,18 +42,18 @@ export const useDefaultHomePagePath = () => { [readableAlphaSortedActiveNonSystemObjectMetadataItems], ); - const getFirstView = useRecoilCallback(({ snapshot }) => { - return (objectMetadataItemId: string | undefined | null) => { - const views = snapshot - .getLoadable(coreViewsState) - .getValue() - .map(convertCoreViewToView); + const coreViews = useRecoilValueV2(coreViewsState); + + const getFirstView = useCallback( + (objectMetadataItemId: string | undefined | null) => { + const views = coreViews.map(convertCoreViewToView); return views.find( (view) => view.objectMetadataId === objectMetadataItemId, ); - }; - }, []); + }, + [coreViews], + ); const firstObjectPathInfo = useMemo(() => { const [firstObjectMetadataItem] = @@ -67,33 +68,30 @@ export const useDefaultHomePagePath = () => { return { objectMetadataItem: firstObjectMetadataItem, view }; }, [getFirstView, readableAlphaSortedActiveNonSystemObjectMetadataItems]); - const getDefaultObjectPathInfo = useRecoilCallback( - ({ snapshot }) => { - return () => { - const lastVisitedObjectMetadataItemId = snapshot - .getLoadable(lastVisitedObjectMetadataItemIdState) - .getValue(); + const getDefaultObjectPathInfo = useCallback(() => { + const lastVisitedObjectMetadataItemId = jotaiStore.get( + lastVisitedObjectMetadataItemIdState.atom, + ); - const lastVisitedObjectMetadataItem = isDefined( - lastVisitedObjectMetadataItemId, - ) - ? getActiveObjectMetadataItemMatchingId( - lastVisitedObjectMetadataItemId, - ) - : undefined; + const lastVisitedObjectMetadataItem = isDefined( + lastVisitedObjectMetadataItemId, + ) + ? getActiveObjectMetadataItemMatchingId(lastVisitedObjectMetadataItemId) + : undefined; - if (isDefined(lastVisitedObjectMetadataItem)) { - return { - view: getFirstView(lastVisitedObjectMetadataItemId), - objectMetadataItem: lastVisitedObjectMetadataItem, - }; - } - - return firstObjectPathInfo; + if (isDefined(lastVisitedObjectMetadataItem)) { + return { + view: getFirstView(lastVisitedObjectMetadataItemId), + objectMetadataItem: lastVisitedObjectMetadataItem, }; - }, - [firstObjectPathInfo, getActiveObjectMetadataItemMatchingId, getFirstView], - ); + } + + return firstObjectPathInfo; + }, [ + firstObjectPathInfo, + getActiveObjectMetadataItemMatchingId, + getFirstView, + ]); const defaultHomePagePath = useMemo(() => { if (!isDefined(currentUser)) { diff --git a/packages/twenty-front/src/modules/navigation/hooks/useIsSettingsDrawer.ts b/packages/twenty-front/src/modules/navigation/hooks/useIsSettingsDrawer.ts index 1ee9d50614..1a063d9f81 100644 --- a/packages/twenty-front/src/modules/navigation/hooks/useIsSettingsDrawer.ts +++ b/packages/twenty-front/src/modules/navigation/hooks/useIsSettingsDrawer.ts @@ -1,12 +1,12 @@ import { useIsSettingsPage } from '@/navigation/hooks/useIsSettingsPage'; -import { useRecoilValue } from 'recoil'; import { currentMobileNavigationDrawerState } from '@/navigation/states/currentMobileNavigationDrawerState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; export const useIsSettingsDrawer = () => { const isMobile = useIsMobile(); const isSettingsPage = useIsSettingsPage(); - const currentMobileNavigationDrawer = useRecoilValue( + const currentMobileNavigationDrawer = useRecoilValueV2( currentMobileNavigationDrawerState, ); return isMobile diff --git a/packages/twenty-front/src/modules/navigation/hooks/useLastVisitedView.ts b/packages/twenty-front/src/modules/navigation/hooks/useLastVisitedView.ts index b53bd5e145..036f3ec69f 100644 --- a/packages/twenty-front/src/modules/navigation/hooks/useLastVisitedView.ts +++ b/packages/twenty-front/src/modules/navigation/hooks/useLastVisitedView.ts @@ -1,9 +1,9 @@ import { lastVisitedViewPerObjectMetadataItemState } from '@/navigation/states/lastVisitedViewPerObjectMetadataItemState'; import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; export const useLastVisitedView = () => { - const lastVisitedViewPerObjectMetadataItem = useRecoilValue( + const lastVisitedViewPerObjectMetadataItem = useRecoilValueV2( lastVisitedViewPerObjectMetadataItemState, ); diff --git a/packages/twenty-front/src/modules/navigation/hooks/useOpenSettings.ts b/packages/twenty-front/src/modules/navigation/hooks/useOpenSettings.ts index b50843c66b..04f6b7b934 100644 --- a/packages/twenty-front/src/modules/navigation/hooks/useOpenSettings.ts +++ b/packages/twenty-front/src/modules/navigation/hooks/useOpenSettings.ts @@ -1,13 +1,12 @@ import { currentMobileNavigationDrawerState } from '@/navigation/states/currentMobileNavigationDrawerState'; import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded'; import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; -import { useSetRecoilState } from 'recoil'; export const useOpenSettingsMenu = () => { const setIsNavigationDrawerExpanded = useSetRecoilStateV2( isNavigationDrawerExpandedState, ); - const setCurrentMobileNavigationDrawer = useSetRecoilState( + const setCurrentMobileNavigationDrawer = useSetRecoilStateV2( currentMobileNavigationDrawerState, ); diff --git a/packages/twenty-front/src/modules/navigation/hooks/useSetLastVisitedObjectMetadataId.ts b/packages/twenty-front/src/modules/navigation/hooks/useSetLastVisitedObjectMetadataId.ts index 3ac91bdf21..9d9025aeba 100644 --- a/packages/twenty-front/src/modules/navigation/hooks/useSetLastVisitedObjectMetadataId.ts +++ b/packages/twenty-front/src/modules/navigation/hooks/useSetLastVisitedObjectMetadataId.ts @@ -1,31 +1,32 @@ import { lastVisitedObjectMetadataItemIdState } from '@/navigation/states/lastVisitedObjectMetadataItemIdState'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; -import { useRecoilCallback } from 'recoil'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; +import { useCallback } from 'react'; import { isDefined } from 'twenty-shared/utils'; export const useSetLastVisitedObjectMetadataId = () => { - const setLastVisitedObjectMetadataId = useRecoilCallback( - ({ set, snapshot }) => - ({ objectMetadataItemId }: { objectMetadataItemId: string }) => { - const objectMetadataItems = snapshot - .getLoadable(objectMetadataItemsState) - .getValue(); + const setLastVisitedObjectMetadataId = useCallback( + ({ objectMetadataItemId }: { objectMetadataItemId: string }) => { + const objectMetadataItems = jotaiStore.get(objectMetadataItemsState.atom); - const objectMetadataItem = objectMetadataItems.find( - (item) => item.id === objectMetadataItemId, + const objectMetadataItem = objectMetadataItems.find( + (item) => item.id === objectMetadataItemId, + ); + + const lastVisitedObjectMetadataItemId = jotaiStore.get( + lastVisitedObjectMetadataItemIdState.atom, + ); + + if ( + isDefined(objectMetadataItem) && + lastVisitedObjectMetadataItemId !== objectMetadataItemId + ) { + jotaiStore.set( + lastVisitedObjectMetadataItemIdState.atom, + objectMetadataItemId, ); - - const lastVisitedObjectMetadataItemId = snapshot - .getLoadable(lastVisitedObjectMetadataItemIdState) - .getValue(); - - if ( - isDefined(objectMetadataItem) && - lastVisitedObjectMetadataItemId !== objectMetadataItemId - ) { - set(lastVisitedObjectMetadataItemIdState, objectMetadataItemId); - } - }, + } + }, [], ); diff --git a/packages/twenty-front/src/modules/navigation/hooks/useSetLastVisitedViewForObjectMetadataNamePlural.ts b/packages/twenty-front/src/modules/navigation/hooks/useSetLastVisitedViewForObjectMetadataNamePlural.ts index 67a572c440..6fa1185aee 100644 --- a/packages/twenty-front/src/modules/navigation/hooks/useSetLastVisitedViewForObjectMetadataNamePlural.ts +++ b/packages/twenty-front/src/modules/navigation/hooks/useSetLastVisitedViewForObjectMetadataNamePlural.ts @@ -1,56 +1,54 @@ import { lastVisitedViewPerObjectMetadataItemState } from '@/navigation/states/lastVisitedViewPerObjectMetadataItemState'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { coreViewsState } from '@/views/states/coreViewState'; import { type CoreViewWithRelations } from '@/views/types/CoreViewWithRelations'; -import { useRecoilCallback } from 'recoil'; +import { useCallback } from 'react'; import { isDefined } from 'twenty-shared/utils'; export const useSetLastVisitedViewForObjectMetadataNamePlural = () => { - const setLastVisitedViewForObjectMetadataNamePlural = useRecoilCallback( - ({ set, snapshot }) => - async ({ - objectNamePlural, - viewId, - }: { - objectNamePlural: string; - viewId: string; - }) => { - const views = snapshot.getLoadable(coreViewsState).getValue(); + const setLastVisitedViewForObjectMetadataNamePlural = useCallback( + async ({ + objectNamePlural, + viewId, + }: { + objectNamePlural: string; + viewId: string; + }) => { + const views = jotaiStore.get(coreViewsState.atom); - const view = views.find( - (view: CoreViewWithRelations) => view.id === viewId, - ); + const view = views.find( + (view: CoreViewWithRelations) => view.id === viewId, + ); - const objectMetadataItems = snapshot - .getLoadable(objectMetadataItemsState) - .getValue(); + const objectMetadataItems = jotaiStore.get(objectMetadataItemsState.atom); - const objectMetadataItem = objectMetadataItems.find( - (item) => item.namePlural === objectNamePlural, - ); + const objectMetadataItem = objectMetadataItems.find( + (item) => item.namePlural === objectNamePlural, + ); - if (!isDefined(objectMetadataItem) || !isDefined(view)) { - return; - } + if (!isDefined(objectMetadataItem) || !isDefined(view)) { + return; + } - if (view.objectMetadataId !== objectMetadataItem.id) { - return; - } + if (view.objectMetadataId !== objectMetadataItem.id) { + return; + } - const lastVisitedViewPerObjectMetadataItem = snapshot - .getLoadable(lastVisitedViewPerObjectMetadataItemState) - .getValue(); + const lastVisitedViewPerObjectMetadataItem = jotaiStore.get( + lastVisitedViewPerObjectMetadataItemState.atom, + ); - const lastVisitedViewId = - lastVisitedViewPerObjectMetadataItem?.[objectMetadataItem?.id]; + const lastVisitedViewId = + lastVisitedViewPerObjectMetadataItem?.[objectMetadataItem?.id]; - if (isDefined(objectMetadataItem) && lastVisitedViewId !== viewId) { - set(lastVisitedViewPerObjectMetadataItemState, { - ...lastVisitedViewPerObjectMetadataItem, - [objectMetadataItem.id]: viewId, - }); - } - }, + if (isDefined(objectMetadataItem) && lastVisitedViewId !== viewId) { + jotaiStore.set(lastVisitedViewPerObjectMetadataItemState.atom, { + ...lastVisitedViewPerObjectMetadataItem, + [objectMetadataItem.id]: viewId, + }); + } + }, [], ); diff --git a/packages/twenty-front/src/modules/navigation/states/currentMobileNavigationDrawerState.ts b/packages/twenty-front/src/modules/navigation/states/currentMobileNavigationDrawerState.ts index e74a1c90a1..a061df527b 100644 --- a/packages/twenty-front/src/modules/navigation/states/currentMobileNavigationDrawerState.ts +++ b/packages/twenty-front/src/modules/navigation/states/currentMobileNavigationDrawerState.ts @@ -1,5 +1,6 @@ -import { createState } from '@/ui/utilities/state/utils/createState'; -export const currentMobileNavigationDrawerState = createState< +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; + +export const currentMobileNavigationDrawerState = createStateV2< 'main' | 'settings' >({ key: 'currentMobileNavigationDrawerState', diff --git a/packages/twenty-front/src/modules/navigation/states/lastVisitedObjectMetadataItemIdState.ts b/packages/twenty-front/src/modules/navigation/states/lastVisitedObjectMetadataItemIdState.ts index 5644f1909c..2776ef5f17 100644 --- a/packages/twenty-front/src/modules/navigation/states/lastVisitedObjectMetadataItemIdState.ts +++ b/packages/twenty-front/src/modules/navigation/states/lastVisitedObjectMetadataItemIdState.ts @@ -1,8 +1,9 @@ -import { localStorageEffect } from '~/utils/recoil/localStorageEffect'; -import { createState } from '@/ui/utilities/state/utils/createState'; +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; -export const lastVisitedObjectMetadataItemIdState = createState({ +export const lastVisitedObjectMetadataItemIdState = createStateV2< + string | null +>({ key: 'lastVisitedObjectMetadataItemIdState', defaultValue: null, - effects: [localStorageEffect()], + useLocalStorage: true, }); diff --git a/packages/twenty-front/src/modules/navigation/states/lastVisitedViewPerObjectMetadataItemState.ts b/packages/twenty-front/src/modules/navigation/states/lastVisitedViewPerObjectMetadataItemState.ts index 95c564d2f8..b16b1ab915 100644 --- a/packages/twenty-front/src/modules/navigation/states/lastVisitedViewPerObjectMetadataItemState.ts +++ b/packages/twenty-front/src/modules/navigation/states/lastVisitedViewPerObjectMetadataItemState.ts @@ -1,11 +1,10 @@ -import { localStorageEffect } from '~/utils/recoil/localStorageEffect'; -import { createState } from '@/ui/utilities/state/utils/createState'; +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; -export const lastVisitedViewPerObjectMetadataItemState = createState | null>({ key: 'lastVisitedViewPerObjectMetadataItemState', defaultValue: null, - effects: [localStorageEffect()], + useLocalStorage: true, }); diff --git a/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerItemForObjectMetadataItem.tsx b/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerItemForObjectMetadataItem.tsx index 473aca2c27..52a3d5f720 100644 --- a/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerItemForObjectMetadataItem.tsx +++ b/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerItemForObjectMetadataItem.tsx @@ -12,11 +12,12 @@ import { NavigationDrawerItemsCollapsableContainer } from '@/ui/navigation/navig import { NavigationDrawerSubItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSubItem'; import { getNavigationSubItemLeftAdornment } from '@/ui/navigation/navigation-drawer/utils/getNavigationSubItemLeftAdornment'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; +import { useFamilySelectorValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilySelectorValueV2'; import { coreViewsFromObjectMetadataItemFamilySelector } from '@/views/states/selectors/coreViewsFromObjectMetadataItemFamilySelector'; import { ViewKey } from '@/views/types/ViewKey'; import { useTheme } from '@emotion/react'; import { useLocation } from 'react-router-dom'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { AppPath } from 'twenty-shared/types'; import { getAppPath, isDefined } from 'twenty-shared/utils'; import { Avatar, useIcons } from 'twenty-ui/display'; @@ -49,14 +50,13 @@ export const NavigationDrawerItemForObjectMetadataItem = ({ FeatureFlagKey.IS_NAVIGATION_MENU_ITEM_EDITING_ENABLED, ); const iconColors = getNavigationMenuItemIconColors(theme); - const lastVisitedViewPerObjectMetadataItem = useRecoilValue( + const lastVisitedViewPerObjectMetadataItem = useRecoilValueV2( lastVisitedViewPerObjectMetadataItemState, ); - const views = useRecoilValue( - coreViewsFromObjectMetadataItemFamilySelector({ - objectMetadataItemId: objectMetadataItem.id, - }), + const views = useFamilySelectorValueV2( + coreViewsFromObjectMetadataItemFamilySelector, + { objectMetadataItemId: objectMetadataItem.id }, ); const contextStoreCurrentViewId = useRecoilComponentValue( diff --git a/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForWorkspaceItems.tsx b/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForWorkspaceItems.tsx index 496a8d40e2..cea8c4c35f 100644 --- a/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForWorkspaceItems.tsx +++ b/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForWorkspaceItems.tsx @@ -4,7 +4,6 @@ import styled from '@emotion/styled'; import { Droppable } from '@hello-pangea/dnd'; import { useLingui } from '@lingui/react/macro'; import { useContext } from 'react'; -import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { IconLink, IconPlus } from 'twenty-ui/display'; @@ -27,6 +26,7 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadat import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { getObjectPermissionsForObject } from '@/object-metadata/utils/getObjectPermissionsForObject'; import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { DraggableItem } from '@/ui/layout/draggable-list/components/DraggableItem'; import { NavigationDrawerAnimatedCollapseWrapper } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerAnimatedCollapseWrapper'; import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem'; @@ -71,11 +71,11 @@ export const NavigationDrawerSectionForWorkspaceItems = ({ const workspaceDropDisabled = useIsDropDisabledForSection(true); const { toggleNavigationSection, isNavigationSectionOpen } = useNavigationSection('Workspace'); - const coreViews = useRecoilValue(coreViewsState); + const coreViews = useRecoilValueV2(coreViewsState); const views = coreViews.map(convertCoreViewToView); const { objectPermissionsByObjectMetadataId } = useObjectPermissions(); - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); const { isDragging } = useContext(NavigationMenuItemDragContext); const { addToNavigationFallbackDestination } = useContext( NavigationDropTargetContext, diff --git a/packages/twenty-front/src/modules/object-metadata/components/ObjectMetadataItemsLoadEffect.tsx b/packages/twenty-front/src/modules/object-metadata/components/ObjectMetadataItemsLoadEffect.tsx index 2ad4ff2116..a9c7569671 100644 --- a/packages/twenty-front/src/modules/object-metadata/components/ObjectMetadataItemsLoadEffect.tsx +++ b/packages/twenty-front/src/modules/object-metadata/components/ObjectMetadataItemsLoadEffect.tsx @@ -1,16 +1,15 @@ -import { useEffect, useState } from 'react'; -import { useRecoilValue } from 'recoil'; - import { currentUserState } from '@/auth/states/currentUserState'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; +import { useEffect, useState } from 'react'; import { useLoadMockedObjectMetadataItems } from '@/object-metadata/hooks/useLoadMockedObjectMetadataItems'; import { useRefreshObjectMetadataItems } from '@/object-metadata/hooks/useRefreshObjectMetadataItems'; import { isWorkspaceActiveOrSuspended } from 'twenty-shared/workspace'; import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull'; export const ObjectMetadataItemsLoadEffect = () => { - const currentUser = useRecoilValue(currentUserState); - const currentWorkspace = useRecoilValue(currentWorkspaceState); + const currentUser = useRecoilValueV2(currentUserState); + const currentWorkspace = useRecoilValueV2(currentWorkspaceState); const [isInitialized, setIsInitialized] = useState(false); const { refreshObjectMetadataItems } = useRefreshObjectMetadataItems(); diff --git a/packages/twenty-front/src/modules/object-metadata/components/ObjectMetadataItemsProvider.tsx b/packages/twenty-front/src/modules/object-metadata/components/ObjectMetadataItemsProvider.tsx index fde9228076..167d4e774c 100644 --- a/packages/twenty-front/src/modules/object-metadata/components/ObjectMetadataItemsProvider.tsx +++ b/packages/twenty-front/src/modules/object-metadata/components/ObjectMetadataItemsProvider.tsx @@ -1,17 +1,17 @@ import React from 'react'; -import { useRecoilValue } from 'recoil'; import { PreComputedChipGeneratorsProvider } from '@/object-metadata/components/PreComputedChipGeneratorsProvider'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { shouldAppBeLoadingState } from '@/object-metadata/states/shouldAppBeLoadingState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { UserOrMetadataLoader } from '~/loading/components/UserOrMetadataLoader'; export const ObjectMetadataItemsProvider = ({ children, }: React.PropsWithChildren) => { - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); - const shouldAppBeLoading = useRecoilValue(shouldAppBeLoadingState); + const shouldAppBeLoading = useRecoilValueV2(shouldAppBeLoadingState); const shouldDisplayChildren = !shouldAppBeLoading && objectMetadataItems.length > 0; diff --git a/packages/twenty-front/src/modules/object-metadata/components/PreComputedChipGeneratorsProvider.tsx b/packages/twenty-front/src/modules/object-metadata/components/PreComputedChipGeneratorsProvider.tsx index f2c9445a24..1374b81dfa 100644 --- a/packages/twenty-front/src/modules/object-metadata/components/PreComputedChipGeneratorsProvider.tsx +++ b/packages/twenty-front/src/modules/object-metadata/components/PreComputedChipGeneratorsProvider.tsx @@ -1,7 +1,7 @@ import React, { useMemo } from 'react'; -import { useRecoilValue } from 'recoil'; import { allowRequestsToTwentyIconsState } from '@/client-config/states/allowRequestsToTwentyIcons'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { PreComputedChipGeneratorsContext } from '@/object-metadata/contexts/PreComputedChipGeneratorsContext'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { getRecordChipGenerators } from '@/object-record/utils/getRecordChipGenerators'; @@ -11,8 +11,8 @@ import { FeatureFlagKey } from '~/generated-metadata/graphql'; export const PreComputedChipGeneratorsProvider = ({ children, }: React.PropsWithChildren) => { - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); - const allowRequestsToTwentyIcons = useRecoilValue( + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); + const allowRequestsToTwentyIcons = useRecoilValueV2( allowRequestsToTwentyIconsState, ); const isFilesFieldMigrated = useIsFeatureEnabled( diff --git a/packages/twenty-front/src/modules/object-metadata/components/RemoteNavigationDrawerSection.tsx b/packages/twenty-front/src/modules/object-metadata/components/RemoteNavigationDrawerSection.tsx index 0f41946a23..3c3a0e4d35 100644 --- a/packages/twenty-front/src/modules/object-metadata/components/RemoteNavigationDrawerSection.tsx +++ b/packages/twenty-front/src/modules/object-metadata/components/RemoteNavigationDrawerSection.tsx @@ -1,15 +1,14 @@ -import { useRecoilValue } from 'recoil'; - import { currentUserState } from '@/auth/states/currentUserState'; import { NavigationDrawerSectionForObjectMetadataItems } from '@/object-metadata/components/NavigationDrawerSectionForObjectMetadataItems'; import { NavigationDrawerSectionForObjectMetadataItemsSkeletonLoader } from '@/object-metadata/components/NavigationDrawerSectionForObjectMetadataItemsSkeletonLoader'; import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems'; import { useIsPrefetchLoading } from '@/prefetch/hooks/useIsPrefetchLoading'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { useLingui } from '@lingui/react/macro'; import { isDefined } from 'twenty-shared/utils'; export const RemoteNavigationDrawerSection = () => { - const currentUser = useRecoilValue(currentUserState); + const currentUser = useRecoilValueV2(currentUserState); const { t } = useLingui(); const { activeNonSystemObjectMetadataItems } = diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useColumnDefinitionsFromObjectMetadata.test.ts b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useColumnDefinitionsFromObjectMetadata.test.ts index d9406a8572..f7bf32e806 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useColumnDefinitionsFromObjectMetadata.test.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useColumnDefinitionsFromObjectMetadata.test.ts @@ -6,6 +6,7 @@ import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { CUSTOM_WORKSPACE_APPLICATION_MOCK } from '@/object-metadata/hooks/__tests__/constants/CustomWorkspaceApplicationMock.test.constant'; import { useColumnDefinitionsFromObjectMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromObjectMetadata'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { SubscriptionInterval, SubscriptionStatus, @@ -18,8 +19,14 @@ const Wrapper = getJestMetadataAndApolloMocksAndActionMenuWrapper({ apolloMocks: [], componentInstanceId: 'instanceId', contextStoreCurrentObjectMetadataNameSingular: 'company', - onInitializeRecoilSnapshot: ({ set }) => { - set(currentWorkspaceState, { + onInitializeRecoilSnapshot: () => { + // Recoil state initialization removed - using Jotai instead + }, +}); + +describe('useColumnDefinitionsFromObjectMetadata', () => { + it('should return expected definitions', () => { + jotaiStore.set(currentWorkspaceState.atom, { workspaceCustomApplication: { id: CUSTOM_WORKSPACE_APPLICATION_MOCK.id, }, @@ -65,11 +72,7 @@ const Wrapper = getJestMetadataAndApolloMocksAndActionMenuWrapper({ fastModel: DEFAULT_FAST_MODEL, smartModel: DEFAULT_SMART_MODEL, }); - }, -}); -describe('useColumnDefinitionsFromObjectMetadata', () => { - it('should return expected definitions', () => { const companyObjectMetadata = generatedMockObjectMetadataItems.find( (item) => item.nameSingular === 'company', ); diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useFilteredObjectMetadataItems.test.tsx b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useFilteredObjectMetadataItems.test.tsx index ebe34a8079..e2fdba7889 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useFilteredObjectMetadataItems.test.tsx +++ b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useFilteredObjectMetadataItems.test.tsx @@ -1,5 +1,6 @@ import { MockedProvider } from '@apollo/client/testing'; import { act, renderHook } from '@testing-library/react'; +import { Provider as JotaiProvider } from 'jotai'; import { type ReactNode } from 'react'; import { RecoilRoot } from 'recoil'; @@ -10,6 +11,7 @@ import { } from '@/object-metadata/hooks/__mocks__/useFilteredObjectMetadataItems'; import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { isDefined } from 'twenty-shared/utils'; import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems'; @@ -27,17 +29,22 @@ const mocks = [ }, ]; -const Wrapper = ({ children }: { children: ReactNode }) => ( - - set(objectMetadataItemsState, generatedMockObjectMetadataItems) - } - > - - {children} - - -); +const Wrapper = ({ children }: { children: ReactNode }) => { + jotaiStore.set( + objectMetadataItemsState.atom, + generatedMockObjectMetadataItems, + ); + + return ( + + + + {children} + + + + ); +}; describe('useFilteredObjectMetadataItems', () => { it('should findActiveObjectMetadataItemByNamePlural', async () => { diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useGetObjectRecordIdentifierByNameSingular.test.tsx b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useGetObjectRecordIdentifierByNameSingular.test.tsx index 415568c172..7aa392466c 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useGetObjectRecordIdentifierByNameSingular.test.tsx +++ b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useGetObjectRecordIdentifierByNameSingular.test.tsx @@ -1,11 +1,26 @@ import { renderHook } from '@testing-library/react'; -import { RecoilRoot, useSetRecoilState } from 'recoil'; +import { Provider as JotaiProvider } from 'jotai'; +import { RecoilRoot } from 'recoil'; import { useGetObjectRecordIdentifierByNameSingular } from '@/object-metadata/hooks/useGetObjectRecordIdentifierByNameSingular'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems'; +const Wrapper = ({ children }: { children: React.ReactNode }) => ( + + {children} + +); + describe('useGetObjectRecordIdentifierByNameSingular', () => { + beforeEach(() => { + jotaiStore.set( + objectMetadataItemsState.atom, + generatedMockObjectMetadataItems, + ); + }); + it('should work as expected', async () => { const { result, rerender } = renderHook( ({ @@ -15,17 +30,13 @@ describe('useGetObjectRecordIdentifierByNameSingular', () => { record: any; objectNameSingular: string; }) => { - const setMetadataItems = useSetRecoilState(objectMetadataItemsState); - - setMetadataItems(generatedMockObjectMetadataItems); - return useGetObjectRecordIdentifierByNameSingular(true)( record, objectNameSingular, ); }, { - wrapper: RecoilRoot, + wrapper: Wrapper, initialProps: { record: { id: 'recordId' } as any, objectNameSingular: 'viewSort', diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useGetRelationMetadata.test.tsx b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useGetRelationMetadata.test.tsx index aa987685e0..020721dec8 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useGetRelationMetadata.test.tsx +++ b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useGetRelationMetadata.test.tsx @@ -1,19 +1,30 @@ import { MockedProvider } from '@apollo/client/testing'; import { renderHook } from '@testing-library/react'; -import { type ReactNode, useEffect } from 'react'; -import { RecoilRoot, useSetRecoilState } from 'recoil'; +import { type ReactNode } from 'react'; +import { Provider as JotaiProvider } from 'jotai'; +import { RecoilRoot } from 'recoil'; import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMetadata'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems'; const Wrapper = ({ children }: { children: ReactNode }) => ( - - {children} - + + + {children} + + ); describe('useGetRelationMetadata', () => { + beforeEach(() => { + jotaiStore.set( + objectMetadataItemsState.atom, + generatedMockObjectMetadataItems, + ); + }); + it('should return correct properties', async () => { const objectMetadata = generatedMockObjectMetadataItems.find( (item) => item.nameSingular === 'person', @@ -22,21 +33,10 @@ describe('useGetRelationMetadata', () => { (field) => field.name === 'pointOfContactForOpportunities', )!; - const { result } = renderHook( - () => { - const setMetadataItems = useSetRecoilState(objectMetadataItemsState); - - useEffect(() => { - setMetadataItems(generatedMockObjectMetadataItems); - }, [setMetadataItems]); - - return useGetRelationMetadata(); - }, - { - wrapper: Wrapper, - initialProps: {}, - }, - ); + const { result } = renderHook(() => useGetRelationMetadata(), { + wrapper: Wrapper, + initialProps: {}, + }); const { relationFieldMetadataItem, diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useColumnDefinitionsFromObjectMetadata.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useColumnDefinitionsFromObjectMetadata.ts index b329559731..2939fe68be 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/useColumnDefinitionsFromObjectMetadata.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useColumnDefinitionsFromObjectMetadata.ts @@ -6,7 +6,7 @@ import { filterAvailableTableColumns } from '@/object-record/utils/filterAvailab import { availableFieldMetadataItemsForFilterFamilySelector } from '@/object-metadata/states/availableFieldMetadataItemsForFilterFamilySelector'; import { availableFieldMetadataItemsForSortFamilySelector } from '@/object-metadata/states/availableFieldMetadataItemsForSortFamilySelector'; -import { useRecoilValue } from 'recoil'; +import { useFamilySelectorValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilySelectorValueV2'; import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition'; export const useColumnDefinitionsFromObjectMetadata = ( @@ -20,16 +20,18 @@ export const useColumnDefinitionsFromObjectMetadata = ( field.id === objectMetadataItem.labelIdentifierFieldMetadataId), ); - const filterableFieldMetadataItems = useRecoilValue( - availableFieldMetadataItemsForFilterFamilySelector({ + const filterableFieldMetadataItems = useFamilySelectorValueV2( + availableFieldMetadataItemsForFilterFamilySelector, + { objectMetadataItemId: objectMetadataItem.id, - }), + }, ); - const sortableFieldMetadataItems = useRecoilValue( - availableFieldMetadataItemsForSortFamilySelector({ + const sortableFieldMetadataItems = useFamilySelectorValueV2( + availableFieldMetadataItemsForSortFamilySelector, + { objectMetadataItemId: objectMetadataItem.id, - }), + }, ); const restrictedFieldMetadataIds: string[] = []; diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useDoObjectMetadataItemsExist.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useDoObjectMetadataItemsExist.ts index 3c38ebf59f..daca4641d9 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/useDoObjectMetadataItemsExist.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useDoObjectMetadataItemsExist.ts @@ -1,14 +1,13 @@ -import { useRecoilValue } from 'recoil'; - import { objectMetadataItemsBySingularNameSelector } from '@/object-metadata/states/objectMetadataItemsBySingularNameSelector'; - +import { useFamilySelectorValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilySelectorValueV2'; import { isDefined } from 'twenty-shared/utils'; export const useDoObjectMetadataItemsExist = ( objectNameSingulars: string[], ) => { - const objectMetadataItems = useRecoilValue( - objectMetadataItemsBySingularNameSelector(objectNameSingulars), + const objectMetadataItems = useFamilySelectorValueV2( + objectMetadataItemsBySingularNameSelector, + objectNameSingulars, ); return objectMetadataItems.every((objectMetadataItem) => diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useFieldMetadataItemById.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useFieldMetadataItemById.ts index 47297467c2..5323a2637c 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/useFieldMetadataItemById.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useFieldMetadataItemById.ts @@ -1,9 +1,9 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { getFieldMetadataItemById } from '@/object-metadata/utils/getFieldMetadataItemById'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; export const useFieldMetadataItemById = (fieldMetadataId: string) => { - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); return getFieldMetadataItemById({ fieldMetadataId, diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useFieldMetadataItemByIdOrThrow.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useFieldMetadataItemByIdOrThrow.ts index 247a4fdd52..9f100d66dd 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/useFieldMetadataItemByIdOrThrow.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useFieldMetadataItemByIdOrThrow.ts @@ -1,9 +1,9 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { getFieldMetadataItemByIdOrThrow } from '@/object-metadata/utils/getFieldMetadataItemByIdOrThrow'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; export const useFieldMetadataItemByIdOrThrow = (fieldMetadataId: string) => { - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); return getFieldMetadataItemByIdOrThrow({ fieldMetadataId, diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useFilteredObjectMetadataItems.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useFilteredObjectMetadataItems.ts index 63a6efd159..a4e6d653ad 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/useFilteredObjectMetadataItems.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useFilteredObjectMetadataItems.ts @@ -1,10 +1,9 @@ -import { useRecoilValue } from 'recoil'; - import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { useMemo } from 'react'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; export const useFilteredObjectMetadataItems = () => { - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); const activeNonSystemObjectMetadataItems = useMemo( () => diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useGetFieldMetadataItemById.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useGetFieldMetadataItemById.ts index 8debd87c5e..789b44c3cd 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/useGetFieldMetadataItemById.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useGetFieldMetadataItemById.ts @@ -1,4 +1,5 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { getFieldMetadataItemByIdOrThrow } from '@/object-metadata/utils/getFieldMetadataItemByIdOrThrow'; @@ -11,11 +12,11 @@ type GetFieldMetadataItemByIdOrThrowResult = { export const useGetFieldMetadataItemByIdOrThrow = () => { const getFieldMetadataItemById = useRecoilCallback( - ({ snapshot }) => + () => (fieldMetadataId: string): GetFieldMetadataItemByIdOrThrowResult => { - const objectMetadataItems = snapshot - .getLoadable(objectMetadataItemsState) - .getValue(); + const objectMetadataItems = jotaiStore.get( + objectMetadataItemsState.atom, + ); return getFieldMetadataItemByIdOrThrow({ fieldMetadataId, diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useGetObjectMetadataItemById.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useGetObjectMetadataItemById.ts index df1ce4d25c..87e253378b 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/useGetObjectMetadataItemById.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useGetObjectMetadataItemById.ts @@ -1,10 +1,9 @@ -import { useRecoilValue } from 'recoil'; - import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { CustomError, isDefined } from 'twenty-shared/utils'; export const useGetObjectMetadataItemById = () => { - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); const getObjectMetadataItemById = (objectId: string) => { const objectMetadataItem = objectMetadataItems.find( diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useGetObjectRecordIdentifierByNameSingular.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useGetObjectRecordIdentifierByNameSingular.ts index b6a5aa665c..e6c1a8809b 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/useGetObjectRecordIdentifierByNameSingular.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useGetObjectRecordIdentifierByNameSingular.ts @@ -1,13 +1,12 @@ -import { useRecoilValue } from 'recoil'; - import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { getObjectRecordIdentifier } from '@/object-metadata/utils/getObjectRecordIdentifier'; import { type ObjectRecordIdentifier } from '@/object-record/types/ObjectRecordIdentifier'; export const useGetObjectRecordIdentifierByNameSingular = ( allowRequestsToTwentyIcons: boolean, ) => { - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); return (record: any, objectNameSingular: string): ObjectRecordIdentifier => { const objectMetadataItem = objectMetadataItems.find( diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useGetRelationMetadata.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useGetRelationMetadata.ts index 677228eb6b..ef4f00c026 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/useGetRelationMetadata.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useGetRelationMetadata.ts @@ -1,47 +1,44 @@ -import { useRecoilCallback } from 'recoil'; +import { useCallback } from 'react'; import { objectMetadataItemFamilySelector } from '@/object-metadata/states/objectMetadataItemFamilySelector'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { FieldMetadataType } from '~/generated-metadata/graphql'; import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; export const useGetRelationMetadata = () => - useRecoilCallback( - ({ snapshot }) => - ({ - fieldMetadataItem, - }: { - fieldMetadataItem: Pick; - }) => { - if (fieldMetadataItem.type !== FieldMetadataType.RELATION) return null; + useCallback( + ({ + fieldMetadataItem, + }: { + fieldMetadataItem: Pick; + }) => { + if (fieldMetadataItem.type !== FieldMetadataType.RELATION) return null; - const relation = fieldMetadataItem.relation; + const relation = fieldMetadataItem.relation; - if (!relation) return null; + if (!relation) return null; - const relationObjectMetadataItem = snapshot - .getLoadable( - objectMetadataItemFamilySelector({ - objectName: relation.targetObjectMetadata.nameSingular, - objectNameType: 'singular', - }), - ) - .getValue(); + const relationObjectMetadataItem = jotaiStore.get( + objectMetadataItemFamilySelector.selectorFamily({ + objectName: relation.targetObjectMetadata.nameSingular, + objectNameType: 'singular', + }), + ); - if (!relationObjectMetadataItem) return null; + if (!relationObjectMetadataItem) return null; - const relationFieldMetadataItem = - relationObjectMetadataItem.fields.find( - (field) => field.id === relation.targetFieldMetadata.id, - ); + const relationFieldMetadataItem = relationObjectMetadataItem.fields.find( + (field) => field.id === relation.targetFieldMetadata.id, + ); - if (!relationFieldMetadataItem) return null; + if (!relationFieldMetadataItem) return null; - return { - relationFieldMetadataItem, - relationObjectMetadataItem, - relationType: relation.type, - }; - }, + return { + relationFieldMetadataItem, + relationObjectMetadataItem, + relationType: relation.type, + }; + }, [], ); diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useLoadMockedObjectMetadataItems.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useLoadMockedObjectMetadataItems.ts index 9c6d879d59..f8d3b6d5f3 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/useLoadMockedObjectMetadataItems.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useLoadMockedObjectMetadataItems.ts @@ -1,36 +1,29 @@ import { isAppEffectRedirectEnabledState } from '@/app/states/isAppEffectRedirectEnabledState'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; -import { shouldAppBeLoadingState } from '@/object-metadata/states/shouldAppBeLoadingState'; -import { useRecoilCallback } from 'recoil'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; +import { useCallback } from 'react'; import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; export const useLoadMockedObjectMetadataItems = () => { - const loadMockedObjectMetadataItems = useRecoilCallback( - ({ set, snapshot }) => - async () => { - if ( - !isDeeplyEqual( - snapshot.getLoadable(objectMetadataItemsState).getValue(), - generatedMockObjectMetadataItems, - ) - ) { - set(objectMetadataItemsState, generatedMockObjectMetadataItems); - } + const loadMockedObjectMetadataItems = useCallback(async () => { + if ( + !isDeeplyEqual( + jotaiStore.get(objectMetadataItemsState.atom), + generatedMockObjectMetadataItems, + ) + ) { + jotaiStore.set( + objectMetadataItemsState.atom, + generatedMockObjectMetadataItems, + ); + } - if (snapshot.getLoadable(shouldAppBeLoadingState).getValue() === true) { - set(shouldAppBeLoadingState, false); - } + if (jotaiStore.get(isAppEffectRedirectEnabledState.atom) === false) { + jotaiStore.set(isAppEffectRedirectEnabledState.atom, true); + } + }, []); - if ( - snapshot.getLoadable(isAppEffectRedirectEnabledState).getValue() === - false - ) { - set(isAppEffectRedirectEnabledState, true); - } - }, - [], - ); return { loadMockedObjectMetadataItems, }; diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useObjectMetadataItem.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useObjectMetadataItem.ts index 0e247003a5..3d8b00a2fd 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/useObjectMetadataItem.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useObjectMetadataItem.ts @@ -1,8 +1,8 @@ -import { useRecoilValue } from 'recoil'; - import { ObjectMetadataItemNotFoundError } from '@/object-metadata/errors/ObjectMetadataNotFoundError'; import { objectMetadataItemFamilySelector } from '@/object-metadata/states/objectMetadataItemFamilySelector'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { useFamilySelectorValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilySelectorValueV2'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { isDefined } from 'twenty-shared/utils'; import { type ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier'; @@ -10,14 +10,15 @@ import { type ObjectMetadataItemIdentifier } from '@/object-metadata/types/Objec export const useObjectMetadataItem = ({ objectNameSingular, }: ObjectMetadataItemIdentifier) => { - const objectMetadataItem = useRecoilValue( - objectMetadataItemFamilySelector({ + const objectMetadataItem = useFamilySelectorValueV2( + objectMetadataItemFamilySelector, + { objectName: objectNameSingular, objectNameType: 'singular', - }), + }, ); - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); if (!isDefined(objectMetadataItem)) { throw new ObjectMetadataItemNotFoundError( diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useObjectMetadataItemById.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useObjectMetadataItemById.ts index 88c5cb6977..e19d93dc37 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/useObjectMetadataItemById.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useObjectMetadataItemById.ts @@ -1,6 +1,5 @@ -import { useRecoilValue } from 'recoil'; - import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { CustomError, isDefined } from 'twenty-shared/utils'; export const useObjectMetadataItemById = ({ @@ -8,7 +7,7 @@ export const useObjectMetadataItemById = ({ }: { objectId: string; }) => { - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); const objectMetadataItem = objectMetadataItems.find( (objectMetadataItem) => objectMetadataItem.id === objectId, diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useObjectMetadataItems.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useObjectMetadataItems.ts index 215d61200c..536bd713ad 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/useObjectMetadataItems.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useObjectMetadataItems.ts @@ -1,9 +1,9 @@ -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; export const useObjectMetadataItems = () => { - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); return { objectMetadataItems, diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useObjectNamePluralFromSingular.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useObjectNamePluralFromSingular.ts index d7b7ebad64..5b489b9ee2 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/useObjectNamePluralFromSingular.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useObjectNamePluralFromSingular.ts @@ -1,6 +1,5 @@ -import { useRecoilValue } from 'recoil'; - import { objectMetadataItemFamilySelector } from '@/object-metadata/states/objectMetadataItemFamilySelector'; +import { useFamilySelectorValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilySelectorValueV2'; import { isDefined } from 'twenty-shared/utils'; export const useObjectNamePluralFromSingular = ({ @@ -8,11 +7,12 @@ export const useObjectNamePluralFromSingular = ({ }: { objectNameSingular: string; }) => { - const objectMetadataItem = useRecoilValue( - objectMetadataItemFamilySelector({ + const objectMetadataItem = useFamilySelectorValueV2( + objectMetadataItemFamilySelector, + { objectName: objectNameSingular, objectNameType: 'singular', - }), + }, ); if (!isDefined(objectMetadataItem)) { diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useObjectNameSingularFromPlural.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useObjectNameSingularFromPlural.ts index 4218e1c876..2223d9231e 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/useObjectNameSingularFromPlural.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useObjectNameSingularFromPlural.ts @@ -1,6 +1,5 @@ -import { useRecoilValue } from 'recoil'; - import { objectMetadataItemFamilySelector } from '@/object-metadata/states/objectMetadataItemFamilySelector'; +import { useFamilySelectorValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilySelectorValueV2'; import { isDefined } from 'twenty-shared/utils'; export const useObjectNameSingularFromPlural = ({ @@ -8,11 +7,12 @@ export const useObjectNameSingularFromPlural = ({ }: { objectNamePlural: string; }) => { - const objectMetadataItem = useRecoilValue( - objectMetadataItemFamilySelector({ + const objectMetadataItem = useFamilySelectorValueV2( + objectMetadataItemFamilySelector, + { objectName: objectNamePlural, objectNameType: 'plural', - }), + }, ); if (!isDefined(objectMetadataItem)) { diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useRefreshObjectMetadataItems.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useRefreshObjectMetadataItems.ts index ca0883b3ed..0296621f1c 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/useRefreshObjectMetadataItems.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useRefreshObjectMetadataItems.ts @@ -2,12 +2,12 @@ import { isAppEffectRedirectEnabledState } from '@/app/states/isAppEffectRedirec import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState'; import { FIND_MANY_OBJECT_METADATA_ITEMS } from '@/object-metadata/graphql/queries'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; -import { shouldAppBeLoadingState } from '@/object-metadata/states/shouldAppBeLoadingState'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { enrichObjectMetadataItemsWithPermissions } from '@/object-metadata/utils/enrichObjectMetadataItemsWithPermissions'; import { mapPaginatedObjectMetadataItemsToObjectMetadataItems } from '@/object-metadata/utils/mapPaginatedObjectMetadataItemsToObjectMetadataItems'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { type FetchPolicy, useApolloClient } from '@apollo/client'; -import { useRecoilCallback } from 'recoil'; +import { useCallback } from 'react'; import { type ObjectPermissions } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { type ObjectMetadataItemsQuery } from '~/generated-metadata/graphql'; @@ -17,6 +17,58 @@ export const useRefreshObjectMetadataItems = ( fetchPolicy: FetchPolicy = 'network-only', ) => { const client = useApolloClient(); + + const replaceObjectMetadataItemIfDifferent = useCallback( + ( + toSetObjectMetadataItems: Omit< + ObjectMetadataItem, + 'readableFields' | 'updatableFields' + >[], + ) => { + const currentUserWorkspace = jotaiStore.get( + currentUserWorkspaceState.atom, + ); + + if (!isDefined(currentUserWorkspace)) { + return; + } + + const objectPermissionsByObjectMetadataId = + currentUserWorkspace.objectsPermissions.reduce( + (acc, objectPermission) => { + acc[objectPermission.objectMetadataId] = objectPermission; + return acc; + }, + {} as Record< + string, + ObjectPermissions & { objectMetadataId: string } + >, + ); + + const newObjectMetadataItems = enrichObjectMetadataItemsWithPermissions({ + objectMetadataItems: toSetObjectMetadataItems, + objectPermissionsByObjectMetadataId, + }); + + if ( + !isDeeplyEqual( + jotaiStore.get(objectMetadataItemsState.atom), + newObjectMetadataItems, + ) && + newObjectMetadataItems.length > 0 + ) { + jotaiStore.set(objectMetadataItemsState.atom, newObjectMetadataItems); + } + + if (jotaiStore.get(isAppEffectRedirectEnabledState.atom) === false) { + jotaiStore.set(isAppEffectRedirectEnabledState.atom, true); + } + + return newObjectMetadataItems; + }, + [], + ); + const refreshObjectMetadataItems = async () => { const objectMetadataItemsResult = await client.query({ @@ -33,67 +85,6 @@ export const useRefreshObjectMetadataItems = ( return replaceObjectMetadataItemIfDifferent(objectMetadataItems); }; - const replaceObjectMetadataItemIfDifferent = useRecoilCallback( - ({ set, snapshot }) => - ( - toSetObjectMetadataItems: Omit< - ObjectMetadataItem, - 'readableFields' | 'updatableFields' - >[], - ) => { - const currentUserWorkspace = snapshot - .getLoadable(currentUserWorkspaceState) - .getValue(); - - if (!isDefined(currentUserWorkspace)) { - return; - } - - const objectPermissionsByObjectMetadataId = - currentUserWorkspace.objectsPermissions.reduce( - (acc, objectPermission) => { - acc[objectPermission.objectMetadataId] = objectPermission; - return acc; - }, - {} as Record< - string, - ObjectPermissions & { objectMetadataId: string } - >, - ); - - const newObjectMetadataItems = enrichObjectMetadataItemsWithPermissions( - { - objectMetadataItems: toSetObjectMetadataItems, - objectPermissionsByObjectMetadataId, - }, - ); - - if ( - !isDeeplyEqual( - snapshot.getLoadable(objectMetadataItemsState).getValue(), - newObjectMetadataItems, - ) && - newObjectMetadataItems.length > 0 - ) { - set(objectMetadataItemsState, newObjectMetadataItems); - } - - if (snapshot.getLoadable(shouldAppBeLoadingState).getValue() === true) { - set(shouldAppBeLoadingState, false); - } - - if ( - snapshot.getLoadable(isAppEffectRedirectEnabledState).getValue() === - false - ) { - set(isAppEffectRedirectEnabledState, true); - } - - return newObjectMetadataItems; - }, - [], - ); - return { refreshObjectMetadataItems, }; diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useUpdateOneFieldMetadataItem.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useUpdateOneFieldMetadataItem.ts index 07d97ecddd..0c27ffae2c 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/useUpdateOneFieldMetadataItem.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useUpdateOneFieldMetadataItem.ts @@ -9,9 +9,9 @@ import { lastFieldMetadataItemUpdateState } from '@/object-metadata/states/lastF import { type MetadataRequestResult } from '@/object-metadata/types/MetadataRequestResult.type'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { useRefreshCoreViewsByObjectMetadataId } from '@/views/hooks/useRefreshCoreViewsByObjectMetadataId'; +import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; import { ApolloError } from '@apollo/client'; import { t } from '@lingui/core/macro'; -import { useSetRecoilState } from 'recoil'; import { CrudOperationType } from 'twenty-shared/types'; import { v4 as uuidv4 } from 'uuid'; @@ -29,7 +29,7 @@ export const useUpdateOneFieldMetadataItem = () => { const { enqueueErrorSnackBar } = useSnackBar(); - const setLastFieldMetadataItemUpdate = useSetRecoilState( + const setLastFieldMetadataItemUpdate = useSetRecoilStateV2( lastFieldMetadataItemUpdateState, ); diff --git a/packages/twenty-front/src/modules/object-metadata/states/availableFieldMetadataItemsForFilterFamilySelector.ts b/packages/twenty-front/src/modules/object-metadata/states/availableFieldMetadataItemsForFilterFamilySelector.ts index 69cb045926..feaaf835f6 100644 --- a/packages/twenty-front/src/modules/object-metadata/states/availableFieldMetadataItemsForFilterFamilySelector.ts +++ b/packages/twenty-front/src/modules/object-metadata/states/availableFieldMetadataItemsForFilterFamilySelector.ts @@ -1,41 +1,43 @@ import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; import { getFilterFilterableFieldMetadataItems } from '@/object-metadata/utils/getFilterFilterableFieldMetadataItems'; +import { createFamilySelectorV2 } from '@/ui/utilities/state/jotai/utils/createFamilySelectorV2'; import { checkIfFeatureFlagIsEnabledOnWorkspace } from '@/workspace/utils/checkIfFeatureFlagIsEnabledOnWorkspace'; -import { selectorFamily } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { FeatureFlagKey } from '~/generated-metadata/graphql'; export const availableFieldMetadataItemsForFilterFamilySelector = - selectorFamily({ - key: 'availableFieldMetadataItemsForFilterFamilySelector', - get: - ({ objectMetadataItemId }: { objectMetadataItemId: string }) => - ({ get }) => { - const currentWorkspace = get(currentWorkspaceState); - const objectMetadataItems = get(objectMetadataItemsState); + createFamilySelectorV2( + { + key: 'availableFieldMetadataItemsForFilterFamilySelector', + get: + ({ objectMetadataItemId }: { objectMetadataItemId: string }) => + ({ get }) => { + const currentWorkspace = get(currentWorkspaceState); + const objectMetadataItems = get(objectMetadataItemsState); - const objectMetadataItem = objectMetadataItems.find( - (item) => item.id === objectMetadataItemId, - ); - if (!isDefined(objectMetadataItem)) { - return []; - } + const objectMetadataItem = objectMetadataItems.find( + (item) => item.id === objectMetadataItemId, + ); + if (!isDefined(objectMetadataItem)) { + return []; + } - const isJsonFeatureFlagEnabled = checkIfFeatureFlagIsEnabledOnWorkspace( - FeatureFlagKey.IS_JSON_FILTER_ENABLED, - currentWorkspace, - ); + const isJsonFeatureFlagEnabled = + checkIfFeatureFlagIsEnabledOnWorkspace( + FeatureFlagKey.IS_JSON_FILTER_ENABLED, + currentWorkspace, + ); - const filterFilterableFieldMetadataItems = - getFilterFilterableFieldMetadataItems({ - isJsonFilterEnabled: isJsonFeatureFlagEnabled, - }); + const filterFilterableFieldMetadataItems = + getFilterFilterableFieldMetadataItems({ + isJsonFilterEnabled: isJsonFeatureFlagEnabled, + }); - const availableFieldMetadataItemsForFilter = - objectMetadataItem.readableFields.filter( + return objectMetadataItem.readableFields.filter( filterFilterableFieldMetadataItems, ); - return availableFieldMetadataItemsForFilter; - }, - }); + }, + }, + ); diff --git a/packages/twenty-front/src/modules/object-metadata/states/availableFieldMetadataItemsForSortFamilySelector.ts b/packages/twenty-front/src/modules/object-metadata/states/availableFieldMetadataItemsForSortFamilySelector.ts index 18f619b6d5..db609a94b8 100644 --- a/packages/twenty-front/src/modules/object-metadata/states/availableFieldMetadataItemsForSortFamilySelector.ts +++ b/packages/twenty-front/src/modules/object-metadata/states/availableFieldMetadataItemsForSortFamilySelector.ts @@ -1,28 +1,29 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; import { filterSortableFieldMetadataItems } from '@/object-metadata/utils/filterSortableFieldMetadataItems'; -import { selectorFamily } from 'recoil'; +import { createFamilySelectorV2 } from '@/ui/utilities/state/jotai/utils/createFamilySelectorV2'; import { isDefined } from 'twenty-shared/utils'; -export const availableFieldMetadataItemsForSortFamilySelector = selectorFamily({ - key: 'availableFieldMetadataItemsForSortFamilySelector', - get: - ({ objectMetadataItemId }: { objectMetadataItemId: string }) => - ({ get }) => { - const objectMetadataItems = get(objectMetadataItemsState); +export const availableFieldMetadataItemsForSortFamilySelector = + createFamilySelectorV2( + { + key: 'availableFieldMetadataItemsForSortFamilySelector', + get: + ({ objectMetadataItemId }: { objectMetadataItemId: string }) => + ({ get }) => { + const objectMetadataItems = get(objectMetadataItemsState); - const objectMetadataItem = objectMetadataItems.find( - (item) => item.id === objectMetadataItemId, - ); + const objectMetadataItem = objectMetadataItems.find( + (item) => item.id === objectMetadataItemId, + ); - if (!isDefined(objectMetadataItem)) { - return []; - } + if (!isDefined(objectMetadataItem)) { + return []; + } - const availableFieldMetadataItemsForSort = - objectMetadataItem.readableFields.filter( - filterSortableFieldMetadataItems, - ); - - return availableFieldMetadataItemsForSort; + return objectMetadataItem.readableFields.filter( + filterSortableFieldMetadataItems, + ); + }, }, -}); + ); diff --git a/packages/twenty-front/src/modules/object-metadata/states/fieldMetadataItemByIdSelector.ts b/packages/twenty-front/src/modules/object-metadata/states/fieldMetadataItemByIdSelector.ts index b960594730..8be1d00a64 100644 --- a/packages/twenty-front/src/modules/object-metadata/states/fieldMetadataItemByIdSelector.ts +++ b/packages/twenty-front/src/modules/object-metadata/states/fieldMetadataItemByIdSelector.ts @@ -1,9 +1,9 @@ import { flattenedFieldMetadataItemsSelector } from '@/object-metadata/states/flattenedFieldMetadataItemsSelector'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; -import { selectorFamily } from 'recoil'; +import { createFamilySelectorV2 } from '@/ui/utilities/state/jotai/utils/createFamilySelectorV2'; import { findById, isDefined } from 'twenty-shared/utils'; -export const fieldMetadataItemByIdSelector = selectorFamily({ +export const fieldMetadataItemByIdSelector = createFamilySelectorV2({ key: 'fieldMetadataItemByIdSelector', get: ({ fieldMetadataItemId }: { fieldMetadataItemId: string }) => diff --git a/packages/twenty-front/src/modules/object-metadata/states/flattenedFieldMetadataItemsSelector.ts b/packages/twenty-front/src/modules/object-metadata/states/flattenedFieldMetadataItemsSelector.ts index ebc99956d0..aa08c01a2e 100644 --- a/packages/twenty-front/src/modules/object-metadata/states/flattenedFieldMetadataItemsSelector.ts +++ b/packages/twenty-front/src/modules/object-metadata/states/flattenedFieldMetadataItemsSelector.ts @@ -1,18 +1,16 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; -import { selector } from 'recoil'; +import { createSelectorV2 } from '@/ui/utilities/state/jotai/utils/createSelectorV2'; -export const flattenedFieldMetadataItemsSelector = selector< +export const flattenedFieldMetadataItemsSelector = createSelectorV2< FieldMetadataItem[] >({ key: 'flattenedFieldMetadataItemsSelector', get: ({ get }) => { const objectMetadataItems = get(objectMetadataItemsState); - const flattenedFieldMetadataItems = objectMetadataItems.flatMap( + return objectMetadataItems.flatMap( (objectMetadataItem) => objectMetadataItem.fields, ); - - return flattenedFieldMetadataItems; }, }); diff --git a/packages/twenty-front/src/modules/object-metadata/states/flattenedReadableFieldMetadataItemIdsSelector.ts b/packages/twenty-front/src/modules/object-metadata/states/flattenedReadableFieldMetadataItemIdsSelector.ts index 5d1aca5ed2..e025980723 100644 --- a/packages/twenty-front/src/modules/object-metadata/states/flattenedReadableFieldMetadataItemIdsSelector.ts +++ b/packages/twenty-front/src/modules/object-metadata/states/flattenedReadableFieldMetadataItemIdsSelector.ts @@ -1,18 +1,16 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; -import { selector } from 'recoil'; +import { createSelectorV2 } from '@/ui/utilities/state/jotai/utils/createSelectorV2'; -export const flattenedReadableFieldMetadataItemsSelector = selector< +export const flattenedReadableFieldMetadataItemsSelector = createSelectorV2< FieldMetadataItem[] >({ key: 'flattenedReadableFieldMetadataItemsSelector', get: ({ get }) => { const objectMetadataItems = get(objectMetadataItemsState); - const flattenedReadableFieldMetadataItems = objectMetadataItems.flatMap( + return objectMetadataItems.flatMap( (objectMetadataItem) => objectMetadataItem.readableFields, ); - - return flattenedReadableFieldMetadataItems; }, }); diff --git a/packages/twenty-front/src/modules/object-metadata/states/isFieldMetadataItemFilterableAndSortableSelector.ts b/packages/twenty-front/src/modules/object-metadata/states/isFieldMetadataItemFilterableAndSortableSelector.ts index 5d5466047d..4262d0dbd5 100644 --- a/packages/twenty-front/src/modules/object-metadata/states/isFieldMetadataItemFilterableAndSortableSelector.ts +++ b/packages/twenty-front/src/modules/object-metadata/states/isFieldMetadataItemFilterableAndSortableSelector.ts @@ -2,65 +2,64 @@ import { availableFieldMetadataItemsForFilterFamilySelector } from '@/object-met import { availableFieldMetadataItemsForSortFamilySelector } from '@/object-metadata/states/availableFieldMetadataItemsForSortFamilySelector'; import { flattenedFieldMetadataItemsSelector } from '@/object-metadata/states/flattenedFieldMetadataItemsSelector'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; -import { selectorFamily } from 'recoil'; +import { createFamilySelectorV2 } from '@/ui/utilities/state/jotai/utils/createFamilySelectorV2'; import { findById, isDefined } from 'twenty-shared/utils'; -export const isFieldMetadataItemFilterableAndSortableSelector = selectorFamily({ - key: 'isFieldMetadataItemFilterableAndSortableSelector', - get: - ({ fieldMetadataItemId }: { fieldMetadataItemId: string }) => - ({ get }) => { - const objectMetadataItems = get(objectMetadataItemsState); - const flattenedFieldMetadataItems = get( - flattenedFieldMetadataItemsSelector, - ); +export const isFieldMetadataItemFilterableAndSortableSelector = + createFamilySelectorV2({ + key: 'isFieldMetadataItemFilterableAndSortableSelector', + get: + ({ fieldMetadataItemId }: { fieldMetadataItemId: string }) => + ({ get }) => { + const objectMetadataItems = get(objectMetadataItemsState); + const flattenedFieldMetadataItems = get( + flattenedFieldMetadataItemsSelector, + ); - const foundObjectMetadataItem = objectMetadataItems.find( - (objectMetadataItem) => - objectMetadataItem.fields.some(findById(fieldMetadataItemId)), - ); + const foundObjectMetadataItem = objectMetadataItems.find( + (objectMetadataItem) => + objectMetadataItem.fields.some(findById(fieldMetadataItemId)), + ); + + if (!isDefined(foundObjectMetadataItem)) { + return { + isFilterable: false, + isSortable: false, + }; + } + + const foundFieldMetadataItem = flattenedFieldMetadataItems.find( + findById(fieldMetadataItemId), + ); + + if (!isDefined(foundFieldMetadataItem)) { + return { + isFilterable: false, + isSortable: false, + }; + } + + const filterableFieldMetadataItems = get( + availableFieldMetadataItemsForFilterFamilySelector, + { objectMetadataItemId: foundObjectMetadataItem.id }, + ); + + const sortableFieldMetadataItems = get( + availableFieldMetadataItemsForSortFamilySelector, + { objectMetadataItemId: foundObjectMetadataItem.id }, + ); + + const isFilterable = filterableFieldMetadataItems.some( + findById(fieldMetadataItemId), + ); + + const isSortable = sortableFieldMetadataItems.some( + findById(fieldMetadataItemId), + ); - if (!isDefined(foundObjectMetadataItem)) { return { - isFilterable: false, - isSortable: false, + isFilterable, + isSortable, }; - } - - const foundFieldMetadataItem = flattenedFieldMetadataItems.find( - findById(fieldMetadataItemId), - ); - - if (!isDefined(foundFieldMetadataItem)) { - return { - isFilterable: false, - isSortable: false, - }; - } - - const filterableFieldMetadataItems = get( - availableFieldMetadataItemsForFilterFamilySelector({ - objectMetadataItemId: foundObjectMetadataItem.id, - }), - ); - - const sortableFieldMetadataItems = get( - availableFieldMetadataItemsForSortFamilySelector({ - objectMetadataItemId: foundObjectMetadataItem.id, - }), - ); - - const isFilterable = filterableFieldMetadataItems.some( - findById(fieldMetadataItemId), - ); - - const isSortable = sortableFieldMetadataItems.some( - findById(fieldMetadataItemId), - ); - - return { - isFilterable, - isSortable, - }; - }, -}); + }, + }); diff --git a/packages/twenty-front/src/modules/object-metadata/states/isFieldMetadataItemLabelIdentifierSelector.ts b/packages/twenty-front/src/modules/object-metadata/states/isFieldMetadataItemLabelIdentifierSelector.ts index c55b0ae0b0..4ac9f68e0c 100644 --- a/packages/twenty-front/src/modules/object-metadata/states/isFieldMetadataItemLabelIdentifierSelector.ts +++ b/packages/twenty-front/src/modules/object-metadata/states/isFieldMetadataItemLabelIdentifierSelector.ts @@ -1,37 +1,36 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { isLabelIdentifierField } from '@/object-metadata/utils/isLabelIdentifierField'; -import { selectorFamily } from 'recoil'; +import { createFamilySelectorV2 } from '@/ui/utilities/state/jotai/utils/createFamilySelectorV2'; import { findById, isDefined } from 'twenty-shared/utils'; -export const isFieldMetadataItemLabelIdentifierSelector = selectorFamily({ - key: 'isFieldMetadataItemLabelIdentifierSelector', - get: - ({ fieldMetadataItemId }: { fieldMetadataItemId: string }) => - ({ get }) => { - const objectMetadataItems = get(objectMetadataItemsState); +export const isFieldMetadataItemLabelIdentifierSelector = + createFamilySelectorV2({ + key: 'isFieldMetadataItemLabelIdentifierSelector', + get: + ({ fieldMetadataItemId }: { fieldMetadataItemId: string }) => + ({ get }) => { + const objectMetadataItems = get(objectMetadataItemsState); - const foundObjectMetadataItem = objectMetadataItems.find( - (objectMetadataItem) => - objectMetadataItem.fields.some(findById(fieldMetadataItemId)), - ); + const foundObjectMetadataItem = objectMetadataItems.find( + (objectMetadataItem) => + objectMetadataItem.fields.some(findById(fieldMetadataItemId)), + ); - if (!isDefined(foundObjectMetadataItem)) { - return false; - } + if (!isDefined(foundObjectMetadataItem)) { + return false; + } - const foundFieldMetadataItem = foundObjectMetadataItem.fields.find( - findById(fieldMetadataItemId), - ); + const foundFieldMetadataItem = foundObjectMetadataItem.fields.find( + findById(fieldMetadataItemId), + ); - if (!isDefined(foundFieldMetadataItem)) { - return false; - } + if (!isDefined(foundFieldMetadataItem)) { + return false; + } - const fieldIsLabelIdentifier = isLabelIdentifierField({ - fieldMetadataItem: foundFieldMetadataItem, - objectMetadataItem: foundObjectMetadataItem, - }); - - return fieldIsLabelIdentifier; - }, -}); + return isLabelIdentifierField({ + fieldMetadataItem: foundFieldMetadataItem, + objectMetadataItem: foundObjectMetadataItem, + }); + }, + }); diff --git a/packages/twenty-front/src/modules/object-metadata/states/labelIdentifierFieldMetadataItemSelector.ts b/packages/twenty-front/src/modules/object-metadata/states/labelIdentifierFieldMetadataItemSelector.ts index 30840bf25a..75f5071d25 100644 --- a/packages/twenty-front/src/modules/object-metadata/states/labelIdentifierFieldMetadataItemSelector.ts +++ b/packages/twenty-front/src/modules/object-metadata/states/labelIdentifierFieldMetadataItemSelector.ts @@ -1,9 +1,9 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { isLabelIdentifierField } from '@/object-metadata/utils/isLabelIdentifierField'; -import { selectorFamily } from 'recoil'; +import { createFamilySelectorV2 } from '@/ui/utilities/state/jotai/utils/createFamilySelectorV2'; import { isDefined } from 'twenty-shared/utils'; -export const labelIdentifierFieldMetadataItemSelector = selectorFamily({ +export const labelIdentifierFieldMetadataItemSelector = createFamilySelectorV2({ key: 'labelIdentifierFieldMetadataItemSelector', get: ({ objectMetadataItemId }: { objectMetadataItemId: string }) => @@ -18,14 +18,11 @@ export const labelIdentifierFieldMetadataItemSelector = selectorFamily({ return undefined; } - const labelIdentifierFieldMetadataItem = objectMetadataItem.fields.find( - (fieldMetadataItemToFind) => - isLabelIdentifierField({ - fieldMetadataItem: fieldMetadataItemToFind, - objectMetadataItem: objectMetadataItem, - }), + return objectMetadataItem.fields.find((fieldMetadataItemToFind) => + isLabelIdentifierField({ + fieldMetadataItem: fieldMetadataItemToFind, + objectMetadataItem: objectMetadataItem, + }), ); - - return labelIdentifierFieldMetadataItem; }, }); diff --git a/packages/twenty-front/src/modules/object-metadata/states/lastFieldMetadataItemUpdateState.ts b/packages/twenty-front/src/modules/object-metadata/states/lastFieldMetadataItemUpdateState.ts index d738f33784..f6a7c42608 100644 --- a/packages/twenty-front/src/modules/object-metadata/states/lastFieldMetadataItemUpdateState.ts +++ b/packages/twenty-front/src/modules/object-metadata/states/lastFieldMetadataItemUpdateState.ts @@ -1,8 +1,8 @@ import type { FieldMetadataItemUpdate } from '@/object-metadata/types/FieldMetadataItemUpdate'; -import { createState } from '@/ui/utilities/state/utils/createState'; +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; export const lastFieldMetadataItemUpdateState = - createState({ + createStateV2({ key: 'lastFieldMetadataItemUpdateState', defaultValue: null, }); diff --git a/packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemFamilySelector.ts b/packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemFamilySelector.ts index 639a0399da..c0e57e00c1 100644 --- a/packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemFamilySelector.ts +++ b/packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemFamilySelector.ts @@ -1,14 +1,13 @@ -import { selectorFamily } from 'recoil'; - import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { createFamilySelectorV2 } from '@/ui/utilities/state/jotai/utils/createFamilySelectorV2'; type ObjectMetadataItemSelector = { objectName: string; objectNameType: 'singular' | 'plural'; }; -export const objectMetadataItemFamilySelector = selectorFamily< +export const objectMetadataItemFamilySelector = createFamilySelectorV2< ObjectMetadataItem | null, ObjectMetadataItemSelector >({ diff --git a/packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemsByNamePluralMapSelector.ts b/packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemsByNamePluralMapSelector.ts index f8a173cdab..6365c9d689 100644 --- a/packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemsByNamePluralMapSelector.ts +++ b/packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemsByNamePluralMapSelector.ts @@ -1,9 +1,8 @@ -import { selector } from 'recoil'; - import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { createSelectorV2 } from '@/ui/utilities/state/jotai/utils/createSelectorV2'; -export const objectMetadataItemsByNamePluralMapSelector = selector< +export const objectMetadataItemsByNamePluralMapSelector = createSelectorV2< Map >({ key: 'objectMetadataItemsByNamePluralMapSelector', diff --git a/packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemsByNameSingularMapSelector.ts b/packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemsByNameSingularMapSelector.ts index 7f2f4c5449..d1db30c0b7 100644 --- a/packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemsByNameSingularMapSelector.ts +++ b/packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemsByNameSingularMapSelector.ts @@ -1,9 +1,8 @@ -import { selector } from 'recoil'; - import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { createSelectorV2 } from '@/ui/utilities/state/jotai/utils/createSelectorV2'; -export const objectMetadataItemsByNameSingularMapSelector = selector< +export const objectMetadataItemsByNameSingularMapSelector = createSelectorV2< Map >({ key: 'objectMetadataItemsByNameSingularMapSelector', diff --git a/packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemsBySingularNameSelector.ts b/packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemsBySingularNameSelector.ts index 361de5e971..0d8ecbbd7d 100644 --- a/packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemsBySingularNameSelector.ts +++ b/packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemsBySingularNameSelector.ts @@ -1,10 +1,9 @@ -import { selectorFamily } from 'recoil'; - import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { createFamilySelectorV2 } from '@/ui/utilities/state/jotai/utils/createFamilySelectorV2'; import { isDefined } from 'twenty-shared/utils'; -export const objectMetadataItemsBySingularNameSelector = selectorFamily< +export const objectMetadataItemsBySingularNameSelector = createFamilySelectorV2< ObjectMetadataItem[], string[] >({ diff --git a/packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemsState.ts b/packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemsState.ts index a78693c1c8..f87ebe95fe 100644 --- a/packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemsState.ts +++ b/packages/twenty-front/src/modules/object-metadata/states/objectMetadataItemsState.ts @@ -1,7 +1,7 @@ import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { createState } from '@/ui/utilities/state/utils/createState'; +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; -export const objectMetadataItemsState = createState({ +export const objectMetadataItemsState = createStateV2({ key: 'objectMetadataItemsState', defaultValue: [], }); diff --git a/packages/twenty-front/src/modules/object-metadata/states/shouldAppBeLoadingState.ts b/packages/twenty-front/src/modules/object-metadata/states/shouldAppBeLoadingState.ts index 9ccb9ae3c6..dcf7c2c697 100644 --- a/packages/twenty-front/src/modules/object-metadata/states/shouldAppBeLoadingState.ts +++ b/packages/twenty-front/src/modules/object-metadata/states/shouldAppBeLoadingState.ts @@ -1,5 +1,6 @@ -import { createState } from '@/ui/utilities/state/utils/createState'; -export const shouldAppBeLoadingState = createState({ +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; + +export const shouldAppBeLoadingState = createStateV2({ key: 'shouldAppBeLoadingState', defaultValue: false, }); diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/command-menu/components/AdvancedFilterCommandMenuCreateRootFilterButton.tsx b/packages/twenty-front/src/modules/object-record/advanced-filter/command-menu/components/AdvancedFilterCommandMenuCreateRootFilterButton.tsx index d234214b5f..13216747ba 100644 --- a/packages/twenty-front/src/modules/object-record/advanced-filter/command-menu/components/AdvancedFilterCommandMenuCreateRootFilterButton.tsx +++ b/packages/twenty-front/src/modules/object-record/advanced-filter/command-menu/components/AdvancedFilterCommandMenuCreateRootFilterButton.tsx @@ -8,9 +8,9 @@ import { useUpsertRecordFilter } from '@/object-record/record-filter/hooks/useUp import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { useSetRecoilComponentFamilyState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentFamilyState'; import { hasInitializedCurrentRecordFiltersComponentFamilyState } from '@/views/states/hasInitializedCurrentRecordFiltersComponentFamilyState'; +import { useFamilySelectorValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilySelectorValueV2'; import { t } from '@lingui/core/macro'; import { useContext } from 'react'; -import { useRecoilValue } from 'recoil'; import { RecordFilterGroupLogicalOperator } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { IconFilter } from 'twenty-ui/display'; @@ -27,10 +27,11 @@ export const AdvancedFilterCommandMenuCreateRootFilterButton = ({ rootLevelRecordFilterGroupComponentSelector, ); - const availableFieldMetadataItemsForFilter = useRecoilValue( - availableFieldMetadataItemsForFilterFamilySelector({ + const availableFieldMetadataItemsForFilter = useFamilySelectorValueV2( + availableFieldMetadataItemsForFilterFamilySelector, + { objectMetadataItemId: objectMetadataItem.id, - }), + }, ); const defaultFieldMetadataItem = diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/hooks/useGetDefaultFieldMetadataItemForFilter.ts b/packages/twenty-front/src/modules/object-record/advanced-filter/hooks/useGetDefaultFieldMetadataItemForFilter.ts index 796450eab5..df49b213e0 100644 --- a/packages/twenty-front/src/modules/object-record/advanced-filter/hooks/useGetDefaultFieldMetadataItemForFilter.ts +++ b/packages/twenty-front/src/modules/object-record/advanced-filter/hooks/useGetDefaultFieldMetadataItemForFilter.ts @@ -1,37 +1,35 @@ import { availableFieldMetadataItemsForFilterFamilySelector } from '@/object-metadata/states/availableFieldMetadataItemsForFilterFamilySelector'; import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; -import { useRecoilCallback } from 'recoil'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; +import { useCallback } from 'react'; export const useGetDefaultFieldMetadataItemForFilter = () => { - const getDefaultFieldMetadataItemForFilter = useRecoilCallback( - ({ snapshot }) => - (objectMetadataItem: ObjectMetadataItem) => { - const availableFieldMetadataItemsForFilter = getSnapshotValue( - snapshot, - availableFieldMetadataItemsForFilterFamilySelector({ - objectMetadataItemId: objectMetadataItem.id, - }), + const getDefaultFieldMetadataItemForFilter = useCallback( + (objectMetadataItem: ObjectMetadataItem) => { + const availableFieldMetadataItemsForFilter = jotaiStore.get( + availableFieldMetadataItemsForFilterFamilySelector.selectorFamily({ + objectMetadataItemId: objectMetadataItem.id, + }), + ); + + const fieldMetadataItemForLabelIdentifier = + availableFieldMetadataItemsForFilter.find( + (fieldMetadataItem) => + fieldMetadataItem.id === + objectMetadataItem?.labelIdentifierFieldMetadataId, ); - const fieldMetadataItemForLabelIdentifier = - availableFieldMetadataItemsForFilter.find( - (fieldMetadataItem) => - fieldMetadataItem.id === - objectMetadataItem?.labelIdentifierFieldMetadataId, - ); + const firstFieldMetadataItem = + availableFieldMetadataItemsForFilter?.[0] as + | FieldMetadataItem + | undefined; - const firstFieldMetadataItem = - availableFieldMetadataItemsForFilter?.[0] as - | FieldMetadataItem - | undefined; + const defaultFieldMetadataItemForFilter = + fieldMetadataItemForLabelIdentifier ?? firstFieldMetadataItem; - const defaultFieldMetadataItemForFilter = - fieldMetadataItemForLabelIdentifier ?? firstFieldMetadataItem; - - return { defaultFieldMetadataItemForFilter }; - }, + return { defaultFieldMetadataItemForFilter }; + }, [], ); diff --git a/packages/twenty-front/src/modules/object-record/cache/hooks/useUpsertFindManyRecordsQueryInCache.ts b/packages/twenty-front/src/modules/object-record/cache/hooks/useUpsertFindManyRecordsQueryInCache.ts index 0d318597bd..35e31c23b0 100644 --- a/packages/twenty-front/src/modules/object-record/cache/hooks/useUpsertFindManyRecordsQueryInCache.ts +++ b/packages/twenty-front/src/modules/object-record/cache/hooks/useUpsertFindManyRecordsQueryInCache.ts @@ -1,7 +1,6 @@ -import { useRecoilValue } from 'recoil'; - import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { getRecordConnectionFromRecords } from '@/object-record/cache/utils/getRecordConnectionFromRecords'; import { type RecordGqlOperationVariables } from 'twenty-shared/types'; @@ -16,7 +15,7 @@ export const useUpsertFindManyRecordsQueryInCache = ({ }) => { const apolloCoreClient = useApolloCoreClient(); - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); const { objectPermissionsByObjectMetadataId } = useObjectPermissions(); const upsertFindManyRecordsQueryInCache = < diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindManyRecords.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindManyRecords.test.tsx index a8f37c028f..ab92105488 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindManyRecords.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindManyRecords.test.tsx @@ -3,25 +3,30 @@ import { renderHook } from '@testing-library/react'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems'; const Wrapper = getJestMetadataAndApolloMocksWrapper({ apolloMocks: [], - onInitializeRecoilSnapshot: (snapshot) => { - snapshot.set(currentWorkspaceMemberState, { + onInitializeRecoilSnapshot: () => { + jotaiStore.set( + objectMetadataItemsState.atom, + generatedMockObjectMetadataItems, + ); + }, +}); + +describe('useFindManyRecords', () => { + it('should work as expected', async () => { + jotaiStore.set(currentWorkspaceMemberState.atom, { id: '32219445-f587-4c40-b2b1-6d3205ed96da', name: { firstName: 'John', lastName: 'Connor' }, locale: 'en', colorScheme: 'Light', userEmail: 'userEmail', }); - snapshot.set(objectMetadataItemsState, generatedMockObjectMetadataItems); - }, -}); -describe('useFindManyRecords', () => { - it('should work as expected', async () => { const onCompleted = jest.fn(); const { result } = renderHook( diff --git a/packages/twenty-front/src/modules/object-record/hooks/useBuildRecordInputFromRLSPredicates.ts b/packages/twenty-front/src/modules/object-record/hooks/useBuildRecordInputFromRLSPredicates.ts index cbeef38406..59e1fd6eaf 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useBuildRecordInputFromRLSPredicates.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useBuildRecordInputFromRLSPredicates.ts @@ -14,7 +14,7 @@ import { buildRecordInputFromFilter } from '@/object-record/record-table/utils/b import { buildCompositeValueFromSubField } from '@/object-record/record-table/utils/buildValueFromFilter'; import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; import { isUndefined } from '@sniptt/guards'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { RelationType } from 'twenty-shared/types'; import { isDefined, isPlainObject } from 'twenty-shared/utils'; @@ -31,7 +31,7 @@ export const useBuildRecordInputFromRLSPredicates = ({ }: { objectMetadataItem: ObjectMetadataItem; }) => { - const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); + const currentWorkspaceMember = useRecoilValueV2(currentWorkspaceMemberState); const { record: currentWorkspaceMemberRecord } = useFindOneRecord({ objectNameSingular: CoreObjectNameSingular.WorkspaceMember, diff --git a/packages/twenty-front/src/modules/object-record/hooks/useCreateManyRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/useCreateManyRecords.ts index 910d041335..aed705ce8d 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useCreateManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useCreateManyRecords.ts @@ -22,7 +22,7 @@ import { computeOptimisticRecordFromInput } from '@/object-record/utils/computeO import { dispatchObjectRecordOperationBrowserEvent } from '@/browser-event/utils/dispatchObjectRecordOperationBrowserEvent'; import { getCreateManyRecordsMutationResponseField } from '@/object-record/utils/getCreateManyRecordsMutationResponseField'; import { sanitizeRecordInput } from '@/object-record/utils/sanitizeRecordInput'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { type RecordGqlOperationGqlRecordFields } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; @@ -78,7 +78,7 @@ export const useCreateManyRecords = < objectMetadataItem, }); - const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); + const currentWorkspaceMember = useRecoilValueV2(currentWorkspaceMemberState); const { objectMetadataItems } = useObjectMetadataItems(); const { objectPermissionsByObjectMetadataId } = useObjectPermissions(); diff --git a/packages/twenty-front/src/modules/object-record/hooks/useCreateManyRecordsMutation.ts b/packages/twenty-front/src/modules/object-record/hooks/useCreateManyRecordsMutation.ts index 82f2419717..d1929d3bca 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useCreateManyRecordsMutation.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useCreateManyRecordsMutation.ts @@ -1,8 +1,8 @@ import gql from 'graphql-tag'; -import { useRecoilValue } from 'recoil'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery'; import { EMPTY_MUTATION } from '@/object-record/constants/EmptyMutation'; import { type RecordGqlOperationGqlRecordFields } from 'twenty-shared/types'; @@ -24,7 +24,7 @@ export const useCreateManyRecordsMutation = ({ const { objectPermissionsByObjectMetadataId } = useObjectPermissions(); - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); if (isUndefinedOrNull(objectMetadataItem)) { return { createManyRecordsMutation: EMPTY_MUTATION }; diff --git a/packages/twenty-front/src/modules/object-record/hooks/useCreateOneRecord.ts b/packages/twenty-front/src/modules/object-record/hooks/useCreateOneRecord.ts index 27e90d5c84..afad07eca9 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useCreateOneRecord.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useCreateOneRecord.ts @@ -29,7 +29,7 @@ import { computeOptimisticRecordFromInput } from '@/object-record/utils/computeO import { dispatchObjectRecordOperationBrowserEvent } from '@/browser-event/utils/dispatchObjectRecordOperationBrowserEvent'; import { getCreateOneRecordMutationResponseField } from '@/object-record/utils/getCreateOneRecordMutationResponseField'; import { sanitizeRecordInput } from '@/object-record/utils/sanitizeRecordInput'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { CustomError, isDefined } from 'twenty-shared/utils'; type useCreateOneRecordProps = { @@ -68,7 +68,7 @@ export const useCreateOneRecord = < recordGqlFields: computedRecordGqlFields, }); - const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); + const currentWorkspaceMember = useRecoilValueV2(currentWorkspaceMemberState); const createOneRecordInCache = useCreateOneRecordInCache( { diff --git a/packages/twenty-front/src/modules/object-record/hooks/useCreateOneRecordMutation.ts b/packages/twenty-front/src/modules/object-record/hooks/useCreateOneRecordMutation.ts index f899c50b46..5075959af7 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useCreateOneRecordMutation.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useCreateOneRecordMutation.ts @@ -1,7 +1,6 @@ -import { useRecoilValue } from 'recoil'; - import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { generateCreateOneRecordMutation } from '@/object-metadata/utils/generateCreateOneRecordMutation'; import { EMPTY_MUTATION } from '@/object-record/constants/EmptyMutation'; import { type RecordGqlOperationGqlRecordFields } from 'twenty-shared/types'; @@ -19,7 +18,7 @@ export const useCreateOneRecordMutation = ({ objectNameSingular, }); - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); const { objectPermissionsByObjectMetadataId } = useObjectPermissions(); diff --git a/packages/twenty-front/src/modules/object-record/hooks/useDeleteManyRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/useDeleteManyRecords.ts index a838eb178e..c9f3e9ccd6 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useDeleteManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useDeleteManyRecords.ts @@ -17,7 +17,7 @@ import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggr import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore'; import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; import { getDeleteManyRecordsMutationResponseField } from '@/object-record/utils/getDeleteManyRecordsMutationResponseField'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { isDefined } from 'twenty-shared/utils'; import { sleep } from '~/utils/sleep'; @@ -36,7 +36,7 @@ export const useDeleteManyRecords = ({ objectNameSingular, }: useDeleteManyRecordProps) => { const { upsertRecordsInStore } = useUpsertRecordsInStore(); - const apiConfig = useRecoilValue(apiConfigState); + const apiConfig = useRecoilValueV2(apiConfigState); const mutationPageSize = apiConfig?.mutationMaximumAffectedRecords ?? DEFAULT_MUTATION_BATCH_SIZE; diff --git a/packages/twenty-front/src/modules/object-record/hooks/useDestroyManyRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/useDestroyManyRecords.ts index 086b1ebb4d..4a2837e3c0 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useDestroyManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useDestroyManyRecords.ts @@ -14,7 +14,7 @@ import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggr import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore'; import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; import { getDestroyManyRecordsMutationResponseField } from '@/object-record/utils/getDestroyManyRecordsMutationResponseField'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { capitalize, isDefined } from 'twenty-shared/utils'; import { sleep } from '~/utils/sleep'; @@ -33,7 +33,7 @@ export const useDestroyManyRecords = ({ objectNameSingular, }: useDestroyManyRecordProps) => { const { upsertRecordsInStore } = useUpsertRecordsInStore(); - const apiConfig = useRecoilValue(apiConfigState); + const apiConfig = useRecoilValueV2(apiConfigState); const mutationPageSize = apiConfig?.mutationMaximumAffectedRecords ?? DEFAULT_MUTATION_BATCH_SIZE; diff --git a/packages/twenty-front/src/modules/object-record/hooks/useFindDuplicatesRecordsQuery.ts b/packages/twenty-front/src/modules/object-record/hooks/useFindDuplicatesRecordsQuery.ts index 0d4294bad6..a82d4b1719 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useFindDuplicatesRecordsQuery.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useFindDuplicatesRecordsQuery.ts @@ -1,8 +1,8 @@ import gql from 'graphql-tag'; -import { useRecoilValue } from 'recoil'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { isAggregationEnabled } from '@/object-metadata/utils/isAggregationEnabled'; import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery'; import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions'; @@ -20,7 +20,7 @@ export const useFindDuplicateRecordsQuery = ({ const { objectPermissionsByObjectMetadataId } = useObjectPermissions(); - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); const findDuplicateRecordsQuery = gql` query FindDuplicate${capitalize( diff --git a/packages/twenty-front/src/modules/object-record/hooks/useFindManyRecordsQuery.ts b/packages/twenty-front/src/modules/object-record/hooks/useFindManyRecordsQuery.ts index 3483ae22ad..97aaa946d5 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useFindManyRecordsQuery.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useFindManyRecordsQuery.ts @@ -1,7 +1,6 @@ -import { useRecoilValue } from 'recoil'; - import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions'; import { generateFindManyRecordsQuery } from '@/object-record/utils/generateFindManyRecordsQuery'; import { @@ -24,7 +23,7 @@ export const useFindManyRecordsQuery = ({ objectNameSingular, }); - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); const { objectPermissionsByObjectMetadataId } = useObjectPermissions(); diff --git a/packages/twenty-front/src/modules/object-record/hooks/useFindOneRecordQuery.ts b/packages/twenty-front/src/modules/object-record/hooks/useFindOneRecordQuery.ts index 6b55734b64..817536c27c 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useFindOneRecordQuery.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useFindOneRecordQuery.ts @@ -1,8 +1,8 @@ import gql from 'graphql-tag'; -import { useRecoilValue } from 'recoil'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery'; import { type RecordGqlOperationGqlRecordFields } from 'twenty-shared/types'; import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions'; @@ -21,7 +21,7 @@ export const useFindOneRecordQuery = ({ objectNameSingular, }); - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); const { objectPermissionsByObjectMetadataId } = useObjectPermissions(); diff --git a/packages/twenty-front/src/modules/object-record/hooks/useGroupByRecordsQuery.ts b/packages/twenty-front/src/modules/object-record/hooks/useGroupByRecordsQuery.ts index a7aca4367e..b5e8536f3d 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useGroupByRecordsQuery.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useGroupByRecordsQuery.ts @@ -1,7 +1,6 @@ -import { useRecoilValue } from 'recoil'; - import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { type RecordGqlOperationGqlRecordFields } from 'twenty-shared/types'; import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions'; import { generateGroupByRecordsQuery } from '@/object-record/utils/generateGroupByRecordsQuery'; @@ -19,7 +18,7 @@ export const useGroupByRecordsQuery = ({ objectNameSingular, }); - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); const { objectPermissionsByObjectMetadataId } = useObjectPermissions(); diff --git a/packages/twenty-front/src/modules/object-record/hooks/useMergeManyRecordsMutation.ts b/packages/twenty-front/src/modules/object-record/hooks/useMergeManyRecordsMutation.ts index ffddd3e354..f736f11a03 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useMergeManyRecordsMutation.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useMergeManyRecordsMutation.ts @@ -1,8 +1,8 @@ import gql from 'graphql-tag'; -import { useRecoilValue } from 'recoil'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery'; import { EMPTY_MUTATION } from '@/object-record/constants/EmptyMutation'; import { useGenerateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/hooks/useGenerateDepthRecordGqlFieldsFromObject'; @@ -30,7 +30,7 @@ export const useMergeManyRecordsMutation = ({ const appliedRecordGqlFields = recordGqlFields ?? depthOneRecordGqlFields; - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); const { objectPermissionsByObjectMetadataId } = useObjectPermissions(); diff --git a/packages/twenty-front/src/modules/object-record/hooks/useObjectPermissions.ts b/packages/twenty-front/src/modules/object-record/hooks/useObjectPermissions.ts index 79fa5bc032..a4db6c9466 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useObjectPermissions.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useObjectPermissions.ts @@ -1,6 +1,5 @@ -import { useRecoilValue } from 'recoil'; - import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { type ObjectPermissions } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; @@ -12,7 +11,7 @@ type useObjectPermissionsReturnType = { }; export const useObjectPermissions = (): useObjectPermissionsReturnType => { - const currentUserWorkspace = useRecoilValue(currentUserWorkspaceState); + const currentUserWorkspace = useRecoilValueV2(currentUserWorkspaceState); const objectsPermissions = currentUserWorkspace?.objectsPermissions; if (!isDefined(objectsPermissions)) { diff --git a/packages/twenty-front/src/modules/object-record/hooks/useObjectRecordSearchRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/useObjectRecordSearchRecords.ts index 05276bf86f..565c7b2b11 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useObjectRecordSearchRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useObjectRecordSearchRecords.ts @@ -5,7 +5,7 @@ import { useDoObjectMetadataItemsExist } from '@/object-metadata/hooks/useDoObje import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { type WatchQueryFetchPolicy } from '@apollo/client'; import { useMemo } from 'react'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { isDefined } from 'twenty-shared/utils'; import { type ObjectRecordFilterInput, @@ -35,7 +35,7 @@ export const useObjectRecordSearchRecords = ({ filter, fetchPolicy, }: UseSearchRecordsParams) => { - const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); + const currentWorkspaceMember = useRecoilValueV2(currentWorkspaceMemberState); const areDefined = useDoObjectMetadataItemsExist(objectNameSingulars); const { enqueueErrorSnackBar } = useSnackBar(); diff --git a/packages/twenty-front/src/modules/object-record/hooks/useRestoreManyRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/useRestoreManyRecords.ts index 37341f7a96..aa7229595e 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useRestoreManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useRestoreManyRecords.ts @@ -14,7 +14,7 @@ import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useU import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; import { dispatchObjectRecordOperationBrowserEvent } from '@/browser-event/utils/dispatchObjectRecordOperationBrowserEvent'; import { getRestoreManyRecordsMutationResponseField } from '@/object-record/utils/getRestoreManyRecordsMutationResponseField'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { capitalize, isDefined } from 'twenty-shared/utils'; import { sleep } from '~/utils/sleep'; @@ -34,7 +34,7 @@ export const useRestoreManyRecords = ({ }: useRestoreManyRecordProps) => { const { upsertRecordsInStore } = useUpsertRecordsInStore(); - const apiConfig = useRecoilValue(apiConfigState); + const apiConfig = useRecoilValueV2(apiConfigState); const mutationPageSize = apiConfig?.mutationMaximumAffectedRecords ?? DEFAULT_MUTATION_BATCH_SIZE; diff --git a/packages/twenty-front/src/modules/object-record/hooks/useUpdateManyRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/useUpdateManyRecords.ts index 2d9a0c2943..af1edc2bc3 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useUpdateManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useUpdateManyRecords.ts @@ -20,7 +20,7 @@ import { dispatchObjectRecordOperationBrowserEvent } from '@/browser-event/utils import { getUpdatedFieldsFromRecordInput } from '@/object-record/utils/getUpdatedFieldsFromRecordInput'; import { getUpdateManyRecordsMutationResponseField } from '@/object-record/utils/getUpdateManyRecordsMutationResponseField'; import { sanitizeRecordInput } from '@/object-record/utils/sanitizeRecordInput'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { isDefined } from 'twenty-shared/utils'; import { sleep } from '~/utils/sleep'; @@ -44,7 +44,7 @@ export const useUpdateManyRecords = ({ recordGqlFields, }: UseUpdateManyRecordsProps) => { const { upsertRecordsInStore } = useUpsertRecordsInStore(); - const apiConfig = useRecoilValue(apiConfigState); + const apiConfig = useRecoilValueV2(apiConfigState); const mutationPageSize = apiConfig?.mutationMaximumAffectedRecords ?? DEFAULT_MUTATION_BATCH_SIZE; diff --git a/packages/twenty-front/src/modules/object-record/hooks/useUpdateOneRecord.ts b/packages/twenty-front/src/modules/object-record/hooks/useUpdateOneRecord.ts index 184226dac5..b4dab560d9 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useUpdateOneRecord.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useUpdateOneRecord.ts @@ -21,7 +21,7 @@ import { getUpdatedFieldsFromRecordInput } from '@/object-record/utils/getUpdate import { getUpdateOneRecordMutationResponseField } from '@/object-record/utils/getUpdateOneRecordMutationResponseField'; import { sanitizeRecordInput } from '@/object-record/utils/sanitizeRecordInput'; import { isNull } from '@sniptt/guards'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { isDefined } from 'twenty-shared/utils'; import { buildRecordFromKeysWithSameValue } from '~/utils/array/buildRecordFromKeysWithSameValue'; @@ -37,7 +37,7 @@ export const useUpdateOneRecord = () => { const apolloCoreClient = useApolloCoreClient(); const { upsertRecordsInStore } = useUpsertRecordsInStore(); - const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); + const currentWorkspaceMember = useRecoilValueV2(currentWorkspaceMemberState); const { objectMetadataItems } = useObjectMetadataItems(); const { objectPermissionsByObjectMetadataId } = useObjectPermissions(); diff --git a/packages/twenty-front/src/modules/object-record/multiple-objects/hooks/__tests__/useCombinedFindManyRecords.test.tsx b/packages/twenty-front/src/modules/object-record/multiple-objects/hooks/__tests__/useCombinedFindManyRecords.test.tsx index 9f632533b5..5b620de456 100644 --- a/packages/twenty-front/src/modules/object-record/multiple-objects/hooks/__tests__/useCombinedFindManyRecords.test.tsx +++ b/packages/twenty-front/src/modules/object-record/multiple-objects/hooks/__tests__/useCombinedFindManyRecords.test.tsx @@ -1,12 +1,12 @@ import { gql } from '@apollo/client'; import { renderHook, waitFor } from '@testing-library/react'; -import { useSetRecoilState } from 'recoil'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { type RecordGqlFields } from '@/object-record/graphql/record-gql-fields/types/RecordGqlFields'; import { type RecordGqlOperationSignature } from 'twenty-shared/types'; import { useCombinedFindManyRecords } from '@/object-record/multiple-objects/hooks/useCombinedFindManyRecords'; import { useGenerateCombinedFindManyRecordsQuery } from '@/object-record/multiple-objects/hooks/useGenerateCombinedFindManyRecordsQuery'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems'; @@ -123,18 +123,17 @@ const renderUseCombinedFindManyRecordsHook = async ({ }, ]; - const { result } = renderHook( - () => { - const setObjectMetadataItems = useSetRecoilState( - objectMetadataItemsState, - ); - setObjectMetadataItems(generatedMockObjectMetadataItems); + jotaiStore.set( + objectMetadataItemsState.atom, + generatedMockObjectMetadataItems, + ); - return useCombinedFindManyRecords({ + const { result } = renderHook( + () => + useCombinedFindManyRecords({ operationSignatures, skip, - }); - }, + }), { wrapper: getJestMetadataAndApolloMocksWrapper({ apolloMocks: mocks }), }, diff --git a/packages/twenty-front/src/modules/object-record/multiple-objects/hooks/useGenerateCombinedFindManyRecordsQuery.ts b/packages/twenty-front/src/modules/object-record/multiple-objects/hooks/useGenerateCombinedFindManyRecordsQuery.ts index 429064f353..64fc47542f 100644 --- a/packages/twenty-front/src/modules/object-record/multiple-objects/hooks/useGenerateCombinedFindManyRecordsQuery.ts +++ b/packages/twenty-front/src/modules/object-record/multiple-objects/hooks/useGenerateCombinedFindManyRecordsQuery.ts @@ -1,8 +1,8 @@ import { gql } from '@apollo/client'; import { isNonEmptyArray, isUndefined } from '@sniptt/guards'; -import { useRecoilValue } from 'recoil'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { getObjectPermissionsForObject } from '@/object-metadata/utils/getObjectPermissionsForObject'; import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery'; import { type RecordGqlOperationSignature } from 'twenty-shared/types'; @@ -17,7 +17,7 @@ export const useGenerateCombinedFindManyRecordsQuery = ({ }: { operationSignatures: RecordGqlOperationSignature[]; }) => { - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); const { objectPermissionsByObjectMetadataId } = useObjectPermissions(); if (!isNonEmptyArray(operationSignatures)) { diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx index 3c06332dd5..530d806283 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx @@ -8,6 +8,7 @@ import { selectedOperandInDropdownComponentState } from '@/object-record/object- import { getRelativeDateDisplayValue } from '@/object-record/object-filter-dropdown/utils/getRelativeDateDisplayValue'; import { DatePicker } from '@/ui/input/components/internal/date/components/DatePicker'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { UserContext } from '@/users/contexts/UserContext'; import { stringifyRelativeDateFilter } from '@/views/view-filter-value/utils/stringifyRelativeDateFilter'; import { useContext } from 'react'; @@ -24,7 +25,7 @@ import { formatDateString } from '~/utils/string/formatDateString'; export const ObjectFilterDropdownDateInput = () => { const { dateFormat, timeZone } = useContext(UserContext); const dateLocale = useRecoilValue(dateLocaleState); - const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); + const currentWorkspaceMember = useRecoilValueV2(currentWorkspaceMemberState); const selectedOperandInDropdown = useRecoilComponentValue( selectedOperandInDropdownComponentState, diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateTimeInput.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateTimeInput.tsx index 413d887ce1..947a144d15 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateTimeInput.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateTimeInput.tsx @@ -7,6 +7,7 @@ import { objectFilterDropdownCurrentRecordFilterComponentState } from '@/object- import { getRelativeDateDisplayValue } from '@/object-record/object-filter-dropdown/utils/getRelativeDateDisplayValue'; import { DateTimePicker } from '@/ui/input/components/internal/date/components/DateTimePicker'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { UserContext } from '@/users/contexts/UserContext'; import { stringifyRelativeDateFilter } from '@/views/view-filter-value/utils/stringifyRelativeDateFilter'; import { useContext } from 'react'; @@ -27,7 +28,7 @@ import { formatDateTimeString } from '~/utils/string/formatDateTimeString'; export const ObjectFilterDropdownDateTimeInput = () => { const { dateFormat, timeFormat, timeZone } = useContext(UserContext); const dateLocale = useRecoilValue(dateLocaleState); - const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); + const currentWorkspaceMember = useRecoilValueV2(currentWorkspaceMemberState); const { userTimezone } = useUserTimezone(); diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordSelect.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordSelect.tsx index 2b8316e70b..667ab80e94 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordSelect.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordSelect.tsx @@ -20,8 +20,8 @@ import { jsonRelationFilterValueSchema, } from 'twenty-shared/utils'; import { IconUserCircle } from 'twenty-ui/display'; -import { useRecoilValue } from 'recoil'; import { allowRequestsToTwentyIconsState } from '@/client-config/states/allowRequestsToTwentyIcons'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; export const EMPTY_FILTER_VALUE: string = JSON.stringify({ isCurrentWorkspaceMemberSelected: false, @@ -43,7 +43,7 @@ export const ObjectFilterDropdownRecordSelect = ({ fieldMetadataItemUsedInDropdownComponentSelector, ); - const allowRequestsToTwentyIcons = useRecoilValue( + const allowRequestsToTwentyIcons = useRecoilValueV2( allowRequestsToTwentyIconsState, ); diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector.ts index b15ac6f8af..9d3f214103 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector.ts +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector.ts @@ -1,4 +1,5 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext'; import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState'; @@ -16,7 +17,9 @@ export const fieldMetadataItemUsedInDropdownComponentSelector = }), ); - const objectMetadataItems = get(objectMetadataItemsState); + const objectMetadataItems = jotaiStore.get( + objectMetadataItemsState.atom, + ); const correspondingFieldMetadataItem = objectMetadataItems .flatMap((objectMetadataItem) => objectMetadataItem.fields) diff --git a/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/__stories__/ObjectOptionsDropdownContent.stories.tsx b/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/__stories__/ObjectOptionsDropdownContent.stories.tsx index 7d9bc8112f..f74ae30401 100644 --- a/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/__stories__/ObjectOptionsDropdownContent.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/__stories__/ObjectOptionsDropdownContent.stories.tsx @@ -15,13 +15,13 @@ import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewCompon import { ViewType } from '@/views/types/ViewType'; import { useEffect } from 'react'; import { MemoryRouter } from 'react-router-dom'; -import { useSetRecoilState } from 'recoil'; import { ComponentDecorator } from 'twenty-ui/testing'; import { ContextStoreDecorator } from '~/testing/decorators/ContextStoreDecorator'; import { IconsProviderDecorator } from '~/testing/decorators/IconsProviderDecorator'; import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator'; import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator'; import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; const instanceId = 'entity-options-instance'; @@ -31,13 +31,12 @@ const meta: Meta = { component: ObjectOptionsDropdownContent, decorators: [ (Story) => { - const setObjectMetadataItems = useSetRecoilState( - objectMetadataItemsState, - ); - useEffect(() => { - setObjectMetadataItems(generatedMockObjectMetadataItems); - }, [setObjectMetadataItems]); + jotaiStore.set( + objectMetadataItemsState.atom, + generatedMockObjectMetadataItems, + ); + }, []); return ( { const { availableFieldsForCalendar } = useGetAvailableFieldsForCalendar(); const setAndPersistViewType = useRecoilCallback( - ({ snapshot, set }) => + ({ snapshot }) => async (viewType: ViewType) => { const currentViewId = snapshot .getLoadable( @@ -38,9 +39,7 @@ export const useSetViewTypeFromLayoutOptionsMenu = () => { ) .getValue(); - const existingCoreViews = snapshot - .getLoadable(coreViewsState) - .getValue(); + const existingCoreViews = jotaiStore.get(coreViewsState.atom); if (!isDefined(currentViewId)) { throw new Error('No view id found'); @@ -75,7 +74,7 @@ export const useSetViewTypeFromLayoutOptionsMenu = () => { } setRecordIndexViewType(viewType); - set(coreViewsState, [ + jotaiStore.set(coreViewsState.atom, [ ...existingCoreViews.filter( (coreView) => coreView.id !== currentView.id, ), @@ -96,7 +95,7 @@ export const useSetViewTypeFromLayoutOptionsMenu = () => { updateCurrentViewParams.mainGroupByFieldMetadataId = null; await updateCurrentView(updateCurrentViewParams); setRecordIndexViewType(viewType); - set(coreViewsState, [ + jotaiStore.set(coreViewsState.atom, [ ...existingCoreViews.filter( (coreView) => coreView.id !== currentView.id, ), @@ -116,7 +115,7 @@ export const useSetViewTypeFromLayoutOptionsMenu = () => { const calendarFieldMetadataId = availableFieldsForCalendar[0].id; setRecordIndexViewType(viewType); - set(coreViewsState, [ + jotaiStore.set(coreViewsState.atom, [ ...existingCoreViews.filter( (coreView) => coreView.id !== currentView.id, ), diff --git a/packages/twenty-front/src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx b/packages/twenty-front/src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx index b0ce0fa5d7..e4c6deda65 100644 --- a/packages/twenty-front/src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx +++ b/packages/twenty-front/src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx @@ -30,8 +30,8 @@ import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/ho import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2'; import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilComponentStateV2'; +import { useFamilySelectorValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilySelectorValueV2'; import { Trans, useLingui } from '@lingui/react/macro'; -import { useRecoilValue } from 'recoil'; import { findByProperty } from 'twenty-shared/utils'; import { IconX, useIcons } from 'twenty-ui/display'; import { MenuItem } from 'twenty-ui/navigation'; @@ -54,10 +54,11 @@ export const ObjectSortDropdownButton = () => { objectSortDropdownSearchInputComponentState, ); - const sortableFieldMetadataItems = useRecoilValue( - availableFieldMetadataItemsForSortFamilySelector({ + const sortableFieldMetadataItems = useFamilySelectorValueV2( + availableFieldMetadataItemsForSortFamilySelector, + { objectMetadataItemId: objectMetadataItem.id, - }), + }, ); const { getIcon } = useIcons(); diff --git a/packages/twenty-front/src/modules/object-record/record-calendar/month/components/__stories__/RecordCalendarMonth.stories.tsx b/packages/twenty-front/src/modules/object-record/record-calendar/month/components/__stories__/RecordCalendarMonth.stories.tsx index e9473ef071..24e8cec3c1 100644 --- a/packages/twenty-front/src/modules/object-record/record-calendar/month/components/__stories__/RecordCalendarMonth.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-calendar/month/components/__stories__/RecordCalendarMonth.stories.tsx @@ -18,8 +18,8 @@ import { currentRecordFieldsComponentState } from '@/object-record/record-field/ import { type RecordField } from '@/object-record/record-field/types/RecordField'; import { useRecordIndexFieldMetadataDerivedStates } from '@/object-record/record-index/hooks/useRecordIndexFieldMetadataDerivedStates'; import { ViewBarFilterDropdownIds } from '@/views/constants/ViewBarFilterDropdownIds'; +import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; import { coreViewsState } from '@/views/states/coreViewState'; -import { useSetRecoilState } from 'recoil'; import { ComponentDecorator, RouterDecorator } from 'twenty-ui/testing'; import { ContextStoreDecorator } from '~/testing/decorators/ContextStoreDecorator'; import { IconsProviderDecorator } from '~/testing/decorators/IconsProviderDecorator'; @@ -43,7 +43,7 @@ const meta: Meta = { instanceId, ); - const setCoreViews = useSetRecoilState(coreViewsState); + const setCoreViews = useSetRecoilStateV2(coreViewsState); const mockCoreView = mockedCoreViewsData[0]; diff --git a/packages/twenty-front/src/modules/object-record/record-calendar/month/hooks/useRecordCalendarMonthDaysRange.tsx b/packages/twenty-front/src/modules/object-record/record-calendar/month/hooks/useRecordCalendarMonthDaysRange.tsx index 57ae8befbf..ac917d3c36 100644 --- a/packages/twenty-front/src/modules/object-record/record-calendar/month/hooks/useRecordCalendarMonthDaysRange.tsx +++ b/packages/twenty-front/src/modules/object-record/record-calendar/month/hooks/useRecordCalendarMonthDaysRange.tsx @@ -9,6 +9,7 @@ import { format, startOfWeek, } from 'date-fns'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { useRecoilValue } from 'recoil'; import { type Temporal } from 'temporal-polyfill'; @@ -23,7 +24,7 @@ import { dateLocaleState } from '~/localization/states/dateLocaleState'; export const useRecordCalendarMonthDaysRange = ( selectedDate: Temporal.PlainDate, ) => { - const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); + const currentWorkspaceMember = useRecoilValueV2(currentWorkspaceMemberState); const dateLocale = useRecoilValue(dateLocaleState); if (!currentWorkspaceMember) { diff --git a/packages/twenty-front/src/modules/object-record/record-calendar/states/selectors/calendarDayRecordsComponentFamilySelector.ts b/packages/twenty-front/src/modules/object-record/record-calendar/states/selectors/calendarDayRecordsComponentFamilySelector.ts index 7864a98670..aa6b3d02c3 100644 --- a/packages/twenty-front/src/modules/object-record/record-calendar/states/selectors/calendarDayRecordsComponentFamilySelector.ts +++ b/packages/twenty-front/src/modules/object-record/record-calendar/states/selectors/calendarDayRecordsComponentFamilySelector.ts @@ -1,4 +1,5 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { hasObjectMetadataItemPositionField } from '@/object-metadata/utils/hasObjectMetadataItemPositionField'; import { RecordCalendarComponentInstanceContext } from '@/object-record/record-calendar/states/contexts/RecordCalendarComponentInstanceContext'; @@ -26,7 +27,9 @@ export const calendarDayRecordIdsComponentFamilySelector = recordIndexCalendarFieldMetadataIdState, ); - const objectMetadataItems = get(objectMetadataItemsState); + const objectMetadataItems = jotaiStore.get( + objectMetadataItemsState.atom, + ); const objectMetadataItem = objectMetadataItems.find( (objectMetadataItem) => objectMetadataItem.fields.some( diff --git a/packages/twenty-front/src/modules/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationSection.tsx b/packages/twenty-front/src/modules/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationSection.tsx index 09e2a8f4f4..3ea018fd8e 100644 --- a/packages/twenty-front/src/modules/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationSection.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationSection.tsx @@ -21,6 +21,7 @@ import { AggregateOperations } from '@/object-record/record-table/constants/Aggr import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; +import { useFamilySelectorValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilySelectorValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2'; import { coreIndexViewIdFromObjectMetadataItemFamilySelector } from '@/views/states/selectors/coreIndexViewIdFromObjectMetadataItemFamilySelector'; import { useLingui } from '@lingui/react/macro'; @@ -105,10 +106,9 @@ export const RecordDetailRelationSection = ({ dropdownId, ); - const indexViewId = useRecoilValue( - coreIndexViewIdFromObjectMetadataItemFamilySelector({ - objectMetadataItemId: relationObjectMetadataItem.id, - }), + const indexViewId = useFamilySelectorValueV2( + coreIndexViewIdFromObjectMetadataItemFamilySelector, + { objectMetadataItemId: relationObjectMetadataItem.id }, ); const filterQueryParams = { diff --git a/packages/twenty-front/src/modules/object-record/record-field/hooks/useFilterVisibleAndReadableRecordField.ts b/packages/twenty-front/src/modules/object-record/record-field/hooks/useFilterVisibleAndReadableRecordField.ts index 6096fbe9cc..5b9bc71bea 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/hooks/useFilterVisibleAndReadableRecordField.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/hooks/useFilterVisibleAndReadableRecordField.ts @@ -1,10 +1,10 @@ import { flattenedReadableFieldMetadataItemsSelector } from '@/object-metadata/states/flattenedReadableFieldMetadataItemIdsSelector'; import { isHiddenSystemField } from '@/object-metadata/utils/isHiddenSystemField'; import { type RecordField } from '@/object-record/record-field/types/RecordField'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; export const useFilterVisibleAndReadableRecordField = () => { - const flattenedReadableFieldMetadataItems = useRecoilValue( + const flattenedReadableFieldMetadataItems = useRecoilValueV2( flattenedReadableFieldMetadataItemsSelector, ); diff --git a/packages/twenty-front/src/modules/object-record/record-field/states/visibleRecordFieldsComponentSelector.ts b/packages/twenty-front/src/modules/object-record/record-field/states/visibleRecordFieldsComponentSelector.ts index a8fff75b46..614efb8ae9 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/states/visibleRecordFieldsComponentSelector.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/states/visibleRecordFieldsComponentSelector.ts @@ -1,4 +1,5 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { isActiveFieldMetadataItem } from '@/object-metadata/utils/isActiveFieldMetadataItem'; import { RecordFieldsComponentInstanceContext } from '@/object-record/record-field/states/context/RecordFieldsComponentInstanceContext'; import { currentRecordFieldsComponentState } from '@/object-record/record-field/states/currentRecordFieldsComponentState'; @@ -18,7 +19,7 @@ export const visibleRecordFieldsComponentSelector = createComponentSelector({ }), ); - const objectMetadataItems = get(objectMetadataItemsState); + const objectMetadataItems = jotaiStore.get(objectMetadataItemsState.atom); const filteredVisibleAndReadableRecordFields = currentRecordFields.filter( (recordFieldToFilter) => { diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/hooks/useClearField.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/hooks/useClearField.ts index b9f7b2eb56..3d82cfc9f9 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/hooks/useClearField.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/hooks/useClearField.ts @@ -2,6 +2,7 @@ import { useContext } from 'react'; import { useRecoilCallback } from 'recoil'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector'; import { generateEmptyFieldValue } from '@/object-record/utils/generateEmptyFieldValue'; @@ -19,11 +20,11 @@ export const useClearField = () => { const [updateRecord] = useUpdateRecord(); const clearField = useRecoilCallback( - ({ snapshot, set }) => + ({ set }) => () => { - const objectMetadataItems = snapshot - .getLoadable(objectMetadataItemsState) - .getValue(); + const objectMetadataItems = jotaiStore.get( + objectMetadataItemsState.atom, + ); const foundObjectMetadataItem = objectMetadataItems.find( (item) => diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/hooks/useOpenFieldInputEditMode.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/hooks/useOpenFieldInputEditMode.ts index 3d7ed3ba76..d8ff8733d4 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/hooks/useOpenFieldInputEditMode.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/hooks/useOpenFieldInputEditMode.ts @@ -33,6 +33,7 @@ import { getRecordFieldInputInstanceId } from '@/object-record/utils/getRecordFi import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack'; import { useRemoveFocusItemFromFocusStackById } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackById'; import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { useRecoilCallback } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; @@ -70,13 +71,11 @@ export const useOpenFieldInputEditMode = () => { recordId: string; prefix?: string; }) => { - const objectMetadataItems = snapshot - .getLoadable(objectMetadataItemsState) - .getValue(); + const objectMetadataItems = jotaiStore.get( + objectMetadataItemsState.atom, + ); - const currentWorkspace = snapshot - .getLoadable(currentWorkspaceState) - .getValue(); + const currentWorkspace = jotaiStore.get(currentWorkspaceState.atom); const isJunctionRelationsEnabled = currentWorkspace?.featureFlags?.find( diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/hooks/useOpenJunctionRelationFieldInput.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/hooks/useOpenJunctionRelationFieldInput.ts index 87717b79fd..fa69a5370a 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/hooks/useOpenJunctionRelationFieldInput.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/hooks/useOpenJunctionRelationFieldInput.ts @@ -1,4 +1,5 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { type FieldDefinition } from '@/object-record/record-field/ui/types/FieldDefinition'; import { type FieldRelationFromManyValue, @@ -36,9 +37,9 @@ export const useOpenJunctionRelationFieldInput = () => { recordId: string; prefix?: string; }) => { - const objectMetadataItems = snapshot - .getLoadable(objectMetadataItemsState) - .getValue(); + const objectMetadataItems = jotaiStore.get( + objectMetadataItemsState.atom, + ); const sourceObjectMetadataId = objectMetadataItems.find( (item) => diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/components/__stories__/RelationManyToOneFieldInput.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/components/__stories__/RelationManyToOneFieldInput.stories.tsx index 0e3807168f..9d36383a82 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/components/__stories__/RelationManyToOneFieldInput.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/components/__stories__/RelationManyToOneFieldInput.stories.tsx @@ -4,7 +4,6 @@ import { type StoryObj, } from '@storybook/react-vite'; import { useEffect } from 'react'; -import { useSetRecoilState } from 'recoil'; import { expect, userEvent, waitFor, within } from 'storybook/test'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; @@ -23,6 +22,7 @@ import { RecordFieldComponentInstanceContext } from '@/object-record/record-fiel import { recordFieldInputLayoutDirectionLoadingComponentState } from '@/object-record/record-field/ui/states/recordFieldInputLayoutDirectionLoadingComponentState'; import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack'; import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState'; import { FieldMetadataType } from 'twenty-shared/types'; @@ -30,24 +30,16 @@ import { RelationManyToOneFieldInput } from '@/object-record/record-field/ui/met import { getFieldInputEventContextProviderWithJestMocks } from './utils/getFieldInputEventContextProviderWithJestMocks'; const RelationWorkspaceSetterEffect = () => { - const setCurrentWorkspace = useSetRecoilState(currentWorkspaceState); - const setCurrentWorkspaceMember = useSetRecoilState( - currentWorkspaceMemberState, - ); const setRecordFieldInputLayoutDirectionLoading = useSetRecoilComponentState( recordFieldInputLayoutDirectionLoadingComponentState, 'relation-to-one-field-input-123-Relation', ); useEffect(() => { - setCurrentWorkspace(mockCurrentWorkspace); - setCurrentWorkspaceMember(mockedWorkspaceMemberData); + jotaiStore.set(currentWorkspaceState.atom, mockCurrentWorkspace); + jotaiStore.set(currentWorkspaceMemberState.atom, mockedWorkspaceMemberData); setRecordFieldInputLayoutDirectionLoading(false); - }, [ - setCurrentWorkspace, - setCurrentWorkspaceMember, - setRecordFieldInputLayoutDirectionLoading, - ]); + }, [setRecordFieldInputLayoutDirectionLoading]); return <>; }; diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/components/__stories__/RelationOneToManyFieldInput.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/components/__stories__/RelationOneToManyFieldInput.stories.tsx index f91a55fbda..f0a56ade69 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/components/__stories__/RelationOneToManyFieldInput.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/components/__stories__/RelationOneToManyFieldInput.stories.tsx @@ -5,6 +5,7 @@ import { useSetRecoilState } from 'recoil'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { FileUploadDecorator } from '~/testing/decorators/FileUploadDecorator'; import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator'; import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator'; @@ -25,15 +26,10 @@ import { FieldMetadataType } from 'twenty-shared/types'; import { RelationType } from '~/generated-metadata/graphql'; const RelationWorkspaceSetterEffect = () => { - const setCurrentWorkspace = useSetRecoilState(currentWorkspaceState); - const setCurrentWorkspaceMember = useSetRecoilState( - currentWorkspaceMemberState, - ); - useEffect(() => { - setCurrentWorkspace(mockCurrentWorkspace); - setCurrentWorkspaceMember(mockedWorkspaceMemberData); - }, [setCurrentWorkspace, setCurrentWorkspaceMember]); + jotaiStore.set(currentWorkspaceState.atom, mockCurrentWorkspace); + jotaiStore.set(currentWorkspaceMemberState.atom, mockedWorkspaceMemberData); + }, []); return <>; }; diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/hooks/useOpenMorphRelationOneToManyFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/hooks/useOpenMorphRelationOneToManyFieldInput.tsx index c5d5a4f6f7..bff9d7e275 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/hooks/useOpenMorphRelationOneToManyFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/hooks/useOpenMorphRelationOneToManyFieldInput.tsx @@ -1,4 +1,5 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { type FieldDefinition } from '@/object-record/record-field/ui/types/FieldDefinition'; import { type FieldMorphRelationMetadata } from '@/object-record/record-field/ui/types/FieldMetadata'; @@ -53,9 +54,9 @@ export const useOpenMorphRelationOneToManyFieldInput = () => { ) .getValue(); - const objectMetadataItems = snapshot - .getLoadable(objectMetadataItemsState) - .getValue(); + const objectMetadataItems = jotaiStore.get( + objectMetadataItemsState.atom, + ); const objectMetadataItemArray = objectMetadataItems.filter( (objectMetadataItem) => diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/hooks/useOpenRelationFromManyFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/hooks/useOpenRelationFromManyFieldInput.tsx index 540390e64e..ab3f6d2078 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/hooks/useOpenRelationFromManyFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/hooks/useOpenRelationFromManyFieldInput.tsx @@ -13,6 +13,7 @@ import { recordStoreFamilySelector } from '@/object-record/record-store/states/s import { getRecordFieldInputInstanceId } from '@/object-record/utils/getRecordFieldInputId'; import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack'; import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { useRecoilCallback } from 'recoil'; export const useOpenRelationFromManyFieldInput = () => { @@ -50,9 +51,9 @@ export const useOpenRelationFromManyFieldInput = () => { ) .getValue() ?? []; - const objectMetadataItems = snapshot - .getLoadable(objectMetadataItemsState) - .getValue(); + const objectMetadataItems = jotaiStore.get( + objectMetadataItemsState.atom, + ); const objectMetadataItem = objectMetadataItems.find( (objectMetadataItem) => diff --git a/packages/twenty-front/src/modules/object-record/record-filter/hooks/useFilterValueDependencies.ts b/packages/twenty-front/src/modules/object-record/record-filter/hooks/useFilterValueDependencies.ts index 498a3bc735..d880dcc55a 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter/hooks/useFilterValueDependencies.ts +++ b/packages/twenty-front/src/modules/object-record/record-filter/hooks/useFilterValueDependencies.ts @@ -1,20 +1,25 @@ +import { useMemo } from 'react'; + import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { useUserTimezone } from '@/ui/input/components/internal/date/hooks/useUserTimezone'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { type RecordFilterValueDependencies } from 'twenty-shared/types'; export const useFilterValueDependencies = (): { filterValueDependencies: RecordFilterValueDependencies; } => { const { id: currentWorkspaceMemberId } = - useRecoilValue(currentWorkspaceMemberState) ?? {}; + useRecoilValueV2(currentWorkspaceMemberState) ?? {}; const { userTimezone } = useUserTimezone(); - return { - filterValueDependencies: { + const filterValueDependencies = useMemo( + () => ({ currentWorkspaceMemberId, timeZone: userTimezone, - }, - }; + }), + [currentWorkspaceMemberId, userTimezone], + ); + + return { filterValueDependencies }; }; diff --git a/packages/twenty-front/src/modules/object-record/record-filter/hooks/useFilterableFieldMetadataItems.ts b/packages/twenty-front/src/modules/object-record/record-filter/hooks/useFilterableFieldMetadataItems.ts index 244762acc1..9d82ab9bda 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter/hooks/useFilterableFieldMetadataItems.ts +++ b/packages/twenty-front/src/modules/object-record/record-filter/hooks/useFilterableFieldMetadataItems.ts @@ -1,13 +1,14 @@ import { availableFieldMetadataItemsForFilterFamilySelector } from '@/object-metadata/states/availableFieldMetadataItemsForFilterFamilySelector'; -import { useRecoilValue } from 'recoil'; +import { useFamilySelectorValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilySelectorValueV2'; export const useFilterableFieldMetadataItems = ( objectMetadataItemId: string, ) => { - const filterableFieldMetadataItems = useRecoilValue( - availableFieldMetadataItemsForFilterFamilySelector({ + const filterableFieldMetadataItems = useFamilySelectorValueV2( + availableFieldMetadataItemsForFilterFamilySelector, + { objectMetadataItemId, - }), + }, ); return { filterableFieldMetadataItems }; diff --git a/packages/twenty-front/src/modules/object-record/record-filter/hooks/useGetRelativeDateFilterWithUserTimezone.ts b/packages/twenty-front/src/modules/object-record/record-filter/hooks/useGetRelativeDateFilterWithUserTimezone.ts index 64daf72c4d..f8e03e2586 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter/hooks/useGetRelativeDateFilterWithUserTimezone.ts +++ b/packages/twenty-front/src/modules/object-record/record-filter/hooks/useGetRelativeDateFilterWithUserTimezone.ts @@ -1,14 +1,14 @@ import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { detectCalendarStartDay } from '@/localization/utils/detection/detectCalendarStartDay'; import { useUserTimezone } from '@/ui/input/components/internal/date/hooks/useUserTimezone'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { CalendarStartDay } from 'twenty-shared/constants'; import { type FirstDayOfTheWeek } from 'twenty-shared/types'; import { type RelativeDateFilter } from 'twenty-shared/utils'; export const useGetRelativeDateFilterWithUserTimezone = () => { const { userTimezone } = useUserTimezone(); - const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); + const currentWorkspaceMember = useRecoilValueV2(currentWorkspaceMemberState); const getRelativeDateFilterWithUserTimezone = ( relativeDateFilter: RelativeDateFilter, diff --git a/packages/twenty-front/src/modules/object-record/record-filter/states/hasAnySoftDeleteFilterOnView.ts b/packages/twenty-front/src/modules/object-record/record-filter/states/hasAnySoftDeleteFilterOnView.ts index 0b223812e8..e1fcd0f0e7 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter/states/hasAnySoftDeleteFilterOnView.ts +++ b/packages/twenty-front/src/modules/object-record/record-filter/states/hasAnySoftDeleteFilterOnView.ts @@ -1,4 +1,5 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext'; import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; import { isRecordFilterAboutSoftDelete } from '@/object-record/record-filter/utils/isRecordFilterAboutSoftDelete'; @@ -11,7 +12,9 @@ export const hasAnySoftDeleteFilterOnViewComponentSelector = get: ({ instanceId }) => ({ get }) => { - const objectMetadataItems = get(objectMetadataItemsState); + const objectMetadataItems = jotaiStore.get( + objectMetadataItemsState.atom, + ); const currentRecordFilters = get( currentRecordFiltersComponentState.atomFamily({ instanceId }), ); diff --git a/packages/twenty-front/src/modules/object-record/record-group/hooks/useSetRecordGroups.ts b/packages/twenty-front/src/modules/object-record/record-group/hooks/useSetRecordGroups.ts index 13983d8d90..da64e03e72 100644 --- a/packages/twenty-front/src/modules/object-record/record-group/hooks/useSetRecordGroups.ts +++ b/packages/twenty-front/src/modules/object-record/record-group/hooks/useSetRecordGroups.ts @@ -5,6 +5,7 @@ import { recordGroupIdsComponentState } from '@/object-record/record-group/state import { type RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition'; import { recordIndexGroupFieldMetadataItemComponentState } from '@/object-record/record-index/states/recordIndexGroupFieldMetadataComponentState'; import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/utils/getRecordIndexIdFromObjectNamePluralAndViewId'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; import { type ViewGroup } from '@/views/types/ViewGroup'; import { mapViewGroupsToRecordGroupDefinitions } from '@/views/utils/mapViewGroupsToRecordGroupDefinitions'; @@ -27,9 +28,9 @@ export const useSetRecordGroups = () => { recordIndexId: string; objectMetadataItemId: string; }) => { - const objectMetadataItems = snapshot - .getLoadable(objectMetadataItemsState) - .getValue(); + const objectMetadataItems = jotaiStore.get( + objectMetadataItemsState.atom, + ); const objectMetadataItem = objectMetadataItems.find( (objectMetadataItem) => diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexLoadBaseOnContextStoreEffect.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexLoadBaseOnContextStoreEffect.tsx index 817b11bed8..237e560926 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexLoadBaseOnContextStoreEffect.tsx +++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexLoadBaseOnContextStoreEffect.tsx @@ -2,9 +2,9 @@ import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/ import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState'; import { useLoadRecordIndexStates } from '@/object-record/record-index/hooks/useLoadRecordIndexStates'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; +import { useFamilySelectorValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilySelectorValueV2'; import { coreViewFromViewIdFamilySelector } from '@/views/states/selectors/coreViewFromViewIdFamilySelector'; import { useEffect, useState } from 'react'; -import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; export const RecordIndexLoadBaseOnContextStoreEffect = () => { @@ -17,11 +17,9 @@ export const RecordIndexLoadBaseOnContextStoreEffect = () => { undefined, ); - const view = useRecoilValue( - coreViewFromViewIdFamilySelector({ - viewId: contextStoreCurrentViewId ?? '', - }), - ); + const view = useFamilySelectorValueV2(coreViewFromViewIdFamilySelector, { + viewId: contextStoreCurrentViewId ?? '', + }); const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow(); 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 0f3aa5d453..98d0a55b55 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 @@ -21,6 +21,7 @@ import { viewFieldAggregateOperationState } from '@/object-record/record-table/r import { type ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition'; import { convertAggregateOperationToExtendedAggregateOperation } from '@/object-record/utils/convertAggregateOperationToExtendedAggregateOperation'; import { filterAvailableTableColumns } from '@/object-record/utils/filterAvailableTableColumns'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState'; import { type View } from '@/views/types/View'; import { type ViewField } from '@/views/types/ViewField'; @@ -75,21 +76,17 @@ export const useLoadRecordIndexStates = () => { (field) => field.isActive && !isHiddenSystemField(field), ); - const filterableFieldMetadataItems = snapshot - .getLoadable( - availableFieldMetadataItemsForFilterFamilySelector({ - objectMetadataItemId: objectMetadataItem.id, - }), - ) - .getValue(); + const filterableFieldMetadataItems = jotaiStore.get( + availableFieldMetadataItemsForFilterFamilySelector.selectorFamily({ + objectMetadataItemId: objectMetadataItem.id, + }), + ); - const sortableFieldMetadataItems = snapshot - .getLoadable( - availableFieldMetadataItemsForSortFamilySelector({ - objectMetadataItemId: objectMetadataItem.id, - }), - ) - .getValue(); + const sortableFieldMetadataItems = jotaiStore.get( + availableFieldMetadataItemsForSortFamilySelector.selectorFamily({ + objectMetadataItemId: objectMetadataItem.id, + }), + ); const columnDefinitions: ColumnDefinition[] = activeFieldMetadataItems @@ -176,79 +173,71 @@ export const useLoadRecordIndexStates = () => { ); const loadRecordIndexStates = useRecoilCallback( - ({ snapshot }) => - async (view: View, objectMetadataItem: ObjectMetadataItem) => { - const filterableFieldMetadataItems = snapshot - .getLoadable( - availableFieldMetadataItemsForFilterFamilySelector({ - objectMetadataItemId: objectMetadataItem.id, - }), - ) - .getValue(); + () => async (view: View, objectMetadataItem: ObjectMetadataItem) => { + const filterableFieldMetadataItems = jotaiStore.get( + availableFieldMetadataItemsForFilterFamilySelector.selectorFamily({ + objectMetadataItemId: objectMetadataItem.id, + }), + ); - onViewFieldsChange(view.viewFields, objectMetadataItem); + onViewFieldsChange(view.viewFields, objectMetadataItem); - setRecordGroupsFromViewGroups({ - viewId: view.id, - mainGroupByFieldMetadataId: view.mainGroupByFieldMetadataId ?? '', - viewGroups: view.viewGroups, - objectMetadataItem, - }); + setRecordGroupsFromViewGroups({ + viewId: view.id, + mainGroupByFieldMetadataId: view.mainGroupByFieldMetadataId ?? '', + viewGroups: view.viewGroups, + objectMetadataItem, + }); - setContextStoreTargetedRecordsRuleComponentState((prev) => ({ - ...prev, - filters: mapViewFiltersToFilters( - view.viewFilters, - filterableFieldMetadataItems, - ), - })); + setContextStoreTargetedRecordsRuleComponentState((prev) => ({ + ...prev, + filters: mapViewFiltersToFilters( + view.viewFilters, + filterableFieldMetadataItems, + ), + })); - setRecordIndexViewType(view.type); - setRecordIndexOpenRecordIn(view.openRecordIn); - store.set(recordIndexOpenRecordInStateV2.atom, view.openRecordIn); + setRecordIndexViewType(view.type); + setRecordIndexOpenRecordIn(view.openRecordIn); + store.set(recordIndexOpenRecordInStateV2.atom, view.openRecordIn); - setRecordIndexCalendarFieldMetadataIdState( - view.calendarFieldMetadataId ?? null, + setRecordIndexCalendarFieldMetadataIdState( + view.calendarFieldMetadataId ?? null, + ); + + setRecordIndexShouldHideEmptyRecordGroups(view.shouldHideEmptyGroups); + + if (isDefined(view.mainGroupByFieldMetadataId)) { + const recordIndexGroupFieldMetadataItemId = + view.mainGroupByFieldMetadataId; + + const { fieldMetadataItem: recordIndexGroupFieldMetadataItem } = + getFieldMetadataItemByIdOrThrow(recordIndexGroupFieldMetadataItemId); + + setRecordIndexGroupFieldMetadataItem(recordIndexGroupFieldMetadataItem); + } + + const recordIndexGroupAggregateFieldMetadataItem = + objectMetadataItem.fields?.find( + (field) => field.id === view.kanbanAggregateOperationFieldMetadataId, ); - setRecordIndexShouldHideEmptyRecordGroups(view.shouldHideEmptyGroups); - - if (isDefined(view.mainGroupByFieldMetadataId)) { - const recordIndexGroupFieldMetadataItemId = - view.mainGroupByFieldMetadataId; - - const { fieldMetadataItem: recordIndexGroupFieldMetadataItem } = - getFieldMetadataItemByIdOrThrow( - recordIndexGroupFieldMetadataItemId, - ); - - setRecordIndexGroupFieldMetadataItem( - recordIndexGroupFieldMetadataItem, - ); - } - - const recordIndexGroupAggregateFieldMetadataItem = - objectMetadataItem.fields?.find( - (field) => - field.id === view.kanbanAggregateOperationFieldMetadataId, + if (isDefined(view.kanbanAggregateOperation)) { + const convertedAggregateOperation = + convertAggregateOperationToExtendedAggregateOperation( + view.kanbanAggregateOperation, + recordIndexGroupAggregateFieldMetadataItem?.type, ); - if (isDefined(view.kanbanAggregateOperation)) { - const convertedAggregateOperation = - convertAggregateOperationToExtendedAggregateOperation( - view.kanbanAggregateOperation, - recordIndexGroupAggregateFieldMetadataItem?.type, - ); + setRecordIndexGroupAggregateOperation(convertedAggregateOperation); + } - setRecordIndexGroupAggregateOperation(convertedAggregateOperation); - } - - if (isDefined(recordIndexGroupAggregateFieldMetadataItem)) { - setRecordIndexGroupAggregateFieldMetadataItem( - recordIndexGroupAggregateFieldMetadataItem, - ); - } - }, + if (isDefined(recordIndexGroupAggregateFieldMetadataItem)) { + setRecordIndexGroupAggregateFieldMetadataItem( + recordIndexGroupAggregateFieldMetadataItem, + ); + } + }, [ onViewFieldsChange, setRecordGroupsFromViewGroups, diff --git a/packages/twenty-front/src/modules/object-record/record-index/hooks/useRecordIndexFieldMetadataDerivedStates.ts b/packages/twenty-front/src/modules/object-record/record-index/hooks/useRecordIndexFieldMetadataDerivedStates.ts index 1c2bf74f20..17c8579bf2 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/hooks/useRecordIndexFieldMetadataDerivedStates.ts +++ b/packages/twenty-front/src/modules/object-record/record-index/hooks/useRecordIndexFieldMetadataDerivedStates.ts @@ -3,7 +3,7 @@ import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataI import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition'; import { currentRecordFieldsComponentState } from '@/object-record/record-field/states/currentRecordFieldsComponentState'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; -import { useRecoilValue } from 'recoil'; +import { useFamilySelectorValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilySelectorValueV2'; import { isDefined } from 'twenty-shared/utils'; export const useRecordIndexFieldMetadataDerivedStates = ( @@ -48,10 +48,11 @@ export const useRecordIndexFieldMetadataDerivedStates = ( ) : {}; - const labelIdentifierFieldMetadataItem = useRecoilValue( - labelIdentifierFieldMetadataItemSelector({ + const labelIdentifierFieldMetadataItem = useFamilySelectorValueV2( + labelIdentifierFieldMetadataItemSelector, + { objectMetadataItemId: objectMetadataItem?.id ?? '', - }), + }, ); return { diff --git a/packages/twenty-front/src/modules/object-record/record-show/components/SummaryCard.tsx b/packages/twenty-front/src/modules/object-record/record-show/components/SummaryCard.tsx index 1c21f06ee4..0df777d76b 100644 --- a/packages/twenty-front/src/modules/object-record/record-show/components/SummaryCard.tsx +++ b/packages/twenty-front/src/modules/object-record/record-show/components/SummaryCard.tsx @@ -14,6 +14,7 @@ import { RecordTitleCellContainerType } from '@/object-record/record-title-cell/ import { ShowPageSummaryCard } from '@/ui/layout/show-page/components/ShowPageSummaryCard'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { @@ -43,7 +44,7 @@ export const SummaryCard = ({ fieldName: 'createdAt', }), ); - const allowRequestsToTwentyIcons = useRecoilValue( + const allowRequestsToTwentyIcons = useRecoilValueV2( allowRequestsToTwentyIconsState, ); const isFilesFieldMigrated = useIsFeatureEnabled( diff --git a/packages/twenty-front/src/modules/object-record/record-store/states/selectors/recordStoreIdentifierSelector.ts b/packages/twenty-front/src/modules/object-record/record-store/states/selectors/recordStoreIdentifierSelector.ts index a524729f3e..348821c34e 100644 --- a/packages/twenty-front/src/modules/object-record/record-store/states/selectors/recordStoreIdentifierSelector.ts +++ b/packages/twenty-front/src/modules/object-record/record-store/states/selectors/recordStoreIdentifierSelector.ts @@ -3,6 +3,7 @@ import { selectorFamily } from 'recoil'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { getObjectRecordIdentifier } from '@/object-metadata/utils/getObjectRecordIdentifier'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { uncapitalize } from 'twenty-shared/utils'; export const recordStoreIdentifierFamilySelector = selectorFamily({ @@ -23,7 +24,7 @@ export const recordStoreIdentifierFamilySelector = selectorFamily({ recordFromStore?.__typename ?? '', ); - const objectMetadataItems = get(objectMetadataItemsState); + const objectMetadataItems = jotaiStore.get(objectMetadataItemsState.atom); const objectMetadataItem = objectMetadataItems.find( (item) => item.nameSingular === objectNameSingular, diff --git a/packages/twenty-front/src/modules/object-record/record-store/states/selectors/recordStoreIdentifiersSelector.ts b/packages/twenty-front/src/modules/object-record/record-store/states/selectors/recordStoreIdentifiersSelector.ts index 5fcf840034..5410c80190 100644 --- a/packages/twenty-front/src/modules/object-record/record-store/states/selectors/recordStoreIdentifiersSelector.ts +++ b/packages/twenty-front/src/modules/object-record/record-store/states/selectors/recordStoreIdentifiersSelector.ts @@ -1,26 +1,26 @@ -import { selectorFamily } from 'recoil'; - import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { getObjectRecordIdentifier } from '@/object-metadata/utils/getObjectRecordIdentifier'; -import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; +import { recordStoreFamilyStateV2 } from '@/object-record/record-store/states/recordStoreFamilyStateV2'; +import { type ObjectRecordIdentifier } from '@/object-record/types/ObjectRecordIdentifier'; +import { createFamilySelectorV2 } from '@/ui/utilities/state/jotai/utils/createFamilySelectorV2'; import { isDefined, uncapitalize } from 'twenty-shared/utils'; -export const recordStoreIdentifiersFamilySelector = selectorFamily({ +export const recordStoreIdentifiersFamilySelector = createFamilySelectorV2< + ObjectRecordIdentifier[], + { + recordIds: string[]; + allowRequestsToTwentyIcons: boolean; + } +>({ key: 'recordStoreIdentifiersFamilySelector', get: - ({ - recordIds, - allowRequestsToTwentyIcons, - }: { - recordIds: string[]; - allowRequestsToTwentyIcons: boolean; - }) => + ({ recordIds, allowRequestsToTwentyIcons }) => ({ get }) => { const objectMetadataItems = get(objectMetadataItemsState); return recordIds .map((recordId) => { - const recordFromStore = get(recordStoreFamilyState(recordId)); + const recordFromStore = get(recordStoreFamilyStateV2, recordId); const objectNameSingular = uncapitalize( recordFromStore?.__typename ?? '', ); diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/__stories__/perf/RecordTableCell.perf.stories.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/__stories__/perf/RecordTableCell.perf.stories.tsx index 508384a3a3..a83f3c552b 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/components/__stories__/perf/RecordTableCell.perf.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/components/__stories__/perf/RecordTableCell.perf.stories.tsx @@ -1,6 +1,6 @@ import { type Meta, type StoryObj } from '@storybook/react-vite'; import { useEffect } from 'react'; -import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; +import { useSetRecoilState } from 'recoil'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { getBasePathToShowPage } from '@/object-metadata/utils/getBasePathToShowPage'; @@ -14,6 +14,8 @@ import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorato import { getProfilingStory } from '~/testing/profiling/utils/getProfilingStory'; import { labelIdentifierFieldMetadataItemSelector } from '@/object-metadata/states/labelIdentifierFieldMetadataItemSelector'; +import { useFamilySelectorValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilySelectorValueV2'; +import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2'; import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition'; import { RecordComponentInstanceContextsWrapper } from '@/object-record/components/RecordComponentInstanceContextsWrapper'; @@ -47,7 +49,7 @@ const RelationFieldValueSetterEffect = () => { 'recordTableId', ); - const [, setObjectMetadataItems] = useRecoilState(objectMetadataItemsState); + const [, setObjectMetadataItems] = useRecoilStateV2(objectMetadataItemsState); useEffect(() => { setEntity(mockPerformance.entityValue); @@ -125,10 +127,11 @@ const meta: Meta = { ]), ); - const labelIdentifierFieldMetadataItem = useRecoilValue( - labelIdentifierFieldMetadataItemSelector({ + const labelIdentifierFieldMetadataItem = useFamilySelectorValueV2( + labelIdentifierFieldMetadataItemSelector, + { objectMetadataItemId: mockPerformance.objectMetadataItem.id, - }), + }, ); return ( diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/useBuildRecordInputFromFilters.ts b/packages/twenty-front/src/modules/object-record/record-table/hooks/useBuildRecordInputFromFilters.ts index f68e33fecb..0c1a851825 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/hooks/useBuildRecordInputFromFilters.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/hooks/useBuildRecordInputFromFilters.ts @@ -5,19 +5,18 @@ import { buildRecordInputFromFilter } from '@/object-record/record-table/utils/b import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; export const useBuildRecordInputFromFilters = ({ objectMetadataItem, }: { objectMetadataItem: ObjectMetadataItem; }) => { - // we might need to build a recoil callback for better performance const currentRecordFilters = useRecoilComponentValue( currentRecordFiltersComponentState, ); - const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); + const currentWorkspaceMember = useRecoilValueV2(currentWorkspaceMemberState); const buildRecordInputFromFilters = (): Partial => { return buildRecordInputFromFilter({ diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/internal/__tests__/useCloseRecordTableCellInGroup.test.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/internal/__tests__/useCloseRecordTableCellInGroup.test.tsx index 5a07f0ad5b..dfc044446e 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/internal/__tests__/useCloseRecordTableCellInGroup.test.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/internal/__tests__/useCloseRecordTableCellInGroup.test.tsx @@ -1,4 +1,5 @@ import { act, renderHook } from '@testing-library/react'; +import { Provider as JotaiProvider } from 'jotai'; import { RecoilRoot } from 'recoil'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; @@ -20,48 +21,58 @@ import { useCloseRecordTableCellInGroup } from '@/object-record/record-table/rec import { recordTableCellEditModePositionComponentState } from '@/object-record/record-table/states/recordTableCellEditModePositionComponentState'; import { useDragSelect } from '@/ui/utilities/drag-select/hooks/useDragSelect'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems'; const recordTableId = 'record-table-id'; -const Wrapper = ({ children }: { children: React.ReactNode }) => ( - { - snapshot.set(objectMetadataItemsState, generatedMockObjectMetadataItems); - }} - > - - - { + jotaiStore.set( + objectMetadataItemsState.atom, + generatedMockObjectMetadataItems, + ); + + return ( + + + - - - + + - - {children} - - - - - - - - -); + + + {children} + + + + + + + + + + ); +}; describe('useCloseRecordTableCellInGroup', () => { it('should work as expected', async () => { diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/internal/__tests__/useCloseRecordTableCellNoGroup.test.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/internal/__tests__/useCloseRecordTableCellNoGroup.test.tsx index d5cd4e7035..e27604d98f 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/internal/__tests__/useCloseRecordTableCellNoGroup.test.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/internal/__tests__/useCloseRecordTableCellNoGroup.test.tsx @@ -1,4 +1,5 @@ import { act, renderHook } from '@testing-library/react'; +import { Provider as JotaiProvider } from 'jotai'; import { RecoilRoot } from 'recoil'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; @@ -20,51 +21,59 @@ import { useCloseRecordTableCellNoGroup } from '@/object-record/record-table/rec import { recordTableCellEditModePositionComponentState } from '@/object-record/record-table/states/recordTableCellEditModePositionComponentState'; import { useDragSelect } from '@/ui/utilities/drag-select/hooks/useDragSelect'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems'; const recordTableId = 'record-table-id'; -const Wrapper = ({ children }: { children: React.ReactNode }) => ( - { - snapshot.set(objectMetadataItemsState, generatedMockObjectMetadataItems); - }} - > - - {}} - > - - { + jotaiStore.set( + objectMetadataItemsState.atom, + generatedMockObjectMetadataItems, + ); + + return ( + + + + {}} > - - + - - {children} - - - - - - - - -); + + + {children} + + + + + + + + + + ); +}; describe('useCloseRecordTableCellNoGroup', () => { it('should work as expected', async () => { diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableColumnHead.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableColumnHead.tsx index 893304502c..193e71fcff 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableColumnHead.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableColumnHead.tsx @@ -6,7 +6,7 @@ import { isFieldMetadataItemLabelIdentifierSelector } from '@/object-metadata/st import { type RecordField } from '@/object-record/record-field/types/RecordField'; import { shouldCompactRecordTableFirstColumnComponentState } from '@/object-record/record-table/states/shouldCompactRecordTableFirstColumnComponentState'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; -import { useRecoilValue } from 'recoil'; +import { useFamilySelectorValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilySelectorValueV2'; import { useIcons } from 'twenty-ui/display'; import { MOBILE_VIEWPORT } from 'twenty-ui/theme'; @@ -54,10 +54,9 @@ export const RecordTableColumnHead = ({ }: RecordTableColumnHeadProps) => { const theme = useTheme(); - const correspondingFieldMetadataItem = useRecoilValue( - fieldMetadataItemByIdSelector({ - fieldMetadataItemId: recordField.fieldMetadataItemId, - }), + const correspondingFieldMetadataItem = useFamilySelectorValueV2( + fieldMetadataItemByIdSelector, + { fieldMetadataItemId: recordField.fieldMetadataItemId }, ); const { getIcon } = useIcons(); @@ -65,10 +64,9 @@ export const RecordTableColumnHead = ({ correspondingFieldMetadataItem.foundFieldMetadataItem?.icon, ); - const isLabelIdentifier = useRecoilValue( - isFieldMetadataItemLabelIdentifierSelector({ - fieldMetadataItemId: recordField.fieldMetadataItemId, - }), + const isLabelIdentifier = useFamilySelectorValueV2( + isFieldMetadataItemLabelIdentifierSelector, + { fieldMetadataItemId: recordField.fieldMetadataItemId }, ); const shouldCompactRecordTableFirstColumn = useRecoilComponentValue( diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableColumnHeadDropdownMenu.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableColumnHeadDropdownMenu.tsx index 1df12bd145..0ee1767b96 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableColumnHeadDropdownMenu.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableColumnHeadDropdownMenu.tsx @@ -14,7 +14,7 @@ import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown'; import { useToggleScrollWrapper } from '@/ui/utilities/scroll/hooks/useToggleScrollWrapper'; import styled from '@emotion/styled'; import { useLingui } from '@lingui/react/macro'; -import { useRecoilValue } from 'recoil'; +import { useFamilySelectorValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilySelectorValueV2'; import { IconArrowLeft, IconArrowRight, @@ -44,10 +44,9 @@ export const RecordTableColumnHeadDropdownMenu = ({ const { visibleRecordFields } = useRecordTableContextOrThrow(); - const isLabelIdentifier = useRecoilValue( - isFieldMetadataItemLabelIdentifierSelector({ - fieldMetadataItemId: recordField.fieldMetadataItemId, - }), + const isLabelIdentifier = useFamilySelectorValueV2( + isFieldMetadataItemLabelIdentifierSelector, + { fieldMetadataItemId: recordField.fieldMetadataItemId }, ); const secondVisibleRecordField = visibleRecordFields[1]; @@ -125,10 +124,9 @@ export const RecordTableColumnHeadDropdownMenu = ({ openRecordFilterChipFromTableHeader(recordField.fieldMetadataItemId); }; - const { isFilterable, isSortable } = useRecoilValue( - isFieldMetadataItemFilterableAndSortableSelector({ - fieldMetadataItemId: recordField.fieldMetadataItemId, - }), + const { isFilterable, isSortable } = useFamilySelectorValueV2( + isFieldMetadataItemFilterableAndSortableSelector, + { fieldMetadataItemId: recordField.fieldMetadataItemId }, ); const showSeparator = diff --git a/packages/twenty-front/src/modules/object-record/record-table/virtualization/components/RecordTableVirtualizedFieldMetadataUpdateEffect.tsx b/packages/twenty-front/src/modules/object-record/record-table/virtualization/components/RecordTableVirtualizedFieldMetadataUpdateEffect.tsx index 920eb7c584..f5903089d2 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/virtualization/components/RecordTableVirtualizedFieldMetadataUpdateEffect.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/virtualization/components/RecordTableVirtualizedFieldMetadataUpdateEffect.tsx @@ -5,8 +5,8 @@ import { useResetVirtualizationBecauseDataChanged } from '@/object-record/record import { lastProcessedFieldMetadataUpdateIdComponentState } from '@/object-record/record-table/virtualization/states/lastProcessedFieldMetadataUpdateIdComponentState'; import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { useEffect } from 'react'; -import { useRecoilValue } from 'recoil'; export const RecordTableVirtualizedFieldMetadataUpdateEffect = () => { const { objectNameSingular } = useRecordTableContextOrThrow(); @@ -18,7 +18,7 @@ export const RecordTableVirtualizedFieldMetadataUpdateEffect = () => { visibleRecordFieldsComponentSelector, ); - const lastFieldMetadataItemUpdate = useRecoilValue( + const lastFieldMetadataItemUpdate = useRecoilValueV2( lastFieldMetadataItemUpdateState, ); diff --git a/packages/twenty-front/src/modules/object-record/record-table/virtualization/components/RecordTableVirtualizedSSESubscribeEffect.tsx b/packages/twenty-front/src/modules/object-record/record-table/virtualization/components/RecordTableVirtualizedSSESubscribeEffect.tsx index db17c0bb12..1f4d85a4dc 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/virtualization/components/RecordTableVirtualizedSSESubscribeEffect.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/virtualization/components/RecordTableVirtualizedSSESubscribeEffect.tsx @@ -1,3 +1,5 @@ +import { useMemo } from 'react'; + import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy'; import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState'; import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies'; @@ -26,9 +28,8 @@ export const RecordTableVirtualizedSSESubscribeEffect = () => { const queryId = `record-table-virtualized-${objectMetadataItem.nameSingular}`; - useListenToEventsForQuery({ - queryId, - operationSignature: { + const operationSignature = useMemo( + () => ({ objectNameSingular: objectMetadataItem.nameSingular, variables: { filter: computeRecordGqlOperationFilter({ @@ -39,7 +40,19 @@ export const RecordTableVirtualizedSSESubscribeEffect = () => { }), orderBy: turnSortsIntoOrderBy(objectMetadataItem, currentRecordSorts), }, - }, + }), + [ + objectMetadataItem, + currentRecordFilters, + currentRecordFilterGroups, + filterValueDependencies, + currentRecordSorts, + ], + ); + + useListenToEventsForQuery({ + queryId, + operationSignature, }); return null; diff --git a/packages/twenty-front/src/modules/object-record/spreadsheet-import/hooks/__tests__/useBuildSpreadSheetImportFields.test.tsx b/packages/twenty-front/src/modules/object-record/spreadsheet-import/hooks/__tests__/useBuildSpreadSheetImportFields.test.tsx index 1694a7e8c9..2471f1fd73 100644 --- a/packages/twenty-front/src/modules/object-record/spreadsheet-import/hooks/__tests__/useBuildSpreadSheetImportFields.test.tsx +++ b/packages/twenty-front/src/modules/object-record/spreadsheet-import/hooks/__tests__/useBuildSpreadSheetImportFields.test.tsx @@ -1,10 +1,12 @@ import { renderHook } from '@testing-library/react'; +import { Provider as JotaiProvider } from 'jotai'; import { type ReactNode } from 'react'; -import { RecoilRoot, useSetRecoilState } from 'recoil'; +import { RecoilRoot } from 'recoil'; import { useIcons } from 'twenty-ui/display'; import { JestObjectMetadataItemSetter } from '~/testing/jest/JestObjectMetadataItemSetter'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; import { type IndexMetadataItem } from '@/object-metadata/types/IndexMetadataItem'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; @@ -13,9 +15,11 @@ import { FieldMetadataType, RelationType } from '~/generated-metadata/graphql'; const Wrapper = ({ children }: { children: ReactNode }) => { return ( - - {children} - + + + {children} + + ); }; @@ -33,6 +37,7 @@ describe('useBuildSpreadSheetImportFields', () => { getIcons: () => ({}), }); jest.clearAllMocks(); + jotaiStore.set(objectMetadataItemsState.atom, []); }); const createMockFieldMetadataItem = ( @@ -76,17 +81,9 @@ describe('useBuildSpreadSheetImportFields', () => { }) as ObjectMetadataItem; it('should build importFields for basic field types', () => { - const { result } = renderHook( - () => { - const setObjectMetadataItems = useSetRecoilState( - objectMetadataItemsState, - ); - setObjectMetadataItems([]); - - return useBuildSpreadsheetImportFields(); - }, - { wrapper: Wrapper }, - ); + const { result } = renderHook(() => useBuildSpreadsheetImportFields(), { + wrapper: Wrapper, + }); const fieldMetadataItems: FieldMetadataItem[] = [ createMockFieldMetadataItem({ @@ -137,17 +134,9 @@ describe('useBuildSpreadSheetImportFields', () => { }); it('should build importFields for select types', () => { - const { result } = renderHook( - () => { - const setObjectMetadataItems = useSetRecoilState( - objectMetadataItemsState, - ); - setObjectMetadataItems([]); - - return useBuildSpreadsheetImportFields(); - }, - { wrapper: Wrapper }, - ); + const { result } = renderHook(() => useBuildSpreadsheetImportFields(), { + wrapper: Wrapper, + }); const fieldMetadataItems: FieldMetadataItem[] = [ createMockFieldMetadataItem({ @@ -227,17 +216,9 @@ describe('useBuildSpreadSheetImportFields', () => { }); it('should build importFields for composite types (full name)', () => { - const { result } = renderHook( - () => { - const setObjectMetadataItems = useSetRecoilState( - objectMetadataItemsState, - ); - setObjectMetadataItems([]); - - return useBuildSpreadsheetImportFields(); - }, - { wrapper: Wrapper }, - ); + const { result } = renderHook(() => useBuildSpreadsheetImportFields(), { + wrapper: Wrapper, + }); const fieldMetadataItems: FieldMetadataItem[] = [ createMockFieldMetadataItem({ @@ -269,17 +250,9 @@ describe('useBuildSpreadSheetImportFields', () => { }); it('should filter out ACTOR fields', () => { - const { result } = renderHook( - () => { - const setObjectMetadataItems = useSetRecoilState( - objectMetadataItemsState, - ); - setObjectMetadataItems([]); - - return useBuildSpreadsheetImportFields(); - }, - { wrapper: Wrapper }, - ); + const { result } = renderHook(() => useBuildSpreadsheetImportFields(), { + wrapper: Wrapper, + }); const fieldMetadataItems: FieldMetadataItem[] = [ createMockFieldMetadataItem({ @@ -304,17 +277,9 @@ describe('useBuildSpreadSheetImportFields', () => { }); it('should return empty array for unsupported field types', () => { - const { result } = renderHook( - () => { - const setObjectMetadataItems = useSetRecoilState( - objectMetadataItemsState, - ); - setObjectMetadataItems([]); - - return useBuildSpreadsheetImportFields(); - }, - { wrapper: Wrapper }, - ); + const { result } = renderHook(() => useBuildSpreadsheetImportFields(), { + wrapper: Wrapper, + }); const fieldMetadataItems: FieldMetadataItem[] = [ createMockFieldMetadataItem({ @@ -336,97 +301,100 @@ describe('useBuildSpreadSheetImportFields', () => { }); it('should build importFields for relation field type', () => { - const { result } = renderHook( - () => { - const setObjectMetadataItems = useSetRecoilState( - objectMetadataItemsState, - ); - - const targetObjectMetadata = createMockObjectMetadataItem({ - id: 'target-object-id', - nameSingular: 'company', - namePlural: 'companies', - labelSingular: 'Company', - labelPlural: 'Companies', - fields: [ - createMockFieldMetadataItem({ - id: 'company-id-field', - name: 'id', - label: 'ID', - type: FieldMetadataType.UUID, - }), - createMockFieldMetadataItem({ - id: 'company-name-field', - name: 'name', - label: 'Name', - type: FieldMetadataType.TEXT, - }), - createMockFieldMetadataItem({ - id: 'company-email-field', - name: 'emails', - label: 'Emails', - type: FieldMetadataType.EMAILS, - }), + const targetObjectMetadata = createMockObjectMetadataItem({ + id: 'target-object-id', + nameSingular: 'company', + namePlural: 'companies', + labelSingular: 'Company', + labelPlural: 'Companies', + fields: [ + createMockFieldMetadataItem({ + id: 'company-id-field', + name: 'id', + label: 'ID', + type: FieldMetadataType.UUID, + }), + createMockFieldMetadataItem({ + id: 'company-name-field', + name: 'name', + label: 'Name', + type: FieldMetadataType.TEXT, + }), + createMockFieldMetadataItem({ + id: 'company-email-field', + name: 'emails', + label: 'Emails', + type: FieldMetadataType.EMAILS, + }), + ], + indexMetadatas: [ + { + id: 'primary-key-index', + name: 'primaryKeyIndex', + createdAt: '2023-01-01', + updatedAt: '2023-01-01', + isUnique: true, + indexFieldMetadatas: [ + { + id: 'index-field-1', + fieldMetadataId: 'company-id-field', + createdAt: '2023-01-01', + updatedAt: '2023-01-01', + order: 0, + }, ], - indexMetadatas: [ + }, + { + id: 'unique-name-index', + name: 'uniqueNameIndex', + createdAt: '2023-01-01', + updatedAt: '2023-01-01', + isUnique: true, + indexFieldMetadatas: [ { - id: 'primary-key-index', - name: 'primaryKeyIndex', + id: 'index-field-2', + fieldMetadataId: 'company-name-field', createdAt: '2023-01-01', updatedAt: '2023-01-01', - isUnique: true, - indexFieldMetadatas: [ - { - id: 'index-field-1', - fieldMetadataId: 'company-id-field', - createdAt: '2023-01-01', - updatedAt: '2023-01-01', - order: 0, - }, - ], + order: 0, }, + ], + }, + { + id: 'unique-email-index', + name: 'uniqueEmailIndex', + createdAt: '2023-01-01', + updatedAt: '2023-01-01', + isUnique: true, + indexFieldMetadatas: [ { - id: 'unique-name-index', - name: 'uniqueNameIndex', + id: 'index-field-3', + fieldMetadataId: 'company-email-field', createdAt: '2023-01-01', updatedAt: '2023-01-01', - isUnique: true, - indexFieldMetadatas: [ - { - id: 'index-field-2', - fieldMetadataId: 'company-name-field', - createdAt: '2023-01-01', - updatedAt: '2023-01-01', - order: 0, - }, - ], + order: 0, }, - { - id: 'unique-email-index', - name: 'uniqueEmailIndex', - createdAt: '2023-01-01', - updatedAt: '2023-01-01', - isUnique: true, - indexFieldMetadatas: [ - { - id: 'index-field-3', - fieldMetadataId: 'company-email-field', - createdAt: '2023-01-01', - updatedAt: '2023-01-01', - order: 0, - }, - ], - }, - ] as IndexMetadataItem[], - }); + ], + }, + ] as IndexMetadataItem[], + }); - setObjectMetadataItems([targetObjectMetadata]); - - return useBuildSpreadsheetImportFields(); - }, - { wrapper: Wrapper }, + const RelationTestWrapper = ({ children }: { children: ReactNode }) => ( + + + + {children} + + + ); + const { result } = renderHook(() => useBuildSpreadsheetImportFields(), { + wrapper: RelationTestWrapper, + }); + const fieldMetadataItems: FieldMetadataItem[] = [ createMockFieldMetadataItem({ type: FieldMetadataType.RELATION, diff --git a/packages/twenty-front/src/modules/object-record/spreadsheet-import/hooks/useBuildSpreadSheetImportFields.ts b/packages/twenty-front/src/modules/object-record/spreadsheet-import/hooks/useBuildSpreadSheetImportFields.ts index e495b96e0f..6787f3376c 100644 --- a/packages/twenty-front/src/modules/object-record/spreadsheet-import/hooks/useBuildSpreadSheetImportFields.ts +++ b/packages/twenty-front/src/modules/object-record/spreadsheet-import/hooks/useBuildSpreadSheetImportFields.ts @@ -14,7 +14,7 @@ import { type SpreadsheetImportField, type SpreadsheetImportFields, } from '@/spreadsheet-import/types'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { assertUnreachable, getUniqueConstraintsFields, @@ -25,7 +25,7 @@ import { FieldMetadataType, RelationType } from '~/generated-metadata/graphql'; export const useBuildSpreadsheetImportFields = () => { const { getIcon } = useIcons(); - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); const buildSpreadsheetImportFields = ( fieldMetadataItems: FieldMetadataItem[], diff --git a/packages/twenty-front/src/modules/onboarding/hooks/__tests__/useOnboardingStatus.test.ts b/packages/twenty-front/src/modules/onboarding/hooks/__tests__/useOnboardingStatus.test.ts index 39f8986b78..efc5d41a42 100644 --- a/packages/twenty-front/src/modules/onboarding/hooks/__tests__/useOnboardingStatus.test.ts +++ b/packages/twenty-front/src/modules/onboarding/hooks/__tests__/useOnboardingStatus.test.ts @@ -1,12 +1,15 @@ import { renderHook } from '@testing-library/react'; -import { act } from 'react'; -import { RecoilRoot, useSetRecoilState } from 'recoil'; +import { createElement, act } from 'react'; +import { Provider as JotaiProvider } from 'jotai'; +import { RecoilRoot } from 'recoil'; import { type CurrentUser, currentUserState, } from '@/auth/states/currentUserState'; import { tokenPairState } from '@/auth/states/tokenPairState'; +import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { useOnboardingStatus } from '@/onboarding/hooks/useOnboardingStatus'; import { OnboardingStatus } from '~/generated-metadata/graphql'; @@ -22,12 +25,19 @@ const currentUser = { onboardingStatus: null, } as CurrentUser; +const Wrapper = ({ children }: { children: React.ReactNode }) => + createElement( + JotaiProvider, + { store: jotaiStore }, + createElement(RecoilRoot, null, children), + ); + const renderHooks = () => { const { result } = renderHook( () => { const onboardingStatus = useOnboardingStatus(); - const setCurrentUser = useSetRecoilState(currentUserState); - const setTokenPair = useSetRecoilState(tokenPairState); + const setCurrentUser = useSetRecoilStateV2(currentUserState); + const setTokenPair = useSetRecoilStateV2(tokenPairState); return { onboardingStatus, @@ -36,7 +46,7 @@ const renderHooks = () => { }; }, { - wrapper: RecoilRoot, + wrapper: Wrapper, }, ); return { result }; diff --git a/packages/twenty-front/src/modules/onboarding/hooks/__tests__/useSetNextOnboardingStatus.test.ts b/packages/twenty-front/src/modules/onboarding/hooks/__tests__/useSetNextOnboardingStatus.test.ts index 14ec4f09cb..d04a3c84f4 100644 --- a/packages/twenty-front/src/modules/onboarding/hooks/__tests__/useSetNextOnboardingStatus.test.ts +++ b/packages/twenty-front/src/modules/onboarding/hooks/__tests__/useSetNextOnboardingStatus.test.ts @@ -1,11 +1,19 @@ import { act, renderHook } from '@testing-library/react'; -import { RecoilRoot, useRecoilState, useSetRecoilState } from 'recoil'; +import { createElement } from 'react'; +import { Provider as JotaiProvider } from 'jotai'; +import { RecoilRoot } from 'recoil'; import { v4 } from 'uuid'; import { currentUserState } from '@/auth/states/currentUserState'; import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { useSetNextOnboardingStatus } from '@/onboarding/hooks/useSetNextOnboardingStatus'; +import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2'; +import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; +import { + jotaiStore, + resetJotaiStore, +} from '@/ui/utilities/state/jotai/jotaiStore'; import { OnboardingStatus, @@ -17,6 +25,13 @@ import { mockedUserData, } from '~/testing/mock-data/users'; +const Wrapper = ({ children }: { children: React.ReactNode }) => + createElement( + JotaiProvider, + { store: jotaiStore }, + createElement(RecoilRoot, null, children), + ); + const renderHooks = ( onboardingStatus: OnboardingStatus, withCurrentBillingSubscription: boolean, @@ -25,11 +40,11 @@ const renderHooks = ( ) => { const { result } = renderHook( () => { - const [currentUser, setCurrentUser] = useRecoilState(currentUserState); - const setCurrentUserWorkspace = useSetRecoilState( + const [currentUser, setCurrentUser] = useRecoilStateV2(currentUserState); + const setCurrentUserWorkspace = useSetRecoilStateV2( currentUserWorkspaceState, ); - const setCurrentWorkspace = useSetRecoilState(currentWorkspaceState); + const setCurrentWorkspace = useSetRecoilStateV2(currentWorkspaceState); const setNextOnboardingStatus = useSetNextOnboardingStatus(); return { currentUser, @@ -40,7 +55,7 @@ const renderHooks = ( }; }, { - wrapper: RecoilRoot, + wrapper: Wrapper, }, ); act(() => { @@ -69,6 +84,10 @@ const renderHooks = ( }; describe('useSetNextOnboardingStatus', () => { + beforeEach(() => { + resetJotaiStore(); + }); + it('should set next onboarding status for ProfileCreation', () => { const nextOnboardingStatus = renderHooks( OnboardingStatus.PROFILE_CREATION, diff --git a/packages/twenty-front/src/modules/onboarding/hooks/useOnboardingStatus.ts b/packages/twenty-front/src/modules/onboarding/hooks/useOnboardingStatus.ts index fb36541ed5..fd5357d320 100644 --- a/packages/twenty-front/src/modules/onboarding/hooks/useOnboardingStatus.ts +++ b/packages/twenty-front/src/modules/onboarding/hooks/useOnboardingStatus.ts @@ -1,11 +1,10 @@ -import { useRecoilValue } from 'recoil'; - import { useIsLogged } from '@/auth/hooks/useIsLogged'; import { currentUserState } from '@/auth/states/currentUserState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { type OnboardingStatus } from '~/generated-metadata/graphql'; export const useOnboardingStatus = (): OnboardingStatus | null | undefined => { - const currentUser = useRecoilValue(currentUserState); + const currentUser = useRecoilValueV2(currentUserState); const isLoggedIn = useIsLogged(); return isLoggedIn ? currentUser?.onboardingStatus : undefined; }; diff --git a/packages/twenty-front/src/modules/onboarding/hooks/useSetNextOnboardingStatus.ts b/packages/twenty-front/src/modules/onboarding/hooks/useSetNextOnboardingStatus.ts index 33653861d6..18109ca934 100644 --- a/packages/twenty-front/src/modules/onboarding/hooks/useSetNextOnboardingStatus.ts +++ b/packages/twenty-front/src/modules/onboarding/hooks/useSetNextOnboardingStatus.ts @@ -1,5 +1,3 @@ -import { useRecoilCallback, useRecoilValue } from 'recoil'; - import { isDefined } from 'twenty-shared/utils'; import { @@ -12,7 +10,10 @@ import { } from '@/auth/states/currentWorkspaceState'; import { calendarBookingPageIdState } from '@/client-config/states/calendarBookingPageIdState'; import { usePermissionFlagMap } from '@/settings/roles/hooks/usePermissionFlagMap'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; +import { useCallback } from 'react'; import { OnboardingStatus, PermissionFlagType, @@ -62,37 +63,33 @@ const getNextOnboardingStatus = ({ }; export const useSetNextOnboardingStatus = () => { - const currentUser = useRecoilValue(currentUserState); - const currentWorkspace = useRecoilValue(currentWorkspaceState); - const calendarBookingPageId = useRecoilValue(calendarBookingPageIdState); + const currentUser = useRecoilValueV2(currentUserState); + const currentWorkspace = useRecoilValueV2(currentWorkspaceState); + const calendarBookingPageId = useRecoilValueV2(calendarBookingPageIdState); const permissionMap = usePermissionFlagMap(); const isAccountSyncEnabled = permissionMap[PermissionFlagType.CONNECTED_ACCOUNTS]; - return useRecoilCallback( - ({ set }) => - () => { - const nextOnboardingStatus = getNextOnboardingStatus({ - currentUser, - currentWorkspace, - calendarBookingPageId, - isAccountSyncEnabled, - }); - set(currentUserState, (current) => { - if (isDefined(current)) { - return { - ...current, - onboardingStatus: nextOnboardingStatus, - }; - } - return current; - }); - }, - [ + return useCallback(() => { + const nextOnboardingStatus = getNextOnboardingStatus({ currentUser, currentWorkspace, calendarBookingPageId, isAccountSyncEnabled, - ], - ); + }); + jotaiStore.set(currentUserState.atom, (current) => { + if (isDefined(current)) { + return { + ...current, + onboardingStatus: nextOnboardingStatus, + }; + } + return current; + }); + }, [ + currentUser, + currentWorkspace, + calendarBookingPageId, + isAccountSyncEnabled, + ]); }; diff --git a/packages/twenty-front/src/modules/page-layout/hooks/useIsCurrentObjectCustom.ts b/packages/twenty-front/src/modules/page-layout/hooks/useIsCurrentObjectCustom.ts index 01f74ea161..0f689db759 100644 --- a/packages/twenty-front/src/modules/page-layout/hooks/useIsCurrentObjectCustom.ts +++ b/packages/twenty-front/src/modules/page-layout/hooks/useIsCurrentObjectCustom.ts @@ -1,16 +1,17 @@ import { objectMetadataItemFamilySelector } from '@/object-metadata/states/objectMetadataItemFamilySelector'; import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext'; -import { useRecoilValue } from 'recoil'; +import { useFamilySelectorValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilySelectorValueV2'; import { isDefined } from 'twenty-shared/utils'; export const useIsCurrentObjectCustom = () => { const { targetRecordIdentifier } = useLayoutRenderingContext(); - const objectMetadataItem = useRecoilValue( - objectMetadataItemFamilySelector({ + const objectMetadataItem = useFamilySelectorValueV2( + objectMetadataItemFamilySelector, + { objectName: targetRecordIdentifier?.targetObjectNameSingular ?? '', objectNameType: 'singular', - }), + }, ); if (!isDefined(targetRecordIdentifier)) { diff --git a/packages/twenty-front/src/modules/page-layout/hooks/useOpportunityDefaultChartConfig.ts b/packages/twenty-front/src/modules/page-layout/hooks/useOpportunityDefaultChartConfig.ts index 84a3f87d70..e625be3c73 100644 --- a/packages/twenty-front/src/modules/page-layout/hooks/useOpportunityDefaultChartConfig.ts +++ b/packages/twenty-front/src/modules/page-layout/hooks/useOpportunityDefaultChartConfig.ts @@ -1,16 +1,17 @@ import { objectMetadataItemFamilySelector } from '@/object-metadata/states/objectMetadataItemFamilySelector'; -import { useRecoilValue } from 'recoil'; +import { useFamilySelectorValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilySelectorValueV2'; import { isDefined } from 'twenty-shared/utils'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { type GraphWidgetFieldSelection } from '@/page-layout/types/GraphWidgetFieldSelection'; export const useOpportunityDefaultChartConfig = () => { - const opportunityObjectMetadata = useRecoilValue( - objectMetadataItemFamilySelector({ + const opportunityObjectMetadata = useFamilySelectorValueV2( + objectMetadataItemFamilySelector, + { objectName: CoreObjectNameSingular.Opportunity, objectNameType: 'singular', - }), + }, ); const buildBarChartFieldSelection = (): diff --git a/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetActionFieldSeeAll.tsx b/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetActionFieldSeeAll.tsx index d440633e2c..37c53aa23a 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetActionFieldSeeAll.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetActionFieldSeeAll.tsx @@ -9,12 +9,12 @@ import { isFieldWidget } from '@/page-layout/widgets/field/utils/isFieldWidget'; import { useCurrentWidget } from '@/page-layout/widgets/hooks/useCurrentWidget'; import { useTargetRecord } from '@/ui/layout/contexts/useTargetRecord'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; +import { useFamilySelectorValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilySelectorValueV2'; import { coreIndexViewIdFromObjectMetadataItemFamilySelector } from '@/views/states/selectors/coreIndexViewIdFromObjectMetadataItemFamilySelector'; import { css } from '@emotion/react'; import styled from '@emotion/styled'; import { t } from '@lingui/core/macro'; import { Link } from 'react-router-dom'; -import { useRecoilValue } from 'recoil'; import { AppPath, ViewFilterOperand } from 'twenty-shared/types'; import { getAppPath, isDefined } from 'twenty-shared/utils'; import { @@ -92,10 +92,9 @@ export const WidgetActionFieldSeeAll = () => { ({ id }) => id === relationMetadata?.relationFieldMetadataId, ); - const indexViewId = useRecoilValue( - coreIndexViewIdFromObjectMetadataItemFamilySelector({ - objectMetadataItemId: relationObjectMetadataItem?.id ?? '', - }), + const indexViewId = useFamilySelectorValueV2( + coreIndexViewIdFromObjectMetadataItemFamilySelector, + { objectMetadataItemId: relationObjectMetadataItem?.id ?? '' }, ); if ( diff --git a/packages/twenty-front/src/modules/page-layout/widgets/components/__stories__/DashboardWidgetPlaceholder.stories.tsx b/packages/twenty-front/src/modules/page-layout/widgets/components/__stories__/DashboardWidgetPlaceholder.stories.tsx index 56e3965b45..333d7f89d3 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/components/__stories__/DashboardWidgetPlaceholder.stories.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/components/__stories__/DashboardWidgetPlaceholder.stories.tsx @@ -1,5 +1,6 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { shouldAppBeLoadingState } from '@/object-metadata/states/shouldAppBeLoadingState'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { PageLayoutContentProvider } from '@/page-layout/contexts/PageLayoutContentContext'; import { PAGE_LAYOUT_TEST_INSTANCE_ID, @@ -34,11 +35,11 @@ const meta: Meta = { decorators: [ (Story) => { const initializeState = (snapshot: MutableSnapshot) => { - snapshot.set( - objectMetadataItemsState, + jotaiStore.set( + objectMetadataItemsState.atom, generatedMockObjectMetadataItems, ); - snapshot.set(shouldAppBeLoadingState, false); + jotaiStore.set(shouldAppBeLoadingState.atom, false); snapshot.set( pageLayoutPersistedComponentState.atomFamily({ instanceId: PAGE_LAYOUT_TEST_INSTANCE_ID, diff --git a/packages/twenty-front/src/modules/page-layout/widgets/components/__stories__/WidgetRenderer.stories.tsx b/packages/twenty-front/src/modules/page-layout/widgets/components/__stories__/WidgetRenderer.stories.tsx index 924d71328f..b49c59ac07 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/components/__stories__/WidgetRenderer.stories.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/components/__stories__/WidgetRenderer.stories.tsx @@ -14,6 +14,7 @@ import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceSta import { ApolloCoreClientContext } from '@/object-metadata/contexts/ApolloCoreClientContext'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { shouldAppBeLoadingState } from '@/object-metadata/states/shouldAppBeLoadingState'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; @@ -279,8 +280,11 @@ export const WithNumberChart: Story = { }; const initializeState = (snapshot: MutableSnapshot) => { - snapshot.set(objectMetadataItemsState, generatedMockObjectMetadataItems); - snapshot.set(shouldAppBeLoadingState, false); + jotaiStore.set( + objectMetadataItemsState.atom, + generatedMockObjectMetadataItems, + ); + jotaiStore.set(shouldAppBeLoadingState.atom, false); const pageLayoutData = createPageLayoutWithWidget(widget); snapshot.set( pageLayoutPersistedComponentState.atomFamily({ @@ -364,8 +368,11 @@ export const WithGaugeChart: Story = { }; const initializeState = (snapshot: MutableSnapshot) => { - snapshot.set(objectMetadataItemsState, generatedMockObjectMetadataItems); - snapshot.set(shouldAppBeLoadingState, false); + jotaiStore.set( + objectMetadataItemsState.atom, + generatedMockObjectMetadataItems, + ); + jotaiStore.set(shouldAppBeLoadingState.atom, false); const pageLayoutData = createPageLayoutWithWidget(widget); snapshot.set( pageLayoutPersistedComponentState.atomFamily({ @@ -453,8 +460,11 @@ export const WithBarChart: Story = { }; const initializeState = (snapshot: MutableSnapshot) => { - snapshot.set(objectMetadataItemsState, generatedMockObjectMetadataItems); - snapshot.set(shouldAppBeLoadingState, false); + jotaiStore.set( + objectMetadataItemsState.atom, + generatedMockObjectMetadataItems, + ); + jotaiStore.set(shouldAppBeLoadingState.atom, false); const pageLayoutData = createPageLayoutWithWidget(widget); snapshot.set( pageLayoutPersistedComponentState.atomFamily({ @@ -545,8 +555,11 @@ export const SmallWidget: Story = { }; const initializeState = (snapshot: MutableSnapshot) => { - snapshot.set(objectMetadataItemsState, generatedMockObjectMetadataItems); - snapshot.set(shouldAppBeLoadingState, false); + jotaiStore.set( + objectMetadataItemsState.atom, + generatedMockObjectMetadataItems, + ); + jotaiStore.set(shouldAppBeLoadingState.atom, false); const pageLayoutData = createPageLayoutWithWidget(widget); snapshot.set( pageLayoutPersistedComponentState.atomFamily({ @@ -641,8 +654,11 @@ export const MediumWidget: Story = { }; const initializeState = (snapshot: MutableSnapshot) => { - snapshot.set(objectMetadataItemsState, generatedMockObjectMetadataItems); - snapshot.set(shouldAppBeLoadingState, false); + jotaiStore.set( + objectMetadataItemsState.atom, + generatedMockObjectMetadataItems, + ); + jotaiStore.set(shouldAppBeLoadingState.atom, false); const pageLayoutData = createPageLayoutWithWidget(widget); snapshot.set( pageLayoutPersistedComponentState.atomFamily({ @@ -737,8 +753,11 @@ export const LargeWidget: Story = { }; const initializeState = (snapshot: MutableSnapshot) => { - snapshot.set(objectMetadataItemsState, generatedMockObjectMetadataItems); - snapshot.set(shouldAppBeLoadingState, false); + jotaiStore.set( + objectMetadataItemsState.atom, + generatedMockObjectMetadataItems, + ); + jotaiStore.set(shouldAppBeLoadingState.atom, false); const pageLayoutData = createPageLayoutWithWidget(widget); snapshot.set( pageLayoutPersistedComponentState.atomFamily({ @@ -829,8 +848,11 @@ export const WideWidget: Story = { }; const initializeState = (snapshot: MutableSnapshot) => { - snapshot.set(objectMetadataItemsState, generatedMockObjectMetadataItems); - snapshot.set(shouldAppBeLoadingState, false); + jotaiStore.set( + objectMetadataItemsState.atom, + generatedMockObjectMetadataItems, + ); + jotaiStore.set(shouldAppBeLoadingState.atom, false); const pageLayoutData = createPageLayoutWithWidget(widget); snapshot.set( pageLayoutPersistedComponentState.atomFamily({ @@ -925,8 +947,11 @@ export const TallWidget: Story = { }; const initializeState = (snapshot: MutableSnapshot) => { - snapshot.set(objectMetadataItemsState, generatedMockObjectMetadataItems); - snapshot.set(shouldAppBeLoadingState, false); + jotaiStore.set( + objectMetadataItemsState.atom, + generatedMockObjectMetadataItems, + ); + jotaiStore.set(shouldAppBeLoadingState.atom, false); const pageLayoutData = createPageLayoutWithWidget(widget); snapshot.set( pageLayoutPersistedComponentState.atomFamily({ @@ -1017,8 +1042,11 @@ export const WithManyToOneRelationFieldWidget: Story = { }; const initializeState = (snapshot: MutableSnapshot) => { - snapshot.set(objectMetadataItemsState, generatedMockObjectMetadataItems); - snapshot.set(shouldAppBeLoadingState, false); + jotaiStore.set( + objectMetadataItemsState.atom, + generatedMockObjectMetadataItems, + ); + jotaiStore.set(shouldAppBeLoadingState.atom, false); const pageLayoutData = createPageLayoutWithWidget( widget, PageLayoutType.RECORD_PAGE, @@ -1131,8 +1159,11 @@ export const WithOneToManyRelationFieldWidget: Story = { }; const initializeState = (snapshot: MutableSnapshot) => { - snapshot.set(objectMetadataItemsState, generatedMockObjectMetadataItems); - snapshot.set(shouldAppBeLoadingState, false); + jotaiStore.set( + objectMetadataItemsState.atom, + generatedMockObjectMetadataItems, + ); + jotaiStore.set(shouldAppBeLoadingState.atom, false); const pageLayoutData = createPageLayoutWithWidget( widget, PageLayoutType.RECORD_PAGE, @@ -1237,8 +1268,11 @@ export const OneToManyRelationFieldWidgetWithSeeAllButton: Story = { }; const initializeState = (snapshot: MutableSnapshot) => { - snapshot.set(objectMetadataItemsState, generatedMockObjectMetadataItems); - snapshot.set(shouldAppBeLoadingState, false); + jotaiStore.set( + objectMetadataItemsState.atom, + generatedMockObjectMetadataItems, + ); + jotaiStore.set(shouldAppBeLoadingState.atom, false); const pageLayoutData = createPageLayoutWithWidget( widget, PageLayoutType.RECORD_PAGE, @@ -1370,8 +1404,11 @@ export const OnMobile: Story = { }; const initializeState = (snapshot: MutableSnapshot) => { - snapshot.set(objectMetadataItemsState, generatedMockObjectMetadataItems); - snapshot.set(shouldAppBeLoadingState, false); + jotaiStore.set( + objectMetadataItemsState.atom, + generatedMockObjectMetadataItems, + ); + jotaiStore.set(shouldAppBeLoadingState.atom, false); const pageLayoutData = createPageLayoutWithWidget( widget, PageLayoutType.RECORD_PAGE, @@ -1466,8 +1503,11 @@ export const InSidePanel: Story = { }; const initializeState = (snapshot: MutableSnapshot) => { - snapshot.set(objectMetadataItemsState, generatedMockObjectMetadataItems); - snapshot.set(shouldAppBeLoadingState, false); + jotaiStore.set( + objectMetadataItemsState.atom, + generatedMockObjectMetadataItems, + ); + jotaiStore.set(shouldAppBeLoadingState.atom, false); const pageLayoutData = createPageLayoutWithWidget( widget, PageLayoutType.RECORD_PAGE, @@ -1623,8 +1663,11 @@ export const Catalog: CatalogStory = { }; const initializeState = (snapshot: MutableSnapshot) => { - snapshot.set(objectMetadataItemsState, generatedMockObjectMetadataItems); - snapshot.set(shouldAppBeLoadingState, false); + jotaiStore.set( + objectMetadataItemsState.atom, + generatedMockObjectMetadataItems, + ); + jotaiStore.set(shouldAppBeLoadingState.atom, false); if (state === 'hover') { snapshot.set( @@ -1654,8 +1697,9 @@ export const Catalog: CatalogStory = { ); } + // currentUserWorkspaceState is now a Jotai state, set it directly if (isRestricted === true) { - snapshot.set(currentUserWorkspaceState, { + jotaiStore.set(currentUserWorkspaceState.atom, { permissionFlags: [], twoFactorAuthenticationMethodSummary: null, objectsPermissions: [ @@ -1672,7 +1716,7 @@ export const Catalog: CatalogStory = { ], }); } else { - snapshot.set(currentUserWorkspaceState, { + jotaiStore.set(currentUserWorkspaceState.atom, { permissionFlags: [], twoFactorAuthenticationMethodSummary: null, objectsPermissions: [ 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 46eefe9de8..084ac6f250 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 @@ -365,8 +365,11 @@ export const TextFieldWidget: Story = { }; const initializeState = (snapshot: MutableSnapshot) => { - snapshot.set(objectMetadataItemsState, generatedMockObjectMetadataItems); - snapshot.set(shouldAppBeLoadingState, false); + jotaiStore.set( + objectMetadataItemsState.atom, + generatedMockObjectMetadataItems, + ); + jotaiStore.set(shouldAppBeLoadingState.atom, false); const pageLayoutData = createPageLayoutWithWidget( widget, companyObjectMetadataItem.id, @@ -460,8 +463,11 @@ export const AddressFieldWidget: Story = { }; const initializeState = (snapshot: MutableSnapshot) => { - snapshot.set(objectMetadataItemsState, generatedMockObjectMetadataItems); - snapshot.set(shouldAppBeLoadingState, false); + jotaiStore.set( + objectMetadataItemsState.atom, + generatedMockObjectMetadataItems, + ); + jotaiStore.set(shouldAppBeLoadingState.atom, false); const pageLayoutData = createPageLayoutWithWidget( widget, companyObjectMetadataItem.id, @@ -558,8 +564,11 @@ export const NumberFieldWidget: Story = { }; const initializeState = (snapshot: MutableSnapshot) => { - snapshot.set(objectMetadataItemsState, generatedMockObjectMetadataItems); - snapshot.set(shouldAppBeLoadingState, false); + jotaiStore.set( + objectMetadataItemsState.atom, + generatedMockObjectMetadataItems, + ); + jotaiStore.set(shouldAppBeLoadingState.atom, false); const pageLayoutData = createPageLayoutWithWidget( widget, companyObjectMetadataItem.id, @@ -653,8 +662,11 @@ export const LinkFieldWidget: Story = { }; const initializeState = (snapshot: MutableSnapshot) => { - snapshot.set(objectMetadataItemsState, generatedMockObjectMetadataItems); - snapshot.set(shouldAppBeLoadingState, false); + jotaiStore.set( + objectMetadataItemsState.atom, + generatedMockObjectMetadataItems, + ); + jotaiStore.set(shouldAppBeLoadingState.atom, false); const pageLayoutData = createPageLayoutWithWidget( widget, companyObjectMetadataItem.id, @@ -748,8 +760,11 @@ export const ManyToOneRelationFieldWidget: Story = { }; const initializeState = (snapshot: MutableSnapshot) => { - snapshot.set(objectMetadataItemsState, generatedMockObjectMetadataItems); - snapshot.set(shouldAppBeLoadingState, false); + jotaiStore.set( + objectMetadataItemsState.atom, + generatedMockObjectMetadataItems, + ); + jotaiStore.set(shouldAppBeLoadingState.atom, false); const pageLayoutData = createPageLayoutWithWidget( widget, companyObjectMetadataItem.id, @@ -854,8 +869,11 @@ export const OneToManyRelationFieldWidget: Story = { }; const initializeState = (snapshot: MutableSnapshot) => { - snapshot.set(objectMetadataItemsState, generatedMockObjectMetadataItems); - snapshot.set(shouldAppBeLoadingState, false); + jotaiStore.set( + objectMetadataItemsState.atom, + generatedMockObjectMetadataItems, + ); + jotaiStore.set(shouldAppBeLoadingState.atom, false); const pageLayoutData = createPageLayoutWithWidget( widget, companyObjectMetadataItem.id, @@ -951,8 +969,11 @@ export const BooleanFieldWidget: Story = { }; const initializeState = (snapshot: MutableSnapshot) => { - snapshot.set(objectMetadataItemsState, generatedMockObjectMetadataItems); - snapshot.set(shouldAppBeLoadingState, false); + jotaiStore.set( + objectMetadataItemsState.atom, + generatedMockObjectMetadataItems, + ); + jotaiStore.set(shouldAppBeLoadingState.atom, false); const pageLayoutData = createPageLayoutWithWidget( widget, companyObjectMetadataItem.id, @@ -1045,8 +1066,11 @@ export const CurrencyFieldWidget: Story = { }; const initializeState = (snapshot: MutableSnapshot) => { - snapshot.set(objectMetadataItemsState, generatedMockObjectMetadataItems); - snapshot.set(shouldAppBeLoadingState, false); + jotaiStore.set( + objectMetadataItemsState.atom, + generatedMockObjectMetadataItems, + ); + jotaiStore.set(shouldAppBeLoadingState.atom, false); const pageLayoutData = createPageLayoutWithWidget( widget, companyObjectMetadataItem.id, @@ -1139,8 +1163,11 @@ export const EmailsFieldWidget: Story = { }; const initializeState = (snapshot: MutableSnapshot) => { - snapshot.set(objectMetadataItemsState, generatedMockObjectMetadataItems); - snapshot.set(shouldAppBeLoadingState, false); + jotaiStore.set( + objectMetadataItemsState.atom, + generatedMockObjectMetadataItems, + ); + jotaiStore.set(shouldAppBeLoadingState.atom, false); const pageLayoutData = createPageLayoutWithWidget( widget, personObjectMetadataItem.id, @@ -1234,8 +1261,11 @@ export const PhonesFieldWidget: Story = { }; const initializeState = (snapshot: MutableSnapshot) => { - snapshot.set(objectMetadataItemsState, generatedMockObjectMetadataItems); - snapshot.set(shouldAppBeLoadingState, false); + jotaiStore.set( + objectMetadataItemsState.atom, + generatedMockObjectMetadataItems, + ); + jotaiStore.set(shouldAppBeLoadingState.atom, false); const pageLayoutData = createPageLayoutWithWidget( widget, personObjectMetadataItem.id, @@ -1329,8 +1359,11 @@ export const SelectFieldWidget: Story = { }; const initializeState = (snapshot: MutableSnapshot) => { - snapshot.set(objectMetadataItemsState, generatedMockObjectMetadataItems); - snapshot.set(shouldAppBeLoadingState, false); + jotaiStore.set( + objectMetadataItemsState.atom, + generatedMockObjectMetadataItems, + ); + jotaiStore.set(shouldAppBeLoadingState.atom, false); const pageLayoutData = createPageLayoutWithWidget( widget, opportunityObjectMetadataItem.id, @@ -1429,8 +1462,11 @@ export const MultiSelectFieldWidget: Story = { }; const initializeState = (snapshot: MutableSnapshot) => { - snapshot.set(objectMetadataItemsState, generatedMockObjectMetadataItems); - snapshot.set(shouldAppBeLoadingState, false); + jotaiStore.set( + objectMetadataItemsState.atom, + generatedMockObjectMetadataItems, + ); + jotaiStore.set(shouldAppBeLoadingState.atom, false); const pageLayoutData = createPageLayoutWithWidget( widget, companyObjectMetadataItem.id, @@ -1528,8 +1564,11 @@ export const TimelineActivityRelationFieldWidget: Story = { }; const initializeState = (snapshot: MutableSnapshot) => { - snapshot.set(objectMetadataItemsState, generatedMockObjectMetadataItems); - snapshot.set(shouldAppBeLoadingState, false); + jotaiStore.set( + objectMetadataItemsState.atom, + generatedMockObjectMetadataItems, + ); + jotaiStore.set(shouldAppBeLoadingState.atom, false); const pageLayoutData = createPageLayoutWithWidget( widget, timelineActivityObjectMetadataItem.id, @@ -1633,8 +1672,11 @@ export const ManyToOneRelationCardWidget: Story = { }; const initializeState = (snapshot: MutableSnapshot) => { - snapshot.set(objectMetadataItemsState, generatedMockObjectMetadataItems); - snapshot.set(shouldAppBeLoadingState, false); + jotaiStore.set( + objectMetadataItemsState.atom, + generatedMockObjectMetadataItems, + ); + jotaiStore.set(shouldAppBeLoadingState.atom, false); const pageLayoutData = createPageLayoutWithWidget( widget, companyObjectMetadataItem.id, @@ -1747,8 +1789,11 @@ export const OneToManyRelationCardWidget: Story = { }; const initializeState = (snapshot: MutableSnapshot) => { - snapshot.set(objectMetadataItemsState, generatedMockObjectMetadataItems); - snapshot.set(shouldAppBeLoadingState, false); + jotaiStore.set( + objectMetadataItemsState.atom, + generatedMockObjectMetadataItems, + ); + jotaiStore.set(shouldAppBeLoadingState.atom, false); const pageLayoutData = createPageLayoutWithWidget( widget, companyObjectMetadataItem.id, @@ -1843,8 +1888,11 @@ export const TimelineActivityRelationCardWidget: Story = { }; const initializeState = (snapshot: MutableSnapshot) => { - snapshot.set(objectMetadataItemsState, generatedMockObjectMetadataItems); - snapshot.set(shouldAppBeLoadingState, false); + jotaiStore.set( + objectMetadataItemsState.atom, + generatedMockObjectMetadataItems, + ); + jotaiStore.set(shouldAppBeLoadingState.atom, false); const pageLayoutData = createPageLayoutWithWidget( widget, timelineActivityObjectMetadataItem.id, @@ -2010,8 +2058,11 @@ export const OneToManyRelationCardWidgetWithProgressiveLoading: Story = { }; const initializeState = (snapshot: MutableSnapshot) => { - snapshot.set(objectMetadataItemsState, generatedMockObjectMetadataItems); - snapshot.set(shouldAppBeLoadingState, false); + jotaiStore.set( + objectMetadataItemsState.atom, + generatedMockObjectMetadataItems, + ); + jotaiStore.set(shouldAppBeLoadingState.atom, false); const pageLayoutData = createPageLayoutWithWidget( widget, companyObjectMetadataItem.id, diff --git a/packages/twenty-front/src/modules/page-layout/widgets/field/hooks/useOpenFieldWidgetFieldInputEditMode.ts b/packages/twenty-front/src/modules/page-layout/widgets/field/hooks/useOpenFieldWidgetFieldInputEditMode.ts index 9599ee597b..7d0a83a531 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/field/hooks/useOpenFieldWidgetFieldInputEditMode.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/field/hooks/useOpenFieldWidgetFieldInputEditMode.ts @@ -23,6 +23,7 @@ import { isFieldRelationManyToOne } from '@/object-record/record-field/ui/types/ import { isFieldRelationOneToMany } from '@/object-record/record-field/ui/types/guards/isFieldRelationOneToMany'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack'; import { useRemoveFocusItemFromFocusStackById } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackById'; import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType'; @@ -78,9 +79,9 @@ export const useOpenFieldWidgetFieldInputEditMode = () => { .getLoadable(recordStoreFamilyState(recordId)) .getValue(); - const objectMetadataItems = snapshot - .getLoadable(objectMetadataItemsState) - .getValue(); + const objectMetadataItems = jotaiStore.get( + objectMetadataItemsState.atom, + ); const activityTargetObjectRecords = getActivityTargetObjectRecords({ activityRecord: activity as Task | Note, diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/components/GraphWidgetBarChartRenderer.tsx b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/components/GraphWidgetBarChartRenderer.tsx index c0327589a4..44f14e94b8 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/components/GraphWidgetBarChartRenderer.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/components/GraphWidgetBarChartRenderer.tsx @@ -11,10 +11,10 @@ import { useCurrentWidget } from '@/page-layout/widgets/hooks/useCurrentWidget'; import { useUserFirstDayOfTheWeek } from '@/ui/input/components/internal/date/hooks/useUserFirstDayOfTheWeek'; import { useUserTimezone } from '@/ui/input/components/internal/date/hooks/useUserTimezone'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; +import { useFamilySelectorValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilySelectorValueV2'; import { coreIndexViewIdFromObjectMetadataItemFamilySelector } from '@/views/states/selectors/coreIndexViewIdFromObjectMetadataItemFamilySelector'; import { lazy, Suspense } from 'react'; import { useNavigate } from 'react-router-dom'; -import { useRecoilValue } from 'recoil'; import { AppPath } from 'twenty-shared/types'; import { getAppPath } from 'twenty-shared/utils'; import { AxisNameDisplay } from '~/generated-metadata/graphql'; @@ -81,14 +81,14 @@ export const GraphWidgetBarChartRenderer = () => { configuration.omitNullValues, ); - const indexViewId = useRecoilValue( - coreIndexViewIdFromObjectMetadataItemFamilySelector({ - objectMetadataItemId: objectMetadataItem.id, - }), + const indexViewId = useFamilySelectorValueV2( + coreIndexViewIdFromObjectMetadataItemFamilySelector, + { objectMetadataItemId: objectMetadataItem.id }, ); const primaryGroupByField = objectMetadataItem.fields.find( - (field) => field.id === configuration.primaryAxisGroupByFieldMetadataId, + (field: { id: string }) => + field.id === configuration.primaryAxisGroupByFieldMetadataId, ); const canRedirectToFilteredView = isFilteredViewRedirectionSupported(primaryGroupByField); diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/hooks/useGraphBarChartWidgetData.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/hooks/useGraphBarChartWidgetData.ts index cc1af49464..09861cf2a2 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/hooks/useGraphBarChartWidgetData.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/hooks/useGraphBarChartWidgetData.ts @@ -96,7 +96,7 @@ export const useGraphBarChartWidgetData = ({ : configuration.primaryAxisGroupByFieldMetadataId; const colorDeterminingField = objectMetadataItem?.fields?.find( - (field) => field.id === colorDeterminingFieldId, + (field: { id: string }) => field.id === colorDeterminingFieldId, ); const selectFieldOptions = useMemo((): FieldMetadataItemOption[] | null => { diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/components/GraphWidgetLineChartRenderer.tsx b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/components/GraphWidgetLineChartRenderer.tsx index 391b6f85ea..f3784a923c 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/components/GraphWidgetLineChartRenderer.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/components/GraphWidgetLineChartRenderer.tsx @@ -11,11 +11,11 @@ import { useCurrentWidget } from '@/page-layout/widgets/hooks/useCurrentWidget'; import { useUserFirstDayOfTheWeek } from '@/ui/input/components/internal/date/hooks/useUserFirstDayOfTheWeek'; import { useUserTimezone } from '@/ui/input/components/internal/date/hooks/useUserTimezone'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; +import { useFamilySelectorValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilySelectorValueV2'; import { coreIndexViewIdFromObjectMetadataItemFamilySelector } from '@/views/states/selectors/coreIndexViewIdFromObjectMetadataItemFamilySelector'; import { type LineSeries, type Point } from '@nivo/line'; import { lazy, Suspense } from 'react'; import { useNavigate } from 'react-router-dom'; -import { useRecoilValue } from 'recoil'; import { AppPath } from 'twenty-shared/types'; import { getAppPath, isDefined } from 'twenty-shared/utils'; import { @@ -89,10 +89,9 @@ export const GraphWidgetLineChartRenderer = () => { configuration.omitNullValues, ); - const indexViewId = useRecoilValue( - coreIndexViewIdFromObjectMetadataItemFamilySelector({ - objectMetadataItemId: objectMetadataItem.id, - }), + const indexViewId = useFamilySelectorValueV2( + coreIndexViewIdFromObjectMetadataItemFamilySelector, + { objectMetadataItemId: objectMetadataItem.id }, ); const { userFirstDayOfTheWeek } = useUserFirstDayOfTheWeek(); diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/hooks/useGraphLineChartWidgetData.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/hooks/useGraphLineChartWidgetData.ts index bcd3306349..04dc287ff5 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/hooks/useGraphLineChartWidgetData.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/hooks/useGraphLineChartWidgetData.ts @@ -65,7 +65,8 @@ export const useGraphLineChartWidgetData = ({ : new Map(); const secondaryAxisField = objectMetadataItem?.fields?.find( - (field) => field.id === configuration.secondaryAxisGroupByFieldMetadataId, + (field: { id: string }) => + field.id === configuration.secondaryAxisGroupByFieldMetadataId, ); const selectFieldOptions = diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/components/GraphWidgetPieChartRenderer.tsx b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/components/GraphWidgetPieChartRenderer.tsx index 91baf61616..2259d9418f 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/components/GraphWidgetPieChartRenderer.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/components/GraphWidgetPieChartRenderer.tsx @@ -10,10 +10,10 @@ import { useCurrentWidget } from '@/page-layout/widgets/hooks/useCurrentWidget'; import { useUserFirstDayOfTheWeek } from '@/ui/input/components/internal/date/hooks/useUserFirstDayOfTheWeek'; import { useUserTimezone } from '@/ui/input/components/internal/date/hooks/useUserTimezone'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; +import { useFamilySelectorValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilySelectorValueV2'; import { coreIndexViewIdFromObjectMetadataItemFamilySelector } from '@/views/states/selectors/coreIndexViewIdFromObjectMetadataItemFamilySelector'; import { lazy, Suspense } from 'react'; import { useNavigate } from 'react-router-dom'; -import { useRecoilValue } from 'recoil'; import { AppPath } from 'twenty-shared/types'; import { getAppPath } from 'twenty-shared/utils'; @@ -52,10 +52,9 @@ export const GraphWidgetPieChartRenderer = () => { const isPageLayoutInEditMode = useRecoilComponentValue( isPageLayoutInEditModeComponentState, ); - const indexViewId = useRecoilValue( - coreIndexViewIdFromObjectMetadataItemFamilySelector({ - objectMetadataItemId: objectMetadataItem.id, - }), + const indexViewId = useFamilySelectorValueV2( + coreIndexViewIdFromObjectMetadataItemFamilySelector, + { objectMetadataItemId: objectMetadataItem.id }, ); const { userFirstDayOfTheWeek } = useUserFirstDayOfTheWeek(); diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/hooks/useGraphPieChartWidgetData.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/hooks/useGraphPieChartWidgetData.ts index c6ddf215dd..6c676fa0a5 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/hooks/useGraphPieChartWidgetData.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/hooks/useGraphPieChartWidgetData.ts @@ -64,7 +64,8 @@ export const useGraphPieChartWidgetData = ({ : new Map(); const groupByField = objectMetadataItem?.fields?.find( - (field) => field.id === configuration.groupByFieldMetadataId, + (field: { id: string }) => + field.id === configuration.groupByFieldMetadataId, ); const selectFieldOptions = diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/hooks/usePieChartCenterMetricData.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/hooks/usePieChartCenterMetricData.ts index d7be6e5d69..bcac1f1de3 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/hooks/usePieChartCenterMetricData.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/hooks/usePieChartCenterMetricData.ts @@ -40,7 +40,8 @@ export const usePieChartCenterMetricData = ({ }); const groupByField = objectMetadataItem.fields.find( - (field) => field.id === configuration.groupByFieldMetadataId, + (field: { id: string }) => + field.id === configuration.groupByFieldMetadataId, ); const centerMetricFilter = useMemo(() => { diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/hooks/useGraphWidgetAggregateQuery.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/hooks/useGraphWidgetAggregateQuery.ts index aebfcc2c81..8655154212 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/hooks/useGraphWidgetAggregateQuery.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/hooks/useGraphWidgetAggregateQuery.ts @@ -36,7 +36,7 @@ export const useGraphWidgetAggregateQuery = ({ const ratioField = isRatioQuery ? objectMetadataItem.fields.find( - (field) => field.id === ratioConfig.fieldMetadataId, + (field: { id: string }) => field.id === ratioConfig.fieldMetadataId, ) : undefined; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/hooks/useGraphWidgetQueryCommon.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/hooks/useGraphWidgetQueryCommon.ts index 9c3738460f..5713f05773 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/hooks/useGraphWidgetQueryCommon.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/hooks/useGraphWidgetQueryCommon.ts @@ -29,7 +29,7 @@ export const useGraphWidgetQueryCommon = ({ const aggregateFieldId = configuration.aggregateFieldMetadataId; const aggregateField = objectMetadataItem.readableFields.find( - (field) => field.id === aggregateFieldId, + (field: { id: string }) => field.id === aggregateFieldId, ); if (!isDefined(aggregateField)) { diff --git a/packages/twenty-front/src/modules/prefetch/components/PrefetchRunFavoriteQueriesEffect.tsx b/packages/twenty-front/src/modules/prefetch/components/PrefetchRunFavoriteQueriesEffect.tsx index 3c9ad90f7f..11281bb70d 100644 --- a/packages/twenty-front/src/modules/prefetch/components/PrefetchRunFavoriteQueriesEffect.tsx +++ b/packages/twenty-front/src/modules/prefetch/components/PrefetchRunFavoriteQueriesEffect.tsx @@ -1,9 +1,4 @@ -import { useEffect } from 'react'; -import { useRecoilCallback, useRecoilValue, useSetRecoilState } from 'recoil'; - import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; -import { type Favorite } from '@/favorites/types/Favorite'; -import { type FavoriteFolder } from '@/favorites/types/FavoriteFolder'; import { useIsSettingsPage } from '@/navigation/hooks/useIsSettingsPage'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; @@ -16,9 +11,16 @@ import { prefetchFavoritesState } from '@/prefetch/states/prefetchFavoritesState import { prefetchIsLoadedFamilyState } from '@/prefetch/states/prefetchIsLoadedFamilyState'; import { PrefetchKey } from '@/prefetch/types/PrefetchKey'; import { useShowAuthModal } from '@/ui/layout/hooks/useShowAuthModal'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; +import { useSetFamilyRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetFamilyRecoilStateV2'; +import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; +import { useCallback, useEffect } from 'react'; import { isDefined } from 'twenty-shared/utils'; import { WorkspaceActivationStatus } from 'twenty-shared/workspace'; +import { type Favorite } from '@/favorites/types/Favorite'; +import { type FavoriteFolder } from '@/favorites/types/FavoriteFolder'; import { FeatureFlagKey } from '~/generated-metadata/graphql'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; @@ -28,18 +30,25 @@ export const PrefetchRunFavoriteQueriesEffect = () => { ); const showAuthModal = useShowAuthModal(); const isSettingsPage = useIsSettingsPage(); - const currentWorkspace = useRecoilValue(currentWorkspaceState); + const currentWorkspace = useRecoilValueV2(currentWorkspaceState); const isWorkspaceActive = currentWorkspace?.activationStatus === WorkspaceActivationStatus.ACTIVE; const { objectMetadataItems } = useObjectMetadataItems(); - const setIsPrefetchFavoritesLoaded = useSetRecoilState( - prefetchIsLoadedFamilyState(PrefetchKey.AllFavorites), + const setIsPrefetchFavoritesLoaded = useSetFamilyRecoilStateV2( + prefetchIsLoadedFamilyState, + PrefetchKey.AllFavorites, ); - const setIsPrefetchFavoritesFoldersLoaded = useSetRecoilState( - prefetchIsLoadedFamilyState(PrefetchKey.AllFavoritesFolders), + const setIsPrefetchFavoritesFoldersLoaded = useSetFamilyRecoilStateV2( + prefetchIsLoadedFamilyState, + PrefetchKey.AllFavoritesFolders, + ); + + const setFavoritesState = useSetRecoilStateV2(prefetchFavoritesState); + const setFavoriteFoldersState = useSetRecoilStateV2( + prefetchFavoriteFoldersState, ); const { objectMetadataItem: favoriteObjectMetadataItem } = @@ -78,49 +87,49 @@ export const PrefetchRunFavoriteQueriesEffect = () => { isNavigationMenuItemEditingEnabled, }); - const setPrefetchFavoritesState = useRecoilCallback( - ({ set, snapshot }) => - (favorites: Favorite[]) => { - const existingFavorites = snapshot - .getLoadable(prefetchFavoritesState) - .getValue(); - - if (!isDeeplyEqual(existingFavorites, favorites)) { - set(prefetchFavoritesState, favorites); - } - }, - [], + const setPrefetchFavoritesStateIfChanged = useCallback( + (newFavorites: Favorite[]) => { + const existingFavorites = jotaiStore.get(prefetchFavoritesState.atom); + if (!isDeeplyEqual(existingFavorites, newFavorites)) { + setFavoritesState(newFavorites); + } + }, + [setFavoritesState], ); - const setPrefetchFavoriteFoldersState = useRecoilCallback( - ({ set, snapshot }) => - (favoriteFolders: FavoriteFolder[]) => { - const existingFavoriteFolders = snapshot - .getLoadable(prefetchFavoriteFoldersState) - .getValue(); - - if (!isDeeplyEqual(existingFavoriteFolders, favoriteFolders)) { - set(prefetchFavoriteFoldersState, favoriteFolders); - } - }, - [], + const setPrefetchFavoriteFoldersStateIfChanged = useCallback( + (newFavoriteFolders: FavoriteFolder[]) => { + const existingFavoriteFolders = jotaiStore.get( + prefetchFavoriteFoldersState.atom, + ); + if (!isDeeplyEqual(existingFavoriteFolders, newFavoriteFolders)) { + setFavoriteFoldersState(newFavoriteFolders); + } + }, + [setFavoriteFoldersState], ); useEffect(() => { if (isDefined(favorites)) { - setPrefetchFavoritesState(favorites as Favorite[]); + setPrefetchFavoritesStateIfChanged(favorites as Favorite[]); setIsPrefetchFavoritesLoaded(true); } - }, [favorites, setPrefetchFavoritesState, setIsPrefetchFavoritesLoaded]); + }, [ + favorites, + setPrefetchFavoritesStateIfChanged, + setIsPrefetchFavoritesLoaded, + ]); useEffect(() => { if (isDefined(favoriteFolders)) { - setPrefetchFavoriteFoldersState(favoriteFolders as FavoriteFolder[]); + setPrefetchFavoriteFoldersStateIfChanged( + favoriteFolders as FavoriteFolder[], + ); setIsPrefetchFavoritesFoldersLoaded(true); } }, [ favoriteFolders, - setPrefetchFavoriteFoldersState, + setPrefetchFavoriteFoldersStateIfChanged, setIsPrefetchFavoritesFoldersLoaded, ]); diff --git a/packages/twenty-front/src/modules/prefetch/components/PrefetchRunNavigationMenuItemQueriesEffect.tsx b/packages/twenty-front/src/modules/prefetch/components/PrefetchRunNavigationMenuItemQueriesEffect.tsx index 9eeaec0484..7889bd7d2e 100644 --- a/packages/twenty-front/src/modules/prefetch/components/PrefetchRunNavigationMenuItemQueriesEffect.tsx +++ b/packages/twenty-front/src/modules/prefetch/components/PrefetchRunNavigationMenuItemQueriesEffect.tsx @@ -1,13 +1,15 @@ -import { useEffect } from 'react'; -import { useRecoilCallback, useRecoilValue, useSetRecoilState } from 'recoil'; - import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { useIsSettingsPage } from '@/navigation/hooks/useIsSettingsPage'; import { prefetchIsLoadedFamilyState } from '@/prefetch/states/prefetchIsLoadedFamilyState'; import { prefetchNavigationMenuItemsState } from '@/prefetch/states/prefetchNavigationMenuItemsState'; import { PrefetchKey } from '@/prefetch/types/PrefetchKey'; import { useShowAuthModal } from '@/ui/layout/hooks/useShowAuthModal'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; +import { useSetFamilyRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetFamilyRecoilStateV2'; +import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; +import { useCallback, useEffect } from 'react'; import { isDefined } from 'twenty-shared/utils'; import { WorkspaceActivationStatus } from 'twenty-shared/workspace'; import { @@ -24,12 +26,17 @@ export const PrefetchRunNavigationMenuItemQueriesEffect = () => { const showAuthModal = useShowAuthModal(); const isSettingsPage = useIsSettingsPage(); - const currentWorkspace = useRecoilValue(currentWorkspaceState); + const currentWorkspace = useRecoilValueV2(currentWorkspaceState); const isWorkspaceActive = currentWorkspace?.activationStatus === WorkspaceActivationStatus.ACTIVE; - const setIsPrefetchNavigationMenuItemsLoaded = useSetRecoilState( - prefetchIsLoadedFamilyState(PrefetchKey.AllNavigationMenuItems), + const setIsPrefetchNavigationMenuItemsLoaded = useSetFamilyRecoilStateV2( + prefetchIsLoadedFamilyState, + PrefetchKey.AllNavigationMenuItems, + ); + + const setNavigationMenuItemsState = useSetRecoilStateV2( + prefetchNavigationMenuItemsState, ); const { data, loading } = useFindManyNavigationMenuItemsQuery({ @@ -41,29 +48,27 @@ export const PrefetchRunNavigationMenuItemQueriesEffect = () => { fetchPolicy: 'cache-and-network', }); - const setPrefetchNavigationMenuItemsState = useRecoilCallback( - ({ set, snapshot }) => - (navigationMenuItems: NavigationMenuItem[]) => { - const existingNavigationMenuItems = snapshot - .getLoadable(prefetchNavigationMenuItemsState) - .getValue(); - - if (!isDeeplyEqual(existingNavigationMenuItems, navigationMenuItems)) { - set(prefetchNavigationMenuItemsState, navigationMenuItems); - } - }, - [], + const setPrefetchNavigationMenuItemsStateIfChanged = useCallback( + (navigationMenuItems: NavigationMenuItem[]) => { + const existingNavigationMenuItems = jotaiStore.get( + prefetchNavigationMenuItemsState.atom, + ); + if (!isDeeplyEqual(existingNavigationMenuItems, navigationMenuItems)) { + setNavigationMenuItemsState(navigationMenuItems); + } + }, + [setNavigationMenuItemsState], ); useEffect(() => { if (!loading && isDefined(data?.navigationMenuItems)) { - setPrefetchNavigationMenuItemsState(data.navigationMenuItems); + setPrefetchNavigationMenuItemsStateIfChanged(data.navigationMenuItems); setIsPrefetchNavigationMenuItemsLoaded(true); } }, [ data, loading, - setPrefetchNavigationMenuItemsState, + setPrefetchNavigationMenuItemsStateIfChanged, setIsPrefetchNavigationMenuItemsLoaded, ]); diff --git a/packages/twenty-front/src/modules/prefetch/hooks/useIsPrefetchLoading.ts b/packages/twenty-front/src/modules/prefetch/hooks/useIsPrefetchLoading.ts index 4a7dbc3ca6..2769011b34 100644 --- a/packages/twenty-front/src/modules/prefetch/hooks/useIsPrefetchLoading.ts +++ b/packages/twenty-front/src/modules/prefetch/hooks/useIsPrefetchLoading.ts @@ -1,19 +1,21 @@ import { prefetchIsLoadedFamilyState } from '@/prefetch/states/prefetchIsLoadedFamilyState'; import { PrefetchKey } from '@/prefetch/types/PrefetchKey'; +import { useFamilyRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilyRecoilValueV2'; import { useIsWorkspaceActivationStatusEqualsTo } from '@/workspace/hooks/useIsWorkspaceActivationStatusEqualsTo'; -import { useRecoilValue } from 'recoil'; import { WorkspaceActivationStatus } from 'twenty-shared/workspace'; export const useIsPrefetchLoading = () => { const isWorkspaceActive = useIsWorkspaceActivationStatusEqualsTo( WorkspaceActivationStatus.ACTIVE, ); - const isFavoriteFoldersPrefetched = useRecoilValue( - prefetchIsLoadedFamilyState(PrefetchKey.AllFavoritesFolders), + const isFavoriteFoldersPrefetched = useFamilyRecoilValueV2( + prefetchIsLoadedFamilyState, + PrefetchKey.AllFavoritesFolders, ); - const areFavoritesPrefetched = useRecoilValue( - prefetchIsLoadedFamilyState(PrefetchKey.AllFavorites), + const areFavoritesPrefetched = useFamilyRecoilValueV2( + prefetchIsLoadedFamilyState, + PrefetchKey.AllFavorites, ); return ( diff --git a/packages/twenty-front/src/modules/prefetch/states/prefetchFavoriteFoldersState.ts b/packages/twenty-front/src/modules/prefetch/states/prefetchFavoriteFoldersState.ts index d461a960a4..3eb173ddbf 100644 --- a/packages/twenty-front/src/modules/prefetch/states/prefetchFavoriteFoldersState.ts +++ b/packages/twenty-front/src/modules/prefetch/states/prefetchFavoriteFoldersState.ts @@ -1,7 +1,7 @@ import { type FavoriteFolder } from '@/favorites/types/FavoriteFolder'; -import { createState } from '@/ui/utilities/state/utils/createState'; +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; -export const prefetchFavoriteFoldersState = createState({ +export const prefetchFavoriteFoldersState = createStateV2({ key: 'prefetchFavoriteFoldersState', defaultValue: [], }); diff --git a/packages/twenty-front/src/modules/prefetch/states/prefetchFavoritesState.ts b/packages/twenty-front/src/modules/prefetch/states/prefetchFavoritesState.ts index 3fac7df68d..28355b4573 100644 --- a/packages/twenty-front/src/modules/prefetch/states/prefetchFavoritesState.ts +++ b/packages/twenty-front/src/modules/prefetch/states/prefetchFavoritesState.ts @@ -1,7 +1,7 @@ import { type Favorite } from '@/favorites/types/Favorite'; -import { createState } from '@/ui/utilities/state/utils/createState'; +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; -export const prefetchFavoritesState = createState({ +export const prefetchFavoritesState = createStateV2({ key: 'prefetchFavoritesState', defaultValue: [], }); diff --git a/packages/twenty-front/src/modules/prefetch/states/prefetchIsLoadedFamilyState.ts b/packages/twenty-front/src/modules/prefetch/states/prefetchIsLoadedFamilyState.ts index fafe9edb41..3189f4c29d 100644 --- a/packages/twenty-front/src/modules/prefetch/states/prefetchIsLoadedFamilyState.ts +++ b/packages/twenty-front/src/modules/prefetch/states/prefetchIsLoadedFamilyState.ts @@ -1,7 +1,7 @@ import { type PrefetchKey } from '@/prefetch/types/PrefetchKey'; -import { createFamilyState } from '@/ui/utilities/state/utils/createFamilyState'; +import { createFamilyStateV2 } from '@/ui/utilities/state/jotai/utils/createFamilyStateV2'; -export const prefetchIsLoadedFamilyState = createFamilyState< +export const prefetchIsLoadedFamilyState = createFamilyStateV2< boolean, PrefetchKey >({ diff --git a/packages/twenty-front/src/modules/prefetch/states/prefetchNavigationMenuItemsState.ts b/packages/twenty-front/src/modules/prefetch/states/prefetchNavigationMenuItemsState.ts index 155295c0c5..5855e3cf72 100644 --- a/packages/twenty-front/src/modules/prefetch/states/prefetchNavigationMenuItemsState.ts +++ b/packages/twenty-front/src/modules/prefetch/states/prefetchNavigationMenuItemsState.ts @@ -1,7 +1,7 @@ -import { createState } from '@/ui/utilities/state/utils/createState'; +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; import { type NavigationMenuItem } from '~/generated-metadata/graphql'; -export const prefetchNavigationMenuItemsState = createState< +export const prefetchNavigationMenuItemsState = createStateV2< NavigationMenuItem[] >({ key: 'prefetchNavigationMenuItemsState', diff --git a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsBlocklistSection.tsx b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsBlocklistSection.tsx index 721f6e68dc..dc69b8c3f5 100644 --- a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsBlocklistSection.tsx +++ b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsBlocklistSection.tsx @@ -1,7 +1,6 @@ -import { useRecoilValue } from 'recoil'; - import { type BlocklistItem } from '@/accounts/types/BlocklistItem'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord'; import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord'; @@ -16,7 +15,7 @@ import { Section } from 'twenty-ui/layout'; export const SettingsAccountsBlocklistSection = () => { const { t } = useLingui(); - const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); + const currentWorkspaceMember = useRecoilValueV2(currentWorkspaceMemberState); const currentWorkspaceMemberId = currentWorkspaceMember?.id ?? ''; diff --git a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsCalendarChannelsContainer.tsx b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsCalendarChannelsContainer.tsx index a40ec5c7d7..0f1580b254 100644 --- a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsCalendarChannelsContainer.tsx +++ b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsCalendarChannelsContainer.tsx @@ -1,5 +1,4 @@ import styled from '@emotion/styled'; -import { useRecoilValue } from 'recoil'; import { type CalendarChannel, @@ -7,6 +6,7 @@ import { } from '@/accounts/types/CalendarChannel'; import { type ConnectedAccount } from '@/accounts/types/ConnectedAccount'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; import { SettingsAccountsCalendarChannelDetails } from '@/settings/accounts/components/SettingsAccountsCalendarChannelDetails'; @@ -26,7 +26,7 @@ export const SettingsAccountsCalendarChannelsContainer = () => { activeTabIdComponentState, SETTINGS_ACCOUNT_CALENDAR_CHANNELS_TAB_LIST_COMPONENT_ID, ); - const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); + const currentWorkspaceMember = useRecoilValueV2(currentWorkspaceMemberState); const { records: accounts } = useFindManyRecords({ objectNameSingular: CoreObjectNameSingular.ConnectedAccount, diff --git a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsCalendarChannelsGeneral.tsx b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsCalendarChannelsGeneral.tsx index 134f09e984..c4374c8dde 100644 --- a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsCalendarChannelsGeneral.tsx +++ b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsCalendarChannelsGeneral.tsx @@ -6,7 +6,7 @@ import styled from '@emotion/styled'; import { t } from '@lingui/core/macro'; import { Section } from '@react-email/components'; import { addMinutes, endOfDay, min, startOfDay } from 'date-fns'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { H2Title } from 'twenty-ui/display'; import { CalendarChannelVisibility, @@ -21,7 +21,7 @@ const StyledGeneralContainer = styled.div` `; export const SettingsAccountsCalendarChannelsGeneral = () => { - const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); + const currentWorkspaceMember = useRecoilValueV2(currentWorkspaceMemberState); const exampleStartDate = new Date(); const exampleEndDate = min([ diff --git a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsListEmptyStateCard.tsx b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsListEmptyStateCard.tsx index 3b27b66d09..241668e2ba 100644 --- a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsListEmptyStateCard.tsx +++ b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsListEmptyStateCard.tsx @@ -8,7 +8,7 @@ import { SettingsCard } from '@/settings/components/SettingsCard'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { useLingui } from '@lingui/react/macro'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { ConnectedAccountProvider, SettingsPath } from 'twenty-shared/types'; import { getSettingsPath } from 'twenty-shared/utils'; import { IconAt, IconGoogle, IconMicrosoft } from 'twenty-ui/display'; @@ -26,20 +26,24 @@ export const SettingsAccountsListEmptyStateCard = () => { const { t } = useLingui(); const theme = useTheme(); - const isGoogleMessagingEnabled = useRecoilValue( + const isGoogleMessagingEnabled = useRecoilValueV2( isGoogleMessagingEnabledState, ); - const isMicrosoftMessagingEnabled = useRecoilValue( + const isMicrosoftMessagingEnabled = useRecoilValueV2( isMicrosoftMessagingEnabledState, ); - const isGoogleCalendarEnabled = useRecoilValue(isGoogleCalendarEnabledState); + const isGoogleCalendarEnabled = useRecoilValueV2( + isGoogleCalendarEnabledState, + ); - const isMicrosoftCalendarEnabled = useRecoilValue( + const isMicrosoftCalendarEnabled = useRecoilValueV2( isMicrosoftCalendarEnabledState, ); - const isImapSmtpCaldavEnabled = useRecoilValue(isImapSmtpCaldavEnabledState); + const isImapSmtpCaldavEnabled = useRecoilValueV2( + isImapSmtpCaldavEnabledState, + ); return ( diff --git a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsMessageChannelsContainer.tsx b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsMessageChannelsContainer.tsx index 45c74e21bf..2a6c370aa5 100644 --- a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsMessageChannelsContainer.tsx +++ b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsMessageChannelsContainer.tsx @@ -1,5 +1,4 @@ import styled from '@emotion/styled'; -import { useRecoilValue } from 'recoil'; import { type ConnectedAccount } from '@/accounts/types/ConnectedAccount'; import { @@ -7,6 +6,7 @@ import { MessageChannelSyncStage, } from '@/accounts/types/MessageChannel'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useGenerateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/hooks/useGenerateDepthRecordGqlFieldsFromObject'; import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; @@ -30,7 +30,7 @@ export const SettingsAccountsMessageChannelsContainer = () => { activeTabIdComponentState, SETTINGS_ACCOUNT_MESSAGE_CHANNELS_TAB_LIST_COMPONENT_ID, ); - const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); + const currentWorkspaceMember = useRecoilValueV2(currentWorkspaceMemberState); const setSelectedMessageChannel = useSetRecoilStateV2( settingsAccountsSelectedMessageChannelStateV2, ); diff --git a/packages/twenty-front/src/modules/settings/accounts/hooks/useImapSmtpCaldavConnectionForm.ts b/packages/twenty-front/src/modules/settings/accounts/hooks/useImapSmtpCaldavConnectionForm.ts index d0383d1950..54458be82e 100644 --- a/packages/twenty-front/src/modules/settings/accounts/hooks/useImapSmtpCaldavConnectionForm.ts +++ b/packages/twenty-front/src/modules/settings/accounts/hooks/useImapSmtpCaldavConnectionForm.ts @@ -1,9 +1,9 @@ import { zodResolver } from '@hookform/resolvers/zod'; import { useCallback, useMemo } from 'react'; import { useForm } from 'react-hook-form'; -import { useRecoilValue } from 'recoil'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { t } from '@lingui/core/macro'; @@ -41,7 +41,7 @@ export const useImapSmtpCaldavConnectionForm = ({ connectedAccountId, }: UseConnectionFormProps = {}) => { const navigate = useNavigateSettings(); - const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); + const currentWorkspaceMember = useRecoilValueV2(currentWorkspaceMemberState); const formMethods = useForm({ mode: 'onSubmit', diff --git a/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminContent.tsx b/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminContent.tsx index 86584a7cdc..9fc97cf6ee 100644 --- a/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminContent.tsx +++ b/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminContent.tsx @@ -4,11 +4,11 @@ import { SETTINGS_ADMIN_TABS } from '@/settings/admin-panel/constants/SettingsAd import { SETTINGS_ADMIN_TABS_ID } from '@/settings/admin-panel/constants/SettingsAdminTabsId'; import { TabList } from '@/ui/layout/tab-list/components/TabList'; import { t } from '@lingui/core/macro'; -import { useRecoilValue } from 'recoil'; import { IconHeart, IconSettings2, IconVariable } from 'twenty-ui/display'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; export const SettingsAdminContent = () => { - const currentUser = useRecoilValue(currentUserState); + const currentUser = useRecoilValueV2(currentUserState); const canAccessFullAdminPanel = currentUser?.canAccessFullAdminPanel; const canImpersonate = currentUser?.canImpersonate; diff --git a/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminGeneral.tsx b/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminGeneral.tsx index 68e3c93cb7..f1e96330f2 100644 --- a/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminGeneral.tsx +++ b/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminGeneral.tsx @@ -10,11 +10,11 @@ import { t } from '@lingui/core/macro'; import { isNonEmptyString } from '@sniptt/guards'; import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2'; import { useState } from 'react'; -import { useRecoilValue } from 'recoil'; import { REACT_APP_SERVER_BASE_URL } from '~/config'; import { useUserLookupAdminPanelMutation } from '~/generated-metadata/graphql'; import { currentUserState } from '@/auth/states/currentUserState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { SettingsAdminTableCard } from '@/settings/admin-panel/components/SettingsAdminTableCard'; import { SettingsAdminVersionContainer } from '@/settings/admin-panel/components/SettingsAdminVersionContainer'; import { SETTINGS_ADMIN_USER_LOOKUP_WORKSPACE_TABS_ID } from '@/settings/admin-panel/constants/SettingsAdminUserLookupWorkspaceTabsId'; @@ -53,13 +53,13 @@ export const SettingsAdminGeneral = () => { const [userLookup] = useUserLookupAdminPanelMutation(); - const currentUser = useRecoilValue(currentUserState); + const currentUser = useRecoilValueV2(currentUserState); const canAccessFullAdminPanel = currentUser?.canAccessFullAdminPanel; const canImpersonate = currentUser?.canImpersonate; - const canManageFeatureFlags = useRecoilValue(canManageFeatureFlagsState); + const canManageFeatureFlags = useRecoilValueV2(canManageFeatureFlagsState); const handleSearch = async () => { setActiveTabId(''); diff --git a/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminWorkspaceContent.tsx b/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminWorkspaceContent.tsx index 22c8b4d1b4..08abddb5fc 100644 --- a/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminWorkspaceContent.tsx +++ b/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminWorkspaceContent.tsx @@ -20,8 +20,8 @@ import { useLingui } from '@lingui/react/macro'; import { isNonEmptyString } from '@sniptt/guards'; import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { useState } from 'react'; -import { useRecoilState, useRecoilValue } from 'recoil'; import { getImageAbsoluteURI, isDefined } from 'twenty-shared/utils'; +import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2'; import { AvatarChip, Chip } from 'twenty-ui/components'; import { H2Title, @@ -58,10 +58,10 @@ const StyledButtonContainer = styled.div` export const SettingsAdminWorkspaceContent = ({ activeWorkspace, }: SettingsAdminWorkspaceContentProps) => { - const canManageFeatureFlags = useRecoilValue(canManageFeatureFlagsState); + const canManageFeatureFlags = useRecoilValueV2(canManageFeatureFlagsState); const { enqueueErrorSnackBar } = useSnackBar(); - const [currentUser] = useRecoilState(currentUserState); - const currentWorkspace = useRecoilValue(currentWorkspaceState); + const [currentUser] = useRecoilStateV2(currentUserState); + const currentWorkspace = useRecoilValueV2(currentWorkspaceState); const [updateFeatureFlag] = useUpdateWorkspaceFeatureFlagMutation(); const [isImpersonateLoading, setIsImpersonationLoading] = useState(false); diff --git a/packages/twenty-front/src/modules/settings/admin-panel/config-variables/components/ConfigVariableActionButtons.tsx b/packages/twenty-front/src/modules/settings/admin-panel/config-variables/components/ConfigVariableActionButtons.tsx index 862b6426f8..6e4a16cf9b 100644 --- a/packages/twenty-front/src/modules/settings/admin-panel/config-variables/components/ConfigVariableActionButtons.tsx +++ b/packages/twenty-front/src/modules/settings/admin-panel/config-variables/components/ConfigVariableActionButtons.tsx @@ -1,5 +1,5 @@ import { useLingui } from '@lingui/react/macro'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { isConfigVariablesInDbEnabledState } from '@/client-config/states/isConfigVariablesInDbEnabledState'; import { @@ -29,7 +29,7 @@ export const ConfigVariableActionButtons = ({ onReset, }: ConfigVariableActionButtonsProps) => { const { t } = useLingui(); - const isConfigVariablesInDbEnabled = useRecoilValue( + const isConfigVariablesInDbEnabled = useRecoilValueV2( isConfigVariablesInDbEnabledState, ); const isFromDatabase = variable.source === ConfigSource.DATABASE; diff --git a/packages/twenty-front/src/modules/settings/admin-panel/config-variables/components/ConfigVariableHelpText.tsx b/packages/twenty-front/src/modules/settings/admin-panel/config-variables/components/ConfigVariableHelpText.tsx index 561f193559..d147e65677 100644 --- a/packages/twenty-front/src/modules/settings/admin-panel/config-variables/components/ConfigVariableHelpText.tsx +++ b/packages/twenty-front/src/modules/settings/admin-panel/config-variables/components/ConfigVariableHelpText.tsx @@ -2,7 +2,7 @@ import styled from '@emotion/styled'; import { useLingui } from '@lingui/react/macro'; import { isConfigVariablesInDbEnabledState } from '@/client-config/states/isConfigVariablesInDbEnabledState'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { ConfigSource, type ConfigVariable, @@ -24,7 +24,7 @@ export const ConfigVariableHelpText = ({ variable, hasValueChanged, }: ConfigVariableHelpTextProps) => { - const isConfigVariablesInDbEnabled = useRecoilValue( + const isConfigVariablesInDbEnabled = useRecoilValueV2( isConfigVariablesInDbEnabledState, ); const { t } = useLingui(); diff --git a/packages/twenty-front/src/modules/settings/admin-panel/config-variables/components/ConfigVariableOptionsDropdownContent.tsx b/packages/twenty-front/src/modules/settings/admin-panel/config-variables/components/ConfigVariableOptionsDropdownContent.tsx index 181f62e4e6..cf9dc9789f 100644 --- a/packages/twenty-front/src/modules/settings/admin-panel/config-variables/components/ConfigVariableOptionsDropdownContent.tsx +++ b/packages/twenty-front/src/modules/settings/admin-panel/config-variables/components/ConfigVariableOptionsDropdownContent.tsx @@ -10,7 +10,7 @@ import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/Drop import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; import { useTheme } from '@emotion/react'; import { t } from '@lingui/core/macro'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { IconChevronLeft, IconEye, IconEyeOff } from 'twenty-ui/display'; import { MenuItem, MenuItemSelectTag } from 'twenty-ui/navigation'; @@ -39,7 +39,7 @@ export const ConfigVariableOptionsDropdownContent = ({ }: ConfigVariableOptionsDropdownContentProps) => { const theme = useTheme(); - const isConfigVariablesInDbEnabled = useRecoilValue( + const isConfigVariablesInDbEnabled = useRecoilValueV2( isConfigVariablesInDbEnabledState, ); diff --git a/packages/twenty-front/src/modules/settings/admin-panel/config-variables/components/ConfigVariableValueInput.tsx b/packages/twenty-front/src/modules/settings/admin-panel/config-variables/components/ConfigVariableValueInput.tsx index 6bd224726f..367549efdb 100644 --- a/packages/twenty-front/src/modules/settings/admin-panel/config-variables/components/ConfigVariableValueInput.tsx +++ b/packages/twenty-front/src/modules/settings/admin-panel/config-variables/components/ConfigVariableValueInput.tsx @@ -3,7 +3,7 @@ import { useLingui } from '@lingui/react/macro'; import { isConfigVariablesInDbEnabledState } from '@/client-config/states/isConfigVariablesInDbEnabledState'; import { TextInput } from '@/ui/input/components/TextInput'; import styled from '@emotion/styled'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { type ConfigVariableValue } from 'twenty-shared/types'; import { type ConfigVariable } from '~/generated-metadata/graphql'; import { ConfigVariableDatabaseInput } from './ConfigVariableDatabaseInput'; @@ -26,7 +26,7 @@ export const ConfigVariableValueInput = ({ disabled, }: ConfigVariableValueInputProps) => { const { t } = useLingui(); - const isConfigVariablesInDbEnabled = useRecoilValue( + const isConfigVariablesInDbEnabled = useRecoilValueV2( isConfigVariablesInDbEnabledState, ); diff --git a/packages/twenty-front/src/modules/settings/admin-panel/hooks/useImpersonationAuth.ts b/packages/twenty-front/src/modules/settings/admin-panel/hooks/useImpersonationAuth.ts index 157e6600e8..6d61f5e80c 100644 --- a/packages/twenty-front/src/modules/settings/admin-panel/hooks/useImpersonationAuth.ts +++ b/packages/twenty-front/src/modules/settings/admin-panel/hooks/useImpersonationAuth.ts @@ -1,12 +1,12 @@ import { isAppEffectRedirectEnabledState } from '@/app/states/isAppEffectRedirectEnabledState'; import { useAuth } from '@/auth/hooks/useAuth'; import { shouldAppBeLoadingState } from '@/object-metadata/states/shouldAppBeLoadingState'; -import { useSetRecoilState } from 'recoil'; +import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; export const useImpersonationAuth = () => { const { getAuthTokensFromLoginToken } = useAuth(); - const setShouldAppBeLoading = useSetRecoilState(shouldAppBeLoadingState); - const setIsAppEffectRedirectEnabled = useSetRecoilState( + const setShouldAppBeLoading = useSetRecoilStateV2(shouldAppBeLoadingState); + const setIsAppEffectRedirectEnabled = useSetRecoilStateV2( isAppEffectRedirectEnabledState, ); diff --git a/packages/twenty-front/src/modules/settings/components/SettingsItemTypeTag.tsx b/packages/twenty-front/src/modules/settings/components/SettingsItemTypeTag.tsx index 98c4b28f34..723ab62c7b 100644 --- a/packages/twenty-front/src/modules/settings/components/SettingsItemTypeTag.tsx +++ b/packages/twenty-front/src/modules/settings/components/SettingsItemTypeTag.tsx @@ -2,8 +2,8 @@ import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { getItemTagInfo } from '@/settings/data-model/utils/getItemTagInfo'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; -import { useRecoilValue } from 'recoil'; import { Avatar } from 'twenty-ui/display'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; type SettingsItemTypeTagProps = { item: { @@ -27,7 +27,7 @@ export const SettingsItemTypeTag = ({ item: { isCustom, isRemote, applicationId }, }: SettingsItemTypeTagProps) => { const theme = useTheme(); - const currentWorkspace = useRecoilValue(currentWorkspaceState); + const currentWorkspace = useRecoilValueV2(currentWorkspaceState); const itemTagInfo = getItemTagInfo({ item: { isCustom, isRemote, applicationId }, workspaceCustomApplicationId: diff --git a/packages/twenty-front/src/modules/settings/data-model/graph-overview/components/SettingsDataModelOverviewField.tsx b/packages/twenty-front/src/modules/settings/data-model/graph-overview/components/SettingsDataModelOverviewField.tsx index f70ff0e4db..8583f46375 100644 --- a/packages/twenty-front/src/modules/settings/data-model/graph-overview/components/SettingsDataModelOverviewField.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/graph-overview/components/SettingsDataModelOverviewField.tsx @@ -1,9 +1,8 @@ import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { Handle, Position } from '@xyflow/react'; -import { useRecoilValue } from 'recoil'; - import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; import { useIcons } from 'twenty-ui/display'; import { RelationType } from '~/generated-metadata/graphql'; @@ -26,7 +25,7 @@ const StyledFieldName = styled.div` `; export const ObjectFieldRow = ({ field }: ObjectFieldRowProps) => { - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); const { getIcon } = useIcons(); const theme = useTheme(); diff --git a/packages/twenty-front/src/modules/settings/domains/components/SettingsCustomDomain.tsx b/packages/twenty-front/src/modules/settings/domains/components/SettingsCustomDomain.tsx index 099fed003c..4ffc994384 100644 --- a/packages/twenty-front/src/modules/settings/domains/components/SettingsCustomDomain.tsx +++ b/packages/twenty-front/src/modules/settings/domains/components/SettingsCustomDomain.tsx @@ -5,6 +5,7 @@ import styled from '@emotion/styled'; import { useLingui } from '@lingui/react/macro'; import { Controller, useFormContext } from 'react-hook-form'; import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { H2Title, IconReload, IconTrash } from 'twenty-ui/display'; import { Button, ButtonGroup } from 'twenty-ui/input'; import { Section } from 'twenty-ui/layout'; @@ -43,7 +44,7 @@ export const SettingsCustomDomain = () => { const { checkCustomDomainRecords } = useCheckCustomDomainValidRecords(); - const currentWorkspace = useRecoilValue(currentWorkspaceState); + const currentWorkspace = useRecoilValueV2(currentWorkspaceState); const { t } = useLingui(); diff --git a/packages/twenty-front/src/modules/settings/domains/components/SettingsDomainRecords.tsx b/packages/twenty-front/src/modules/settings/domains/components/SettingsDomainRecords.tsx index f27cbe0f02..33982c2bf2 100644 --- a/packages/twenty-front/src/modules/settings/domains/components/SettingsDomainRecords.tsx +++ b/packages/twenty-front/src/modules/settings/domains/components/SettingsDomainRecords.tsx @@ -1,7 +1,6 @@ import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { SettingsDnsRecordsTable } from '@/settings/components/SettingsDnsRecordsTable'; import { t } from '@lingui/core/macro'; -import { useRecoilValue } from 'recoil'; import { H2Title } from 'twenty-ui/display'; import { Section } from 'twenty-ui/layout'; import { type ThemeColor } from 'twenty-ui/theme'; @@ -9,13 +8,14 @@ import { type DomainRecord, type DomainValidRecords, } from '~/generated-metadata/graphql'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; export const SettingsDomainRecords = ({ records, }: { records: DomainValidRecords['records']; }) => { - const currentWorkspace = useRecoilValue(currentWorkspaceState); + const currentWorkspace = useRecoilValueV2(currentWorkspaceState); const rowsDefinitions = [ { name: 'Domain Setup', validationType: 'redirection' as const }, diff --git a/packages/twenty-front/src/modules/settings/domains/components/SettingsSubdomain.tsx b/packages/twenty-front/src/modules/settings/domains/components/SettingsSubdomain.tsx index 6d5c48e922..6062d412bb 100644 --- a/packages/twenty-front/src/modules/settings/domains/components/SettingsSubdomain.tsx +++ b/packages/twenty-front/src/modules/settings/domains/components/SettingsSubdomain.tsx @@ -5,10 +5,10 @@ import { Controller, useFormContext } from 'react-hook-form'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { domainConfigurationState } from '@/domain-manager/states/domainConfigurationState'; -import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { H2Title } from 'twenty-ui/display'; import { Section } from 'twenty-ui/layout'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; const StyledDomainFormWrapper = styled.div` align-items: center; @@ -16,10 +16,10 @@ const StyledDomainFormWrapper = styled.div` `; export const SettingsSubdomain = () => { - const domainConfiguration = useRecoilValue(domainConfigurationState); + const domainConfiguration = useRecoilValueV2(domainConfigurationState); const { t } = useLingui(); - const currentWorkspace = useRecoilValue(currentWorkspaceState); + const currentWorkspace = useRecoilValueV2(currentWorkspaceState); const { control } = useFormContext<{ subdomain: string; diff --git a/packages/twenty-front/src/modules/settings/domains/components/SettingsWorkspaceDomainCard.tsx b/packages/twenty-front/src/modules/settings/domains/components/SettingsWorkspaceDomainCard.tsx index e1870620ff..e7d6e5bfaf 100644 --- a/packages/twenty-front/src/modules/settings/domains/components/SettingsWorkspaceDomainCard.tsx +++ b/packages/twenty-front/src/modules/settings/domains/components/SettingsWorkspaceDomainCard.tsx @@ -2,16 +2,18 @@ import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState'; import { SettingsCard } from '@/settings/components/SettingsCard'; import { useLingui } from '@lingui/react/macro'; -import { useRecoilValue } from 'recoil'; import { SettingsPath } from 'twenty-shared/types'; import { getSettingsPath } from 'twenty-shared/utils'; import { IconWorld, Status } from 'twenty-ui/display'; import { UndecoratedLink } from 'twenty-ui/navigation'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; export const SettingsWorkspaceDomainCard = () => { const { t } = useLingui(); - const isMultiWorkspaceEnabled = useRecoilValue(isMultiWorkspaceEnabledState); - const currentWorkspace = useRecoilValue(currentWorkspaceState); + const isMultiWorkspaceEnabled = useRecoilValueV2( + isMultiWorkspaceEnabledState, + ); + const currentWorkspace = useRecoilValueV2(currentWorkspaceState); if (!isMultiWorkspaceEnabled) { return null; diff --git a/packages/twenty-front/src/modules/settings/domains/hooks/useCheckCustomDomainValidRecords.ts b/packages/twenty-front/src/modules/settings/domains/hooks/useCheckCustomDomainValidRecords.ts index 3567cb40e5..f6219b8a9e 100644 --- a/packages/twenty-front/src/modules/settings/domains/hooks/useCheckCustomDomainValidRecords.ts +++ b/packages/twenty-front/src/modules/settings/domains/hooks/useCheckCustomDomainValidRecords.ts @@ -1,15 +1,16 @@ import { useCheckCustomDomainValidRecordsMutation } from '~/generated-metadata/graphql'; -import { useRecoilState, useRecoilValue } from 'recoil'; +import { useRecoilState } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { customDomainRecordsState } from '@/settings/domains/states/customDomainRecordsState'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; export const useCheckCustomDomainValidRecords = () => { const [checkCustomDomainValidRecords] = useCheckCustomDomainValidRecordsMutation(); const { enqueueErrorSnackBar } = useSnackBar(); - const currentWorkspace = useRecoilValue(currentWorkspaceState); + const currentWorkspace = useRecoilValueV2(currentWorkspaceState); const [{ isLoading }, setCustomDomainRecords] = useRecoilState( customDomainRecordsState, diff --git a/packages/twenty-front/src/modules/settings/experience/components/FormatPreferencesSettings.tsx b/packages/twenty-front/src/modules/settings/experience/components/FormatPreferencesSettings.tsx index 3ef96fbe03..c38b1056db 100644 --- a/packages/twenty-front/src/modules/settings/experience/components/FormatPreferencesSettings.tsx +++ b/packages/twenty-front/src/modules/settings/experience/components/FormatPreferencesSettings.tsx @@ -1,7 +1,7 @@ import styled from '@emotion/styled'; -import { useRecoilValue } from 'recoil'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { DateFormat } from '@/localization/constants/DateFormat'; import { NumberFormat } from '@/localization/constants/NumberFormat'; import { TimeFormat } from '@/localization/constants/TimeFormat'; @@ -26,7 +26,7 @@ const StyledContainer = styled.div` `; export const FormatPreferencesSettings = () => { - const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); + const currentWorkspaceMember = useRecoilValueV2(currentWorkspaceMemberState); const { formatPreferences, updateFormatPreference } = useFormatPreferences(); if (!isDefined(currentWorkspaceMember)) return null; diff --git a/packages/twenty-front/src/modules/settings/hooks/__tests__/useSettingsNavigationItems.test.tsx b/packages/twenty-front/src/modules/settings/hooks/__tests__/useSettingsNavigationItems.test.tsx index ccd70fb2d3..72fdd688b5 100644 --- a/packages/twenty-front/src/modules/settings/hooks/__tests__/useSettingsNavigationItems.test.tsx +++ b/packages/twenty-front/src/modules/settings/hooks/__tests__/useSettingsNavigationItems.test.tsx @@ -3,7 +3,7 @@ import { MockedProvider } from '@apollo/client/testing'; import { renderHook } from '@testing-library/react'; import { type ReactNode } from 'react'; import { MemoryRouter } from 'react-router-dom'; -import { type MutableSnapshot, RecoilRoot } from 'recoil'; +import { RecoilRoot } from 'recoil'; import { SettingsPath } from 'twenty-shared/types'; import { type Billing, @@ -15,8 +15,13 @@ import { currentUserState } from '@/auth/states/currentUserState'; import { billingState } from '@/client-config/states/billingState'; import { usePermissionFlagMap } from '@/settings/roles/hooks/usePermissionFlagMap'; import { SnackBarComponentInstanceContext } from '@/ui/feedback/snack-bar-manager/contexts/SnackBarComponentInstanceContext'; +import { + jotaiStore, + resetJotaiStore, +} from '@/ui/utilities/state/jotai/jotaiStore'; import { i18n } from '@lingui/core'; import { I18nProvider } from '@lingui/react'; +import { Provider as JotaiProvider } from 'jotai'; import { SOURCE_LOCALE } from 'twenty-shared/translations'; import { messages } from '~/locales/generated/en'; @@ -45,24 +50,21 @@ const mockBilling: Billing = { __typename: 'Billing', }; -const initializeState = ({ set }: MutableSnapshot) => { - set(currentUserState, mockCurrentUser); - set(billingState, mockBilling); -}; - const Wrapper = ({ children }: { children: ReactNode }) => ( - - - - - {children} - - - - + + + + + + {children} + + + + + ); @@ -70,7 +72,19 @@ jest.mock('@/settings/roles/hooks/usePermissionFlagMap', () => ({ usePermissionFlagMap: jest.fn(), })); +jest.mock('@/domain-manager/hooks/useRedirectToWorkspaceDomain', () => ({ + useRedirectToWorkspaceDomain: jest.fn().mockImplementation(() => ({ + redirectToWorkspaceDomain: jest.fn(), + })), +})); + describe('useSettingsNavigationItems', () => { + beforeEach(() => { + resetJotaiStore(); + jotaiStore.set(currentUserState.atom, mockCurrentUser); + jotaiStore.set(billingState.atom, mockBilling); + }); + it('should hide workspace settings when no permissions', () => { (usePermissionFlagMap as jest.Mock).mockImplementation(() => ({ [PermissionFlagType.WORKSPACE]: false, diff --git a/packages/twenty-front/src/modules/settings/hooks/useSettingsNavigationItems.tsx b/packages/twenty-front/src/modules/settings/hooks/useSettingsNavigationItems.tsx index 7525b2131e..0fa747e8ed 100644 --- a/packages/twenty-front/src/modules/settings/hooks/useSettingsNavigationItems.tsx +++ b/packages/twenty-front/src/modules/settings/hooks/useSettingsNavigationItems.tsx @@ -11,7 +11,7 @@ import { type NavigationDrawerItemIndentationLevel } from '@/ui/navigation/navig import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { t } from '@lingui/core/macro'; import { isNonEmptyString } from '@sniptt/guards'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { IconApi, // IconApps, // TODO: Re-enable when integrations page is ready @@ -62,13 +62,13 @@ export type SettingsNavigationItem = { }; const useSettingsNavigationItems = (): SettingsNavigationSection[] => { - const billing = useRecoilValue(billingState); + const billing = useRecoilValueV2(billingState); const { signOut } = useAuth(); - const supportChat = useRecoilValue(supportChatState); - const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); + const supportChat = useRecoilValueV2(supportChatState); + const currentWorkspaceMember = useRecoilValueV2(currentWorkspaceMemberState); const isBillingEnabled = billing?.isBillingEnabled ?? false; - const currentUser = useRecoilValue(currentUserState); + const currentUser = useRecoilValueV2(currentUserState); const isAdminEnabled = (currentUser?.canImpersonate || currentUser?.canAccessFullAdminPanel) ?? false; diff --git a/packages/twenty-front/src/modules/settings/lab/components/SettingsLabContent.tsx b/packages/twenty-front/src/modules/settings/lab/components/SettingsLabContent.tsx index 0f64241fec..ecc990f2f4 100644 --- a/packages/twenty-front/src/modules/settings/lab/components/SettingsLabContent.tsx +++ b/packages/twenty-front/src/modules/settings/lab/components/SettingsLabContent.tsx @@ -3,9 +3,9 @@ import { SettingsOptionCardContentToggle } from '@/settings/components/SettingsO import { useLabPublicFeatureFlags } from '@/settings/lab/hooks/useLabPublicFeatureFlags'; import styled from '@emotion/styled'; import { useState } from 'react'; -import { useRecoilValue } from 'recoil'; import { Card } from 'twenty-ui/layout'; import { type FeatureFlagKey } from '~/generated-metadata/graphql'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; const StyledCardGrid = styled.div` display: grid; @@ -22,7 +22,7 @@ const StyledImage = styled.img` `; export const SettingsLabContent = () => { - const currentWorkspace = useRecoilValue(currentWorkspaceState); + const currentWorkspace = useRecoilValueV2(currentWorkspaceState); const { labPublicFeatureFlags, handleLabPublicFeatureFlagUpdate } = useLabPublicFeatureFlags(); const [hasImageLoadingError, setHasImageLoadingError] = useState< diff --git a/packages/twenty-front/src/modules/settings/lab/hooks/useLabPublicFeatureFlags.ts b/packages/twenty-front/src/modules/settings/lab/hooks/useLabPublicFeatureFlags.ts index e6992a07e6..25b0b40bb0 100644 --- a/packages/twenty-front/src/modules/settings/lab/hooks/useLabPublicFeatureFlags.ts +++ b/packages/twenty-front/src/modules/settings/lab/hooks/useLabPublicFeatureFlags.ts @@ -3,7 +3,7 @@ import { labPublicFeatureFlagsStateV2 } from '@/client-config/states/labPublicFe import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { t } from '@lingui/core/macro'; import { useState } from 'react'; -import { useRecoilState } from 'recoil'; +import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2'; import { isDefined } from 'twenty-shared/utils'; import { type FeatureFlagKey, @@ -12,7 +12,7 @@ import { export const useLabPublicFeatureFlags = () => { const [error, setError] = useState(null); - const [currentWorkspace, setCurrentWorkspace] = useRecoilState( + const [currentWorkspace, setCurrentWorkspace] = useRecoilStateV2( currentWorkspaceState, ); const labPublicFeatureFlags = useRecoilValueV2(labPublicFeatureFlagsStateV2); diff --git a/packages/twenty-front/src/modules/settings/profile/components/DeleteAccount.tsx b/packages/twenty-front/src/modules/settings/profile/components/DeleteAccount.tsx index 06503647a1..21b9b23ac1 100644 --- a/packages/twenty-front/src/modules/settings/profile/components/DeleteAccount.tsx +++ b/packages/twenty-front/src/modules/settings/profile/components/DeleteAccount.tsx @@ -1,9 +1,8 @@ -import { useRecoilValue } from 'recoil'; - import { useAuth } from '@/auth/hooks/useAuth'; import { availableWorkspacesState } from '@/auth/states/availableWorkspacesState'; import { currentUserState } from '@/auth/states/currentUserState'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { countAvailableWorkspaces } from '@/auth/utils/availableWorkspacesUtils'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal'; @@ -32,12 +31,12 @@ export const DeleteAccount = () => { const [deleteUserAccount] = useDeleteUserAccountMutation(); const [deleteUserFromWorkspace] = useDeleteUserWorkspaceMutation(); - const currentUser = useRecoilValue(currentUserState); + const currentUser = useRecoilValueV2(currentUserState); const userEmail = currentUser?.email; - const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); + const currentWorkspaceMember = useRecoilValueV2(currentWorkspaceMemberState); const currentWorkspaceMemberId = currentWorkspaceMember?.id; const { signOut } = useAuth(); - const availableWorkspaces = useRecoilValue(availableWorkspacesState); + const availableWorkspaces = useRecoilValueV2(availableWorkspacesState); const availableWorkspacesCount = countAvailableWorkspaces(availableWorkspaces); diff --git a/packages/twenty-front/src/modules/settings/profile/components/DeleteWorkspace.tsx b/packages/twenty-front/src/modules/settings/profile/components/DeleteWorkspace.tsx index 9d05a64588..00fb69e287 100644 --- a/packages/twenty-front/src/modules/settings/profile/components/DeleteWorkspace.tsx +++ b/packages/twenty-front/src/modules/settings/profile/components/DeleteWorkspace.tsx @@ -1,8 +1,8 @@ import { Trans, useLingui } from '@lingui/react/macro'; -import { useRecoilValue } from 'recoil'; import { useAuth } from '@/auth/hooks/useAuth'; import { currentUserState } from '@/auth/states/currentUserState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { useRedirectToDefaultDomain } from '@/domain-manager/hooks/useRedirectToDefaultDomain'; import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal'; import { useModal } from '@/ui/layout/modal/hooks/useModal'; @@ -14,7 +14,7 @@ const DELETE_WORKSPACE_MODAL_ID = 'delete-workspace-modal'; export const DeleteWorkspace = () => { const [deleteCurrentWorkspace] = useDeleteCurrentWorkspaceMutation(); - const currentUser = useRecoilValue(currentUserState); + const currentUser = useRecoilValueV2(currentUserState); const userEmail = currentUser?.email; const { t } = useLingui(); const { openModal } = useModal(); diff --git a/packages/twenty-front/src/modules/settings/profile/components/EmailField.tsx b/packages/twenty-front/src/modules/settings/profile/components/EmailField.tsx index da5beaf609..904a736817 100644 --- a/packages/twenty-front/src/modules/settings/profile/components/EmailField.tsx +++ b/packages/twenty-front/src/modules/settings/profile/components/EmailField.tsx @@ -1,8 +1,8 @@ import styled from '@emotion/styled'; import { useState } from 'react'; -import { useRecoilValue } from 'recoil'; import { currentUserState } from '@/auth/states/currentUserState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { useCanEditProfileField } from '@/settings/profile/hooks/useCanEditProfileField'; import { useUpdateEmail } from '@/settings/profile/hooks/useUpdateEmail'; import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput'; @@ -38,7 +38,7 @@ const StyledActionButton = styled(Button)` `; export const EmailField = () => { - const currentUser = useRecoilValue(currentUserState); + const currentUser = useRecoilValueV2(currentUserState); const { canEdit } = useCanEditProfileField('email'); const { updateEmail } = useUpdateEmail(); diff --git a/packages/twenty-front/src/modules/settings/profile/components/NameFields.tsx b/packages/twenty-front/src/modules/settings/profile/components/NameFields.tsx index 045dbfe586..f10c646ab9 100644 --- a/packages/twenty-front/src/modules/settings/profile/components/NameFields.tsx +++ b/packages/twenty-front/src/modules/settings/profile/components/NameFields.tsx @@ -1,11 +1,12 @@ import styled from '@emotion/styled'; import { useLingui } from '@lingui/react/macro'; import { useEffect, useState } from 'react'; -import { useRecoilState, useRecoilValue } from 'recoil'; import { useDebouncedCallback } from 'use-debounce'; import { currentUserState } from '@/auth/states/currentUserState'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; +import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; import { useCanEditProfileField } from '@/settings/profile/hooks/useCanEditProfileField'; @@ -26,8 +27,8 @@ type NameFieldsProps = { export const NameFields = ({ autoSave = true }: NameFieldsProps) => { const { t } = useLingui(); - const currentUser = useRecoilValue(currentUserState); - const [currentWorkspaceMember, setCurrentWorkspaceMember] = useRecoilState( + const currentUser = useRecoilValueV2(currentUserState); + const [currentWorkspaceMember, setCurrentWorkspaceMember] = useRecoilStateV2( currentWorkspaceMemberState, ); const { canEdit: canEditFirstName } = useCanEditProfileField('firstName'); diff --git a/packages/twenty-front/src/modules/settings/profile/components/SetOrChangePassword.tsx b/packages/twenty-front/src/modules/settings/profile/components/SetOrChangePassword.tsx index 4a74b14253..096a9e4807 100644 --- a/packages/twenty-front/src/modules/settings/profile/components/SetOrChangePassword.tsx +++ b/packages/twenty-front/src/modules/settings/profile/components/SetOrChangePassword.tsx @@ -1,13 +1,13 @@ import { useHandleResetPassword } from '@/auth/sign-in-up/hooks/useHandleResetPassword'; import { currentUserState } from '@/auth/states/currentUserState'; import { useLingui } from '@lingui/react/macro'; -import { useRecoilValue } from 'recoil'; import { H2Title } from 'twenty-ui/display'; import { Button } from 'twenty-ui/input'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; export const SetOrChangePassword = () => { const { t } = useLingui(); - const currentUser = useRecoilValue(currentUserState); + const currentUser = useRecoilValueV2(currentUserState); const { handleResetPassword } = useHandleResetPassword(); diff --git a/packages/twenty-front/src/modules/settings/profile/hooks/useCanChangePassword.ts b/packages/twenty-front/src/modules/settings/profile/hooks/useCanChangePassword.ts index 6f74d9f90c..ffd5a2dab3 100644 --- a/packages/twenty-front/src/modules/settings/profile/hooks/useCanChangePassword.ts +++ b/packages/twenty-front/src/modules/settings/profile/hooks/useCanChangePassword.ts @@ -1,12 +1,11 @@ -import { useRecoilValue } from 'recoil'; - import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { PermissionFlagType } from '~/generated-metadata/graphql'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; export const useCanChangePassword = () => { - const currentWorkspace = useRecoilValue(currentWorkspaceState); - const currentUserWorkspace = useRecoilValue(currentUserWorkspaceState); + const currentWorkspace = useRecoilValueV2(currentWorkspaceState); + const currentUserWorkspace = useRecoilValueV2(currentUserWorkspaceState); const isPasswordAuthEnabled = currentWorkspace?.isPasswordAuthEnabled === true; diff --git a/packages/twenty-front/src/modules/settings/profile/hooks/useCanEditProfileField.ts b/packages/twenty-front/src/modules/settings/profile/hooks/useCanEditProfileField.ts index 02f389ea86..1c01f3203a 100644 --- a/packages/twenty-front/src/modules/settings/profile/hooks/useCanEditProfileField.ts +++ b/packages/twenty-front/src/modules/settings/profile/hooks/useCanEditProfileField.ts @@ -2,8 +2,8 @@ import { availableWorkspacesState } from '@/auth/states/availableWorkspacesState import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { countAvailableWorkspaces } from '@/auth/utils/availableWorkspacesUtils'; -import { useRecoilValue } from 'recoil'; import { PermissionFlagType } from '~/generated-metadata/graphql'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; export type EditableProfileField = | 'email' @@ -12,9 +12,9 @@ export type EditableProfileField = | 'profilePicture'; export const useCanEditProfileField = (field: EditableProfileField) => { - const currentWorkspace = useRecoilValue(currentWorkspaceState); - const currentUserWorkspace = useRecoilValue(currentUserWorkspaceState); - const availableWorkspaces = useRecoilValue(availableWorkspacesState); + const currentWorkspace = useRecoilValueV2(currentWorkspaceState); + const currentUserWorkspace = useRecoilValueV2(currentUserWorkspaceState); + const availableWorkspaces = useRecoilValueV2(availableWorkspacesState); if (!currentWorkspace || !currentUserWorkspace) { return { canEdit: false }; diff --git a/packages/twenty-front/src/modules/settings/profile/hooks/useUpdateEmail.ts b/packages/twenty-front/src/modules/settings/profile/hooks/useUpdateEmail.ts index 513a297ac8..cf7cb134cc 100644 --- a/packages/twenty-front/src/modules/settings/profile/hooks/useUpdateEmail.ts +++ b/packages/twenty-front/src/modules/settings/profile/hooks/useUpdateEmail.ts @@ -1,16 +1,16 @@ import { ApolloError } from '@apollo/client'; import { t } from '@lingui/core/macro'; -import { useRecoilValue } from 'recoil'; import { currentUserState } from '@/auth/states/currentUserState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { useUpdateUserEmailMutation } from '~/generated-metadata/graphql'; export const useUpdateEmail = () => { const { enqueueErrorSnackBar, enqueueInfoSnackBar } = useSnackBar(); - const currentUser = useRecoilValue(currentUserState); + const currentUser = useRecoilValueV2(currentUserState); const [updateUserEmail] = useUpdateUserEmailMutation(); diff --git a/packages/twenty-front/src/modules/settings/roles/components/SettingsRolesDefaultRole.tsx b/packages/twenty-front/src/modules/settings/roles/components/SettingsRolesDefaultRole.tsx index 8c49bd529c..99555ac0ac 100644 --- a/packages/twenty-front/src/modules/settings/roles/components/SettingsRolesDefaultRole.tsx +++ b/packages/twenty-front/src/modules/settings/roles/components/SettingsRolesDefaultRole.tsx @@ -5,7 +5,7 @@ import { import { SettingsOptionCardContentSelect } from '@/settings/components/SettingsOptions/SettingsOptionCardContentSelect'; import { Select } from '@/ui/input/components/Select'; import { t } from '@lingui/core/macro'; -import { useRecoilState } from 'recoil'; +import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2'; import { isDefined } from 'twenty-shared/utils'; import { H2Title, IconUserPin, useIcons } from 'twenty-ui/display'; import { Card, Section } from 'twenty-ui/layout'; @@ -24,7 +24,7 @@ export const SettingsRoleDefaultRole = ({ }: SettingsRoleDefaultRoleProps) => { const [updateWorkspace] = useUpdateWorkspaceMutation(); - const [currentWorkspace, setCurrentWorkspace] = useRecoilState( + const [currentWorkspace, setCurrentWorkspace] = useRecoilStateV2( currentWorkspaceState, ); diff --git a/packages/twenty-front/src/modules/settings/roles/components/SettingsRolesTableRow.tsx b/packages/twenty-front/src/modules/settings/roles/components/SettingsRolesTableRow.tsx index b1344cc417..9399c98549 100644 --- a/packages/twenty-front/src/modules/settings/roles/components/SettingsRolesTableRow.tsx +++ b/packages/twenty-front/src/modules/settings/roles/components/SettingsRolesTableRow.tsx @@ -4,7 +4,7 @@ import { TableRow } from '@/ui/layout/table/components/TableRow'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import React from 'react'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { SettingsPath } from 'twenty-shared/types'; import { getSettingsPath, isDefined } from 'twenty-shared/utils'; import { @@ -65,7 +65,9 @@ export const SettingsRolesTableRow = ({ role }: SettingsRolesTableRowProps) => { const { getIcon } = useIcons(); const Icon = getIcon(role.icon ?? 'IconUser'); - const currentWorkspaceMembers = useRecoilValue(currentWorkspaceMembersState); + const currentWorkspaceMembers = useRecoilValueV2( + currentWorkspaceMembersState, + ); const enrichedWorkspaceMembers = role.workspaceMembers .map((workspaceMember) => diff --git a/packages/twenty-front/src/modules/settings/roles/hooks/useHasPermissionFlag.ts b/packages/twenty-front/src/modules/settings/roles/hooks/useHasPermissionFlag.ts index 72f9c3ce93..71902a0c2f 100644 --- a/packages/twenty-front/src/modules/settings/roles/hooks/useHasPermissionFlag.ts +++ b/packages/twenty-front/src/modules/settings/roles/hooks/useHasPermissionFlag.ts @@ -1,14 +1,14 @@ import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState'; -import { useRecoilValue } from 'recoil'; import { WorkspaceActivationStatus, PermissionFlagType, } from '~/generated-metadata/graphql'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; export const useHasPermissionFlag = (permissionFlag?: PermissionFlagType) => { - const currentWorkspace = useRecoilValue(currentWorkspaceState); - const currentUserWorkspace = useRecoilValue(currentUserWorkspaceState); + const currentWorkspace = useRecoilValueV2(currentWorkspaceState); + const currentUserWorkspace = useRecoilValueV2(currentUserWorkspaceState); if (!permissionFlag) { return true; diff --git a/packages/twenty-front/src/modules/settings/roles/hooks/usePermissionFlagMap.ts b/packages/twenty-front/src/modules/settings/roles/hooks/usePermissionFlagMap.ts index 1da240b18d..a224959da7 100644 --- a/packages/twenty-front/src/modules/settings/roles/hooks/usePermissionFlagMap.ts +++ b/packages/twenty-front/src/modules/settings/roles/hooks/usePermissionFlagMap.ts @@ -1,10 +1,10 @@ import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState'; -import { useRecoilValue } from 'recoil'; import { PermissionFlagType } from '~/generated-metadata/graphql'; import { buildRecordFromKeysWithSameValue } from '~/utils/array/buildRecordFromKeysWithSameValue'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; export const usePermissionFlagMap = (): Record => { - const currentUserWorkspace = useRecoilValue(currentUserWorkspaceState); + const currentUserWorkspace = useRecoilValueV2(currentUserWorkspaceState); const currentUserWorkspaceSettingsPermissions = currentUserWorkspace?.permissionFlags; diff --git a/packages/twenty-front/src/modules/settings/roles/role-assignment/components/SettingsRoleAssignment.tsx b/packages/twenty-front/src/modules/settings/roles/role-assignment/components/SettingsRoleAssignment.tsx index 2372fb46de..a0de2a010a 100644 --- a/packages/twenty-front/src/modules/settings/roles/role-assignment/components/SettingsRoleAssignment.tsx +++ b/packages/twenty-front/src/modules/settings/roles/role-assignment/components/SettingsRoleAssignment.tsx @@ -14,6 +14,7 @@ import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useR import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { useState } from 'react'; import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { SettingsPath } from 'twenty-shared/types'; import { useFindManyAgentsQuery, @@ -64,8 +65,10 @@ export const SettingsRoleAssignment = ({ null, ); - const currentWorkspaceMembers = useRecoilValue(currentWorkspaceMembersState); - const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); + const currentWorkspaceMembers = useRecoilValueV2( + currentWorkspaceMembersState, + ); + const currentWorkspaceMember = useRecoilValueV2(currentWorkspaceMemberState); const settingsAllRoles = useRecoilValue(settingsAllRolesSelector); const roleMaps = buildRoleMaps(settingsAllRoles); diff --git a/packages/twenty-front/src/modules/settings/roles/role-assignment/components/SettingsRoleAssignmentConfirmationModalSubtitle.tsx b/packages/twenty-front/src/modules/settings/roles/role-assignment/components/SettingsRoleAssignmentConfirmationModalSubtitle.tsx index 235bc54ca8..9bdabdac95 100644 --- a/packages/twenty-front/src/modules/settings/roles/role-assignment/components/SettingsRoleAssignmentConfirmationModalSubtitle.tsx +++ b/packages/twenty-front/src/modules/settings/roles/role-assignment/components/SettingsRoleAssignmentConfirmationModalSubtitle.tsx @@ -4,8 +4,8 @@ import { type SettingsRoleAssignmentConfirmationModalSelectedRoleTarget } from ' import styled from '@emotion/styled'; import { t } from '@lingui/core/macro'; -import { useRecoilValue } from 'recoil'; import { Avatar } from 'twenty-ui/display'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; const StyledSettingsCardContainer = styled.div` margin-top: ${({ theme }) => theme.spacing(6)}; @@ -20,7 +20,9 @@ export const SettingsRoleAssignmentConfirmationModalSubtitle = ({ selectedRoleTarget, onRoleClick, }: SettingsRoleAssignmentConfirmationModalSubtitleProps) => { - const currentWorkspaceMembers = useRecoilValue(currentWorkspaceMembersState); + const currentWorkspaceMembers = useRecoilValueV2( + currentWorkspaceMembersState, + ); const enrichedSelectedWorkspaceMember = currentWorkspaceMembers.find( (member) => member.id === selectedRoleTarget.id, diff --git a/packages/twenty-front/src/modules/settings/roles/role-assignment/components/SettingsRoleAssignmentTableRow.tsx b/packages/twenty-front/src/modules/settings/roles/role-assignment/components/SettingsRoleAssignmentTableRow.tsx index 50c14797b4..3e28fd8d4a 100644 --- a/packages/twenty-front/src/modules/settings/roles/role-assignment/components/SettingsRoleAssignmentTableRow.tsx +++ b/packages/twenty-front/src/modules/settings/roles/role-assignment/components/SettingsRoleAssignmentTableRow.tsx @@ -7,6 +7,7 @@ import { t } from '@lingui/core/macro'; import styled from '@emotion/styled'; import { useContext } from 'react'; import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { Avatar, IconKey, @@ -55,7 +56,9 @@ export const SettingsRoleAssignmentTableRow = ({ roleTarget, }: SettingsRoleAssignmentTableRowProps) => { const theme = useTheme(); - const currentWorkspaceMembers = useRecoilValue(currentWorkspaceMembersState); + const currentWorkspaceMembers = useRecoilValueV2( + currentWorkspaceMembersState, + ); const { getIcon } = useIcons(); const { dateFormat, timeZone } = useContext(UserContext); const dateLocale = useRecoilValue(dateLocaleState); diff --git a/packages/twenty-front/src/modules/settings/roles/role-assignment/components/SettingsRoleAssignmentWorkspaceMemberPickerDropdownContent.tsx b/packages/twenty-front/src/modules/settings/roles/role-assignment/components/SettingsRoleAssignmentWorkspaceMemberPickerDropdownContent.tsx index fa5ee4d4fa..1e9b8b5402 100644 --- a/packages/twenty-front/src/modules/settings/roles/role-assignment/components/SettingsRoleAssignmentWorkspaceMemberPickerDropdownContent.tsx +++ b/packages/twenty-front/src/modules/settings/roles/role-assignment/components/SettingsRoleAssignmentWorkspaceMemberPickerDropdownContent.tsx @@ -1,10 +1,10 @@ import { currentWorkspaceMembersState } from '@/auth/states/currentWorkspaceMembersState'; import { t } from '@lingui/core/macro'; -import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { MenuItem, MenuItemAvatar } from 'twenty-ui/navigation'; import { type SearchRecord } from '~/generated/graphql'; import { type PartialWorkspaceMember } from '@/settings/roles/types/RoleWithPartialMembers'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; type SettingsRoleAssignmentWorkspaceMemberPickerDropdownContentProps = { loading: boolean; @@ -19,7 +19,9 @@ export const SettingsRoleAssignmentWorkspaceMemberPickerDropdownContent = ({ filteredWorkspaceMembers, onSelect, }: SettingsRoleAssignmentWorkspaceMemberPickerDropdownContentProps) => { - const currentWorkspaceMembers = useRecoilValue(currentWorkspaceMembersState); + const currentWorkspaceMembers = useRecoilValueV2( + currentWorkspaceMembersState, + ); if (loading) { return null; diff --git a/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/object-form/components/SettingsRolePermissionsObjectLevelObjectForm.tsx b/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/object-form/components/SettingsRolePermissionsObjectLevelObjectForm.tsx index 360dbd01a6..632eebcdc6 100644 --- a/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/object-form/components/SettingsRolePermissionsObjectLevelObjectForm.tsx +++ b/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/object-form/components/SettingsRolePermissionsObjectLevelObjectForm.tsx @@ -11,6 +11,7 @@ import { useFeatureFlagsMap } from '@/workspace/hooks/useFeatureFlagsMap'; import { t } from '@lingui/core/macro'; import { useSearchParams } from 'react-router-dom'; import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { SettingsPath, type ViewFilterOperand } from 'twenty-shared/types'; import { getSettingsPath, isDefined } from 'twenty-shared/utils'; import { Button } from 'twenty-ui/input'; @@ -33,7 +34,7 @@ export const SettingsRolePermissionsObjectLevelObjectForm = ({ const [searchParams] = useSearchParams(); const fromAgentId = searchParams.get('fromAgent'); - const currentWorkspace = useRecoilValue(currentWorkspaceState); + const currentWorkspace = useRecoilValueV2(currentWorkspaceState); const settingsDraftRole = useRecoilValue( settingsDraftRoleFamilyState(roleId), diff --git a/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/record-level-permissions/components/SettingsRolePermissionsObjectLevelRecordLevelSection.tsx b/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/record-level-permissions/components/SettingsRolePermissionsObjectLevelRecordLevelSection.tsx index 88e9e70224..1c858e903c 100644 --- a/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/record-level-permissions/components/SettingsRolePermissionsObjectLevelRecordLevelSection.tsx +++ b/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/record-level-permissions/components/SettingsRolePermissionsObjectLevelRecordLevelSection.tsx @@ -10,7 +10,7 @@ import { billingState } from '@/client-config/states/billingState'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { SettingsOptionCardContentButton } from '@/settings/components/SettingsOptions/SettingsOptionCardContentButton'; import { SettingsRolePermissionsObjectLevelRecordLevelPermissionFilterBuilder } from '@/settings/roles/role-permissions/object-level-permissions/record-level-permissions/components/SettingsRolePermissionsObjectLevelRecordLevelPermissionFilterBuilder'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { SettingsPath } from 'twenty-shared/types'; import { Button } from 'twenty-ui/input'; import { useNavigateSettings } from '~/hooks/useNavigateSettings'; @@ -46,7 +46,7 @@ export const SettingsRolePermissionsObjectLevelRecordLevelSection = ({ hasOrganizationPlan, }: SettingsRolePermissionsObjectLevelRecordLevelSectionProps) => { const navigateSettings = useNavigateSettings(); - const billing = useRecoilValue(billingState); + const billing = useRecoilValueV2(billingState); const isBillingEnabled = billing?.isBillingEnabled ?? false; if (!hasOrganizationPlan) { diff --git a/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/record-level-permissions/hooks/useRecordLevelPermissionFilterActions.ts b/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/record-level-permissions/hooks/useRecordLevelPermissionFilterActions.ts index 833a31370b..7ac9449ed1 100644 --- a/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/record-level-permissions/hooks/useRecordLevelPermissionFilterActions.ts +++ b/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/record-level-permissions/hooks/useRecordLevelPermissionFilterActions.ts @@ -1,6 +1,5 @@ /* @license Enterprise */ -import { useRecoilValue } from 'recoil'; import { FieldMetadataType, RecordFilterGroupLogicalOperator, @@ -25,6 +24,7 @@ import { getDefaultSubFieldNameForCompositeFilterableFieldType } from '@/object- import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands'; import { RECORD_LEVEL_PERMISSION_PREDICATE_FIELD_TYPES } from '@/settings/roles/role-permissions/object-level-permissions/record-level-permissions/constants/RecordLevelPermissionPredicateFieldTypes'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; +import { useFamilySelectorValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilySelectorValueV2'; type UseRecordLevelPermissionFilterActionsProps = { objectMetadataItem: ObjectMetadataItem; @@ -42,10 +42,11 @@ export const useRecordLevelPermissionFilterActions = ({ const { setRecordFilterUsedInAdvancedFilterDropdownRow } = useSetRecordFilterUsedInAdvancedFilterDropdownRow(); - const availableFieldMetadataItemsForFilter = useRecoilValue( - availableFieldMetadataItemsForFilterFamilySelector({ + const availableFieldMetadataItemsForFilter = useFamilySelectorValueV2( + availableFieldMetadataItemsForFilterFamilySelector, + { objectMetadataItemId: objectMetadataItem.id, - }), + }, ); const rootRecordFilterGroup = useRecoilComponentValue( diff --git a/packages/twenty-front/src/modules/settings/security/components/SSO/SettingsSSOIdentitiesProvidersListCard.tsx b/packages/twenty-front/src/modules/settings/security/components/SSO/SettingsSSOIdentitiesProvidersListCard.tsx index 74efe45e55..c565971537 100644 --- a/packages/twenty-front/src/modules/settings/security/components/SSO/SettingsSSOIdentitiesProvidersListCard.tsx +++ b/packages/twenty-front/src/modules/settings/security/components/SSO/SettingsSSOIdentitiesProvidersListCard.tsx @@ -13,7 +13,8 @@ import { type ApolloError } from '@apollo/client'; import isPropValid from '@emotion/is-prop-valid'; import styled from '@emotion/styled'; import { useLingui } from '@lingui/react/macro'; -import { useRecoilState, useRecoilValue } from 'recoil'; +import { useRecoilState } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { getSettingsPath } from 'twenty-shared/utils'; import { IconKey } from 'twenty-ui/display'; import { useGetSsoIdentityProvidersQuery } from '~/generated-metadata/graphql'; @@ -28,7 +29,7 @@ const StyledLink = styled(Link, { export const SettingsSSOIdentitiesProvidersListCard = () => { const { enqueueErrorSnackBar } = useSnackBar(); - const currentWorkspace = useRecoilValue(currentWorkspaceState); + const currentWorkspace = useRecoilValueV2(currentWorkspaceState); const { t } = useLingui(); diff --git a/packages/twenty-front/src/modules/settings/security/components/SettingsSecurityAuthBypassOptionsList.tsx b/packages/twenty-front/src/modules/settings/security/components/SettingsSecurityAuthBypassOptionsList.tsx index 80002f8f2f..b12bd2f169 100644 --- a/packages/twenty-front/src/modules/settings/security/components/SettingsSecurityAuthBypassOptionsList.tsx +++ b/packages/twenty-front/src/modules/settings/security/components/SettingsSecurityAuthBypassOptionsList.tsx @@ -5,7 +5,6 @@ import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { ApolloError } from '@apollo/client'; import styled from '@emotion/styled'; import { useLingui } from '@lingui/react/macro'; -import { useRecoilState } from 'recoil'; import { capitalize } from 'twenty-shared/utils'; import { IconGoogle, IconMicrosoft, IconPassword } from 'twenty-ui/display'; import { Card } from 'twenty-ui/layout'; @@ -14,6 +13,7 @@ import { useUpdateWorkspaceMutation, } from '~/generated-metadata/graphql'; import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; +import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2'; const StyledSettingsSecurityOptionsList = styled.div` display: flex; @@ -27,7 +27,7 @@ export const SettingsSecurityAuthBypassOptionsList = () => { const { enqueueErrorSnackBar } = useSnackBar(); const authProviders = useRecoilValueV2(authProvidersState); - const [currentWorkspace, setCurrentWorkspace] = useRecoilState( + const [currentWorkspace, setCurrentWorkspace] = useRecoilStateV2( currentWorkspaceState, ); diff --git a/packages/twenty-front/src/modules/settings/security/components/SettingsSecurityAuthProvidersOptionsList.tsx b/packages/twenty-front/src/modules/settings/security/components/SettingsSecurityAuthProvidersOptionsList.tsx index ba6a70a8e4..cffa968126 100644 --- a/packages/twenty-front/src/modules/settings/security/components/SettingsSecurityAuthProvidersOptionsList.tsx +++ b/packages/twenty-front/src/modules/settings/security/components/SettingsSecurityAuthProvidersOptionsList.tsx @@ -6,7 +6,8 @@ import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { ApolloError } from '@apollo/client'; import styled from '@emotion/styled'; import { useLingui } from '@lingui/react/macro'; -import { useRecoilState, useRecoilValue } from 'recoil'; +import { useRecoilValue } from 'recoil'; +import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2'; import { ConnectedAccountProvider } from 'twenty-shared/types'; import { capitalize } from 'twenty-shared/utils'; import { @@ -37,7 +38,7 @@ export const SettingsSecurityAuthProvidersOptionsList = () => { const SSOIdentitiesProviders = useRecoilValue(SSOIdentitiesProvidersState); const authProviders = useRecoilValueV2(authProvidersState); - const [currentWorkspace, setCurrentWorkspace] = useRecoilState( + const [currentWorkspace, setCurrentWorkspace] = useRecoilStateV2( currentWorkspaceState, ); diff --git a/packages/twenty-front/src/modules/settings/security/components/SettingsSecurityEditableProfileFields.tsx b/packages/twenty-front/src/modules/settings/security/components/SettingsSecurityEditableProfileFields.tsx index d9f1dbf5ce..f590374606 100644 --- a/packages/twenty-front/src/modules/settings/security/components/SettingsSecurityEditableProfileFields.tsx +++ b/packages/twenty-front/src/modules/settings/security/components/SettingsSecurityEditableProfileFields.tsx @@ -8,7 +8,7 @@ import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/Drop import { ApolloError } from '@apollo/client'; import styled from '@emotion/styled'; import { useLingui } from '@lingui/react/macro'; -import { useRecoilState } from 'recoil'; +import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2'; import { isDefined } from 'twenty-shared/utils'; import { IconMail, @@ -37,7 +37,7 @@ export const SettingsSecurityEditableProfileFields = () => { const { t } = useLingui(); const { enqueueErrorSnackBar } = useSnackBar(); - const [currentWorkspace, setCurrentWorkspace] = useRecoilState( + const [currentWorkspace, setCurrentWorkspace] = useRecoilStateV2( currentWorkspaceState, ); const [updateWorkspace] = useUpdateWorkspaceMutation(); diff --git a/packages/twenty-front/src/modules/settings/security/components/Toggle2FA.tsx b/packages/twenty-front/src/modules/settings/security/components/Toggle2FA.tsx index d633cb4d45..7afd69ca33 100644 --- a/packages/twenty-front/src/modules/settings/security/components/Toggle2FA.tsx +++ b/packages/twenty-front/src/modules/settings/security/components/Toggle2FA.tsx @@ -1,6 +1,5 @@ -import { useRecoilState } from 'recoil'; - import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; +import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2'; import { SettingsOptionCardContentToggle } from '@/settings/components/SettingsOptions/SettingsOptionCardContentToggle'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { ApolloError } from '@apollo/client'; @@ -10,7 +9,7 @@ import { useUpdateWorkspaceMutation } from '~/generated-metadata/graphql'; export const Toggle2FA = () => { const { enqueueErrorSnackBar } = useSnackBar(); - const [currentWorkspace, setCurrentWorkspace] = useRecoilState( + const [currentWorkspace, setCurrentWorkspace] = useRecoilStateV2( currentWorkspaceState, ); diff --git a/packages/twenty-front/src/modules/settings/two-factor-authentication/components/DeleteTwoFactorAuthenticationMethod.tsx b/packages/twenty-front/src/modules/settings/two-factor-authentication/components/DeleteTwoFactorAuthenticationMethod.tsx index b144e1fb7f..daf4220dea 100644 --- a/packages/twenty-front/src/modules/settings/two-factor-authentication/components/DeleteTwoFactorAuthenticationMethod.tsx +++ b/packages/twenty-front/src/modules/settings/two-factor-authentication/components/DeleteTwoFactorAuthenticationMethod.tsx @@ -1,7 +1,6 @@ -import { useRecoilValue } from 'recoil'; - import { useAuth } from '@/auth/hooks/useAuth'; import { currentUserState } from '@/auth/states/currentUserState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal'; import { useModal } from '@/ui/layout/modal/hooks/useModal'; @@ -28,7 +27,7 @@ export const DeleteTwoFactorAuthentication = () => { const { loadCurrentUser } = useLoadCurrentUser(); const [deleteTwoFactorAuthenticationMethod] = useDeleteTwoFactorAuthenticationMethodMutation(); - const currentUser = useRecoilValue(currentUserState); + const currentUser = useRecoilValueV2(currentUserState); const userEmail = currentUser?.email; const navigate = useNavigateSettings(); const twoFactorAuthenticationStrategy = diff --git a/packages/twenty-front/src/modules/settings/two-factor-authentication/components/TwoFactorAuthenticationSetupForSettingsEffect.tsx b/packages/twenty-front/src/modules/settings/two-factor-authentication/components/TwoFactorAuthenticationSetupForSettingsEffect.tsx index a96ad41d4a..9bd441875e 100644 --- a/packages/twenty-front/src/modules/settings/two-factor-authentication/components/TwoFactorAuthenticationSetupForSettingsEffect.tsx +++ b/packages/twenty-front/src/modules/settings/two-factor-authentication/components/TwoFactorAuthenticationSetupForSettingsEffect.tsx @@ -4,9 +4,9 @@ import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { gql, useMutation } from '@apollo/client'; import { useLingui } from '@lingui/react/macro'; import { useEffect } from 'react'; -import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; const INITIATE_OTP_PROVISIONING_FOR_AUTHENTICATED_USER = gql` mutation initiateOTPProvisioningForAuthenticatedUser { @@ -19,7 +19,7 @@ const INITIATE_OTP_PROVISIONING_FOR_AUTHENTICATED_USER = gql` export const TwoFactorAuthenticationSetupForSettingsEffect = () => { const { enqueueErrorSnackBar } = useSnackBar(); const [qrCode, setQrCode] = useRecoilStateV2(qrCodeState); - const currentUser = useRecoilValue(currentUserState); + const currentUser = useRecoilValueV2(currentUserState); const { t } = useLingui(); diff --git a/packages/twenty-front/src/modules/settings/two-factor-authentication/hooks/useCurrentUserWorkspaceTwoFactorAuthentication.ts b/packages/twenty-front/src/modules/settings/two-factor-authentication/hooks/useCurrentUserWorkspaceTwoFactorAuthentication.ts index bf7b5c9363..ce01679283 100644 --- a/packages/twenty-front/src/modules/settings/two-factor-authentication/hooks/useCurrentUserWorkspaceTwoFactorAuthentication.ts +++ b/packages/twenty-front/src/modules/settings/two-factor-authentication/hooks/useCurrentUserWorkspaceTwoFactorAuthentication.ts @@ -1,13 +1,13 @@ import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState'; import { useMemo } from 'react'; -import { useRecoilValue } from 'recoil'; import { type TwoFactorAuthenticationMethodDto, useInitiateOtpProvisioningMutation, } from '~/generated-metadata/graphql'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; export const useCurrentUserWorkspaceTwoFactorAuthentication = () => { - const currentUserWorkspace = useRecoilValue(currentUserWorkspaceState); + const currentUserWorkspace = useRecoilValueV2(currentUserWorkspaceState); const [initiateCurrentUserWorkspaceOtpProvisioning] = useInitiateOtpProvisioningMutation(); diff --git a/packages/twenty-front/src/modules/settings/two-factor-authentication/hooks/useWorkspaceTwoFactorAuthenticationPolicy.ts b/packages/twenty-front/src/modules/settings/two-factor-authentication/hooks/useWorkspaceTwoFactorAuthenticationPolicy.ts index 8e79c2233b..858753198f 100644 --- a/packages/twenty-front/src/modules/settings/two-factor-authentication/hooks/useWorkspaceTwoFactorAuthenticationPolicy.ts +++ b/packages/twenty-front/src/modules/settings/two-factor-authentication/hooks/useWorkspaceTwoFactorAuthenticationPolicy.ts @@ -1,8 +1,8 @@ import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; export const useCurrentWorkspaceTwoFactorAuthenticationPolicy = () => { - const currentWorkspace = useRecoilValue(currentWorkspaceState); + const currentWorkspace = useRecoilValueV2(currentWorkspaceState); return { isEnforced: currentWorkspace?.isTwoFactorAuthenticationEnforced ?? false, diff --git a/packages/twenty-front/src/modules/settings/workspace-member/components/WorkspaceMemberPictureUploader.tsx b/packages/twenty-front/src/modules/settings/workspace-member/components/WorkspaceMemberPictureUploader.tsx index 1da381f3ed..82f489d0c9 100644 --- a/packages/twenty-front/src/modules/settings/workspace-member/components/WorkspaceMemberPictureUploader.tsx +++ b/packages/twenty-front/src/modules/settings/workspace-member/components/WorkspaceMemberPictureUploader.tsx @@ -1,8 +1,8 @@ import { t } from '@lingui/core/macro'; import { useState } from 'react'; -import { useRecoilState } from 'recoil'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; +import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; import { useCanEditProfileField } from '@/settings/profile/hooks/useCanEditProfileField'; @@ -39,7 +39,7 @@ export const WorkspaceMemberPictureUploader = ({ const [uploadController, setUploadController] = useState(null); - const [currentWorkspaceMember, setCurrentWorkspaceMember] = useRecoilState( + const [currentWorkspaceMember, setCurrentWorkspaceMember] = useRecoilStateV2( currentWorkspaceMemberState, ); diff --git a/packages/twenty-front/src/modules/settings/workspace/components/NameField.tsx b/packages/twenty-front/src/modules/settings/workspace/components/NameField.tsx index bbe0c34016..50df604016 100644 --- a/packages/twenty-front/src/modules/settings/workspace/components/NameField.tsx +++ b/packages/twenty-front/src/modules/settings/workspace/components/NameField.tsx @@ -1,9 +1,10 @@ import styled from '@emotion/styled'; import { useCallback, useEffect, useState } from 'react'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; import { useDebouncedCallback } from 'use-debounce'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; +import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput'; import { useLingui } from '@lingui/react/macro'; import isEmpty from 'lodash.isempty'; @@ -30,8 +31,8 @@ export const NameField = ({ onNameUpdate, }: NameFieldProps) => { const { t } = useLingui(); - const currentWorkspace = useRecoilValue(currentWorkspaceState); - const setCurrentWorkspace = useSetRecoilState(currentWorkspaceState); + const currentWorkspace = useRecoilValueV2(currentWorkspaceState); + const setCurrentWorkspace = useSetRecoilStateV2(currentWorkspaceState); const [displayName, setDisplayName] = useState( currentWorkspace?.displayName ?? '', diff --git a/packages/twenty-front/src/modules/settings/workspace/components/ToggleImpersonate.tsx b/packages/twenty-front/src/modules/settings/workspace/components/ToggleImpersonate.tsx index f90113ae75..b0cd5b9c03 100644 --- a/packages/twenty-front/src/modules/settings/workspace/components/ToggleImpersonate.tsx +++ b/packages/twenty-front/src/modules/settings/workspace/components/ToggleImpersonate.tsx @@ -1,6 +1,5 @@ -import { useRecoilState } from 'recoil'; - import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; +import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2'; import { SettingsOptionCardContentToggle } from '@/settings/components/SettingsOptions/SettingsOptionCardContentToggle'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { ApolloError } from '@apollo/client'; @@ -12,7 +11,7 @@ import { useUpdateWorkspaceMutation } from '~/generated-metadata/graphql'; export const ToggleImpersonate = () => { const { enqueueErrorSnackBar } = useSnackBar(); - const [currentWorkspace, setCurrentWorkspace] = useRecoilState( + const [currentWorkspace, setCurrentWorkspace] = useRecoilStateV2( currentWorkspaceState, ); diff --git a/packages/twenty-front/src/modules/settings/workspace/components/WorkspaceLogoUploader.tsx b/packages/twenty-front/src/modules/settings/workspace/components/WorkspaceLogoUploader.tsx index 9d4f025218..11a4a016cd 100644 --- a/packages/twenty-front/src/modules/settings/workspace/components/WorkspaceLogoUploader.tsx +++ b/packages/twenty-front/src/modules/settings/workspace/components/WorkspaceLogoUploader.tsx @@ -1,6 +1,5 @@ -import { useRecoilState } from 'recoil'; - import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; +import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2'; import { ImageInput } from '@/ui/input/components/ImageInput'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { buildSignedPath } from 'twenty-shared/utils'; @@ -19,7 +18,7 @@ export const WorkspaceLogoUploader = () => { const [uploadLogoLegacy] = useUploadWorkspaceLogoLegacyMutation(); const [uploadLogo] = useUploadWorkspaceLogoMutation(); const [updateWorkspace] = useUpdateWorkspaceMutation(); - const [currentWorkspace, setCurrentWorkspace] = useRecoilState( + const [currentWorkspace, setCurrentWorkspace] = useRecoilStateV2( currentWorkspaceState, ); diff --git a/packages/twenty-front/src/modules/sign-in-background-mock/components/SignInAppNavigationDrawerMock.tsx b/packages/twenty-front/src/modules/sign-in-background-mock/components/SignInAppNavigationDrawerMock.tsx index 6ecee70f4f..d17d07158b 100644 --- a/packages/twenty-front/src/modules/sign-in-background-mock/components/SignInAppNavigationDrawerMock.tsx +++ b/packages/twenty-front/src/modules/sign-in-background-mock/components/SignInAppNavigationDrawerMock.tsx @@ -5,9 +5,9 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadat import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem'; import { NavigationDrawerSection } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSection'; import { DEFAULT_WORKSPACE_NAME } from '@/ui/navigation/navigation-drawer/constants/DefaultWorkspaceName'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import styled from '@emotion/styled'; import { useLingui } from '@lingui/react/macro'; -import { useRecoilValue } from 'recoil'; import { SettingsPath } from 'twenty-shared/types'; import { getSettingsPath } from 'twenty-shared/utils'; import { IconSearch, IconSettings } from 'twenty-ui/display'; @@ -34,7 +34,7 @@ export const SignInAppNavigationDrawerMock = ({ }: SignInAppNavigationDrawerMockProps) => { const isMobile = useIsMobile(); const { t } = useLingui(); - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); return ( diff --git a/packages/twenty-front/src/modules/sign-in-background-mock/components/SignInBackgroundMockContainer.tsx b/packages/twenty-front/src/modules/sign-in-background-mock/components/SignInBackgroundMockContainer.tsx index 469db706af..9515a4ab4f 100644 --- a/packages/twenty-front/src/modules/sign-in-background-mock/components/SignInBackgroundMockContainer.tsx +++ b/packages/twenty-front/src/modules/sign-in-background-mock/components/SignInBackgroundMockContainer.tsx @@ -13,7 +13,7 @@ import { SignInBackgroundMockContainerEffect } from '@/sign-in-background-mock/c import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { ViewBar } from '@/views/components/ViewBar'; import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { isDefined } from 'twenty-shared/utils'; const StyledContainer = styled.div` @@ -29,7 +29,7 @@ export const SignInBackgroundMockContainer = () => { const recordIndexId = 'sign-up-mock-record-table-id'; const viewBarId = 'companies-mock'; - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); const objectMetadataItemId = useRecoilComponentValue( contextStoreCurrentObjectMetadataItemIdComponentState, diff --git a/packages/twenty-front/src/modules/sse-db-event/components/SSEClientEffect.tsx b/packages/twenty-front/src/modules/sse-db-event/components/SSEClientEffect.tsx index e4318f1ef0..f08000c6bd 100644 --- a/packages/twenty-front/src/modules/sse-db-event/components/SSEClientEffect.tsx +++ b/packages/twenty-front/src/modules/sse-db-event/components/SSEClientEffect.tsx @@ -3,6 +3,7 @@ import { tokenPairState } from '@/auth/states/tokenPairState'; import { useHandleSseClientConnectionRetry } from '@/sse-db-event/hooks/useHandleSseClientConnectionRetry'; import { activeQueryListenersState } from '@/sse-db-event/states/activeQueryListenersState'; import { sseClientState } from '@/sse-db-event/states/sseClientState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; import { isNonEmptyArray } from '@sniptt/guards'; import { createClient } from 'graphql-sse'; @@ -14,7 +15,7 @@ import { REACT_APP_SERVER_BASE_URL } from '~/config'; export const SSEClientEffect = () => { const isLoggedIn = useIsLogged(); const [sseClient, setSseClient] = useRecoilState(sseClientState); - const [tokenPair] = useRecoilState(tokenPairState); + const tokenPair = useRecoilValueV2(tokenPairState); const handleSSEClientConnected = useRecoilCallback( ({ snapshot, set }) => diff --git a/packages/twenty-front/src/modules/sse-db-event/components/SSEEventStreamEffect.tsx b/packages/twenty-front/src/modules/sse-db-event/components/SSEEventStreamEffect.tsx index 38dd0eada0..ce8995ccb1 100644 --- a/packages/twenty-front/src/modules/sse-db-event/components/SSEEventStreamEffect.tsx +++ b/packages/twenty-front/src/modules/sse-db-event/components/SSEEventStreamEffect.tsx @@ -7,6 +7,7 @@ import { isCreatingSseEventStreamState } from '@/sse-db-event/states/isCreatingS import { isDestroyingEventStreamState } from '@/sse-db-event/states/isDestroyingEventStreamState'; import { shouldDestroyEventStreamState } from '@/sse-db-event/states/shouldDestroyEventStreamState'; import { sseEventStreamIdState } from '@/sse-db-event/states/sseEventStreamIdState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { isNonEmptyArray } from '@apollo/client/utilities'; import { isNonEmptyString } from '@sniptt/guards'; import { useEffect } from 'react'; @@ -15,7 +16,7 @@ import { isDefined } from 'twenty-shared/utils'; import { OnboardingStatus } from '~/generated-metadata/graphql'; export const SSEEventStreamEffect = () => { - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); const sseEventStreamId = useRecoilValue(sseEventStreamIdState); const isCreatingSseEventStream = useRecoilValue( @@ -27,7 +28,7 @@ export const SSEEventStreamEffect = () => { const isDestroyingEventStream = useRecoilValue(isDestroyingEventStreamState); const isLoggedIn = useIsLogged(); - const currentUser = useRecoilValue(currentUserState); + const currentUser = useRecoilValueV2(currentUserState); const { triggerEventStreamCreation } = useTriggerEventStreamCreation(); const { triggerEventStreamDestroy } = useTriggerEventStreamDestroy(); diff --git a/packages/twenty-front/src/modules/sse-db-event/hooks/useHandleSseClientConnectionRetry.ts b/packages/twenty-front/src/modules/sse-db-event/hooks/useHandleSseClientConnectionRetry.ts index 664d7337a3..8ab10614ea 100644 --- a/packages/twenty-front/src/modules/sse-db-event/hooks/useHandleSseClientConnectionRetry.ts +++ b/packages/twenty-front/src/modules/sse-db-event/hooks/useHandleSseClientConnectionRetry.ts @@ -6,11 +6,14 @@ import { shouldDestroyEventStreamState } from '@/sse-db-event/states/shouldDestr import { sseClientState } from '@/sse-db-event/states/sseClientState'; import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; import { useRecoilCallback } from 'recoil'; +import { useStore } from 'jotai'; import { isDefined } from 'twenty-shared/utils'; import { getIsDevelopmentEnvironment } from '~/utils/getIsDevelopmentEnvironment'; import { sleep } from '~/utils/sleep'; export const useHandleSseClientConnectionRetry = () => { + const jotaiStore = useStore(); + const handleSseClientConnectionRetry = useRecoilCallback( ({ snapshot, set }) => async (retryCount: number, initialTokenForSseClient: string) => { @@ -24,7 +27,7 @@ export const useHandleSseClientConnectionRetry = () => { return; } - const tokenPair = getSnapshotValue(snapshot, tokenPairState); + const tokenPair = jotaiStore.get(tokenPairState.atom); const currentAppToken = tokenPair?.accessOrWorkspaceAgnosticToken?.token; @@ -56,7 +59,7 @@ export const useHandleSseClientConnectionRetry = () => { await sleep(waitTimeInMs); }, - [], + [jotaiStore], ); return { handleSseClientConnectionRetry }; diff --git a/packages/twenty-front/src/modules/sse-db-event/hooks/useListenToEventsForQuery.ts b/packages/twenty-front/src/modules/sse-db-event/hooks/useListenToEventsForQuery.ts index a50a148181..c8f5d74ebf 100644 --- a/packages/twenty-front/src/modules/sse-db-event/hooks/useListenToEventsForQuery.ts +++ b/packages/twenty-front/src/modules/sse-db-event/hooks/useListenToEventsForQuery.ts @@ -18,7 +18,13 @@ export const useListenToEventsForQuery = ({ }) => { const changeQueryIdListenState = useRecoilCallback( ({ set, snapshot }) => - (shouldListen: boolean, queryId: string) => { + ( + shouldListen: boolean, + targetQueryId: string, + targetOperationSignature: + | RecordGqlOperationSignature + | MetadataGqlOperationSignature, + ) => { const currentRequiredQueryListeners = getSnapshotValue( snapshot, requiredQueryListenersState, @@ -26,7 +32,7 @@ export const useListenToEventsForQuery = ({ const listeningForThisQueryIsActive = currentRequiredQueryListeners.some( - (listener) => listener.queryId === queryId, + (listener) => listener.queryId === targetQueryId, ); if (shouldListen === listeningForThisQueryIsActive) { @@ -36,25 +42,28 @@ export const useListenToEventsForQuery = ({ if (shouldListen) { set(requiredQueryListenersState, [ ...currentRequiredQueryListeners, - { queryId, operationSignature }, + { + queryId: targetQueryId, + operationSignature: targetOperationSignature, + }, ]); } else { set( requiredQueryListenersState, currentRequiredQueryListeners.filter( - (listener) => listener.queryId !== queryId, + (listener) => listener.queryId !== targetQueryId, ), ); } }, - [operationSignature], + [], ); useEffect(() => { - changeQueryIdListenState(true, queryId); + changeQueryIdListenState(true, queryId, operationSignature); return () => { - changeQueryIdListenState(false, queryId); + changeQueryIdListenState(false, queryId, operationSignature); }; - }, [changeQueryIdListenState, queryId]); + }, [changeQueryIdListenState, queryId, operationSignature]); }; diff --git a/packages/twenty-front/src/modules/support/hooks/useInstantiateSupportChat.ts b/packages/twenty-front/src/modules/support/hooks/useInstantiateSupportChat.ts index cc1e2c63a2..15ac0c168c 100644 --- a/packages/twenty-front/src/modules/support/hooks/useInstantiateSupportChat.ts +++ b/packages/twenty-front/src/modules/support/hooks/useInstantiateSupportChat.ts @@ -2,9 +2,9 @@ import { currentUserState } from '@/auth/states/currentUserState'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { supportChatState } from '@/client-config/states/supportChatState'; import { useIsPrefetchLoading } from '@/prefetch/hooks/useIsPrefetchLoading'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { isNonEmptyString } from '@sniptt/guards'; import { useCallback, useEffect, useState } from 'react'; -import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { type User, type WorkspaceMember } from '~/generated-metadata/graphql'; @@ -28,9 +28,9 @@ const insertScript = ({ }; export const useInstantiateSupportChat = () => { - const currentUser = useRecoilValue(currentUserState); - const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); - const supportChat = useRecoilValue(supportChatState); + const currentUser = useRecoilValueV2(currentUserState); + const currentWorkspaceMember = useRecoilValueV2(currentWorkspaceMemberState); + const supportChat = useRecoilValueV2(supportChatState); const [isFrontChatLoaded, setIsFrontChatLoaded] = useState(false); const loading = useIsPrefetchLoading(); diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/DatePicker.tsx b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/DatePicker.tsx index 306bfdb02d..c78987c38a 100644 --- a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/DatePicker.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/DatePicker.tsx @@ -15,9 +15,9 @@ import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown'; import { useTheme } from '@emotion/react'; import { t } from '@lingui/core/macro'; import 'react-datepicker/dist/react-datepicker.css'; -import { useRecoilValue } from 'recoil'; import { useUserTimezone } from '@/ui/input/components/internal/date/hooks/useUserTimezone'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { Temporal } from 'temporal-polyfill'; import { type Nullable } from 'twenty-shared/types'; import { @@ -356,7 +356,7 @@ export const DatePicker = ({ const { closeDropdown: closeDropdownMonthSelect } = useCloseDropdown(); const { closeDropdown: closeDropdownYearSelect } = useCloseDropdown(); - const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); + const currentWorkspaceMember = useRecoilValueV2(currentWorkspaceMemberState); const handleClear = () => { closeDropdowns(); onClear?.(); diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/DatePickerHeader.tsx b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/DatePickerHeader.tsx index 0b927d1f0c..b0fc6b5057 100644 --- a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/DatePickerHeader.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/DatePickerHeader.tsx @@ -13,7 +13,7 @@ import { } from './DateTimePicker'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { Temporal } from 'temporal-polyfill'; import { SOURCE_LOCALE } from 'twenty-shared/translations'; import { isDefined } from 'twenty-shared/utils'; @@ -57,7 +57,7 @@ export const DatePickerHeader = ({ nextMonthButtonDisabled, hideInput = false, }: DatePickerHeaderProps) => { - const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); + const currentWorkspaceMember = useRecoilValueV2(currentWorkspaceMemberState); const userLocale = currentWorkspaceMember?.locale ?? SOURCE_LOCALE; const dateParsed = isDefined(date) ? Temporal.PlainDate.from(date) : null; diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/DatePickerWithoutCalendar.tsx b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/DatePickerWithoutCalendar.tsx index 5192bbb8be..8936590c6a 100644 --- a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/DatePickerWithoutCalendar.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/DatePickerWithoutCalendar.tsx @@ -12,8 +12,8 @@ import { DatePickerHeader } from '@/ui/input/components/internal/date/components import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown'; import { useTheme } from '@emotion/react'; import 'react-datepicker/dist/react-datepicker.css'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { Temporal } from 'temporal-polyfill'; import { type Nullable } from 'twenty-shared/types'; import { isDefined, turnJSDateToPlainDate } from 'twenty-shared/utils'; @@ -328,7 +328,7 @@ export const DatePickerWithoutCalendar = ({ const { closeDropdown: closeDropdownMonthSelect } = useCloseDropdown(); const { closeDropdown: closeDropdownYearSelect } = useCloseDropdown(); - const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); + const currentWorkspaceMember = useRecoilValueV2(currentWorkspaceMemberState); const closeDropdowns = () => { closeDropdownYearSelect(MONTH_AND_YEAR_DROPDOWN_YEAR_SELECT_ID); diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/DateTimePickerHeader.tsx b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/DateTimePickerHeader.tsx index acbafaea1f..6500188fef 100644 --- a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/DateTimePickerHeader.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/DateTimePickerHeader.tsx @@ -1,7 +1,7 @@ import styled from '@emotion/styled'; -import { useRecoilValue } from 'recoil'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { Select } from '@/ui/input/components/Select'; import { DateTimePickerInput } from '@/ui/input/components/internal/date/components/DateTimePickerInput'; @@ -61,7 +61,7 @@ export const DateTimePickerHeader = ({ nextMonthButtonDisabled, hideInput = false, }: DateTimePickerHeaderProps) => { - const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); + const currentWorkspaceMember = useRecoilValueV2(currentWorkspaceMemberState); const userLocale = currentWorkspaceMember?.locale ?? SOURCE_LOCALE; return ( diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/date/hooks/__tests__/useParseZonedDateTimeToIMaskDateTimeInputString.test.tsx b/packages/twenty-front/src/modules/ui/input/components/internal/date/hooks/__tests__/useParseZonedDateTimeToIMaskDateTimeInputString.test.tsx index 71f03e6ec0..f641c6498d 100644 --- a/packages/twenty-front/src/modules/ui/input/components/internal/date/hooks/__tests__/useParseZonedDateTimeToIMaskDateTimeInputString.test.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/internal/date/hooks/__tests__/useParseZonedDateTimeToIMaskDateTimeInputString.test.tsx @@ -1,4 +1,5 @@ import { renderHook } from '@testing-library/react'; +import { Provider as JotaiProvider } from 'jotai'; import { RecoilRoot } from 'recoil'; import { Temporal } from 'temporal-polyfill'; @@ -6,6 +7,7 @@ import { DateFormat } from '@/localization/constants/DateFormat'; import { NumberFormat } from '@/localization/constants/NumberFormat'; import { TimeFormat } from '@/localization/constants/TimeFormat'; import { workspaceMemberFormatPreferencesState } from '@/localization/states/workspaceMemberFormatPreferencesState'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { useParseZonedDateTimeToIMaskDateTimeInputString } from '@/ui/input/components/internal/date/hooks/useParseZonedDateTimeToIMaskDateTimeInputString'; import { CalendarStartDay } from 'twenty-shared/constants'; @@ -21,21 +23,20 @@ describe('useParseZonedDateTimeToIMaskDateTimeInputString', () => { const createWrapper = (dateFormat: DateFormat) => - ({ children }: { children: React.ReactNode }) => ( - { - snapshot.set(workspaceMemberFormatPreferencesState, { - timeZone: 'Pacific/Auckland', - dateFormat, - timeFormat: TimeFormat.HOUR_24, - numberFormat: NumberFormat.COMMAS_AND_DOT, - calendarStartDay: CalendarStartDay.MONDAY, - }); - }} - > - {children} - - ); + ({ children }: { children: React.ReactNode }) => { + jotaiStore.set(workspaceMemberFormatPreferencesState.atom, { + timeZone: 'Pacific/Auckland', + dateFormat, + timeFormat: TimeFormat.HOUR_24, + numberFormat: NumberFormat.COMMAS_AND_DOT, + calendarStartDay: CalendarStartDay.MONDAY, + }); + return ( + + {children} + + ); + }; it('should format with day first format (DD/MM/YYYY, HH:mm)', () => { const { result } = renderHook( diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/date/hooks/__tests__/useUserTimezone.test.tsx b/packages/twenty-front/src/modules/ui/input/components/internal/date/hooks/__tests__/useUserTimezone.test.tsx index 7f074ef331..5ca63084dd 100644 --- a/packages/twenty-front/src/modules/ui/input/components/internal/date/hooks/__tests__/useUserTimezone.test.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/internal/date/hooks/__tests__/useUserTimezone.test.tsx @@ -1,8 +1,10 @@ import { renderHook } from '@testing-library/react'; +import { Provider as JotaiProvider } from 'jotai'; import { RecoilRoot } from 'recoil'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { useUserTimezone } from '@/ui/input/components/internal/date/hooks/useUserTimezone'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; describe('useUserTimezone', () => { const originalIntl = global.Intl; @@ -24,7 +26,9 @@ describe('useUserTimezone', () => { it('should return system timezone when currentWorkspaceMember is null', () => { const Wrapper = ({ children }: { children: React.ReactNode }) => ( - {children} + + {children} + ); const { result } = renderHook(() => useUserTimezone(), { @@ -36,32 +40,30 @@ describe('useUserTimezone', () => { }); it('should return system timezone when currentWorkspaceMember.timeZone is "system"', () => { + jotaiStore.set(currentWorkspaceMemberState.atom, { + id: 'workspace-member-id', + name: { + firstName: 'John', + lastName: 'Doe', + }, + colorScheme: 'Light', + locale: 'en-US', + userEmail: 'john@example.com', + timeZone: 'system', + dateFormat: null, + timeFormat: null, + numberFormat: null, + calendarStartDay: null, + }); + const WrapperWithSystemTimezone = ({ children, }: { children: React.ReactNode; }) => ( - { - snapshot.set(currentWorkspaceMemberState, { - id: 'workspace-member-id', - name: { - firstName: 'John', - lastName: 'Doe', - }, - colorScheme: 'Light', - locale: 'en-US', - userEmail: 'john@example.com', - timeZone: 'system', - dateFormat: null, - timeFormat: null, - numberFormat: null, - calendarStartDay: null, - }); - }} - > - {children} - + + {children} + ); const { result } = renderHook(() => useUserTimezone(), { @@ -75,32 +77,30 @@ describe('useUserTimezone', () => { it('should return user-specific timezone when currentWorkspaceMember.timeZone is set', () => { const userTimezone = 'Europe/Paris'; + jotaiStore.set(currentWorkspaceMemberState.atom, { + id: 'workspace-member-id', + name: { + firstName: 'John', + lastName: 'Doe', + }, + colorScheme: 'Light', + locale: 'en-US', + userEmail: 'john@example.com', + timeZone: userTimezone, + dateFormat: null, + timeFormat: null, + numberFormat: null, + calendarStartDay: null, + }); + const WrapperWithUserTimezone = ({ children, }: { children: React.ReactNode; }) => ( - { - snapshot.set(currentWorkspaceMemberState, { - id: 'workspace-member-id', - name: { - firstName: 'John', - lastName: 'Doe', - }, - colorScheme: 'Light', - locale: 'en-US', - userEmail: 'john@example.com', - timeZone: userTimezone, - dateFormat: null, - timeFormat: null, - numberFormat: null, - calendarStartDay: null, - }); - }} - > - {children} - + + {children} + ); const { result } = renderHook(() => useUserTimezone(), { diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/date/hooks/useUserDateFormat.ts b/packages/twenty-front/src/modules/ui/input/components/internal/date/hooks/useUserDateFormat.ts index 13299bcd62..a5fbe92f98 100644 --- a/packages/twenty-front/src/modules/ui/input/components/internal/date/hooks/useUserDateFormat.ts +++ b/packages/twenty-front/src/modules/ui/input/components/internal/date/hooks/useUserDateFormat.ts @@ -1,9 +1,9 @@ import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { WorkspaceMemberDateFormatEnum } from '~/generated-metadata/graphql'; export const useUserDateFormat = () => { - const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); + const currentWorkspaceMember = useRecoilValueV2(currentWorkspaceMemberState); const userDateFormat = currentWorkspaceMember?.dateFormat ?? WorkspaceMemberDateFormatEnum.SYSTEM; diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/date/hooks/useUserFirstDayOfTheWeek.ts b/packages/twenty-front/src/modules/ui/input/components/internal/date/hooks/useUserFirstDayOfTheWeek.ts index cf75949f09..39fc9f9285 100644 --- a/packages/twenty-front/src/modules/ui/input/components/internal/date/hooks/useUserFirstDayOfTheWeek.ts +++ b/packages/twenty-front/src/modules/ui/input/components/internal/date/hooks/useUserFirstDayOfTheWeek.ts @@ -1,6 +1,6 @@ import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { detectCalendarStartDay } from '@/localization/utils/detection/detectCalendarStartDay'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { CalendarStartDay } from 'twenty-shared/constants'; import { convertCalendarStartDayNonIsoNumberToFirstDayOfTheWeek, @@ -8,7 +8,7 @@ import { } from 'twenty-shared/utils'; export const useUserFirstDayOfTheWeek = () => { - const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); + const currentWorkspaceMember = useRecoilValueV2(currentWorkspaceMemberState); const systemFirstDayOfTheWeek = detectCalendarStartDay(); const isSystemFirstDayOfTheWeek = diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/date/hooks/useUserTimeFormat.ts b/packages/twenty-front/src/modules/ui/input/components/internal/date/hooks/useUserTimeFormat.ts index 52537db5e5..bb6613ec58 100644 --- a/packages/twenty-front/src/modules/ui/input/components/internal/date/hooks/useUserTimeFormat.ts +++ b/packages/twenty-front/src/modules/ui/input/components/internal/date/hooks/useUserTimeFormat.ts @@ -1,9 +1,9 @@ import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { WorkspaceMemberTimeFormatEnum } from '~/generated-metadata/graphql'; export const useUserTimeFormat = () => { - const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); + const currentWorkspaceMember = useRecoilValueV2(currentWorkspaceMemberState); const userTimeFormat = currentWorkspaceMember?.timeFormat ?? WorkspaceMemberTimeFormatEnum.SYSTEM; diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/date/hooks/useUserTimezone.ts b/packages/twenty-front/src/modules/ui/input/components/internal/date/hooks/useUserTimezone.ts index 0f82c0352a..5c0635754f 100644 --- a/packages/twenty-front/src/modules/ui/input/components/internal/date/hooks/useUserTimezone.ts +++ b/packages/twenty-front/src/modules/ui/input/components/internal/date/hooks/useUserTimezone.ts @@ -1,8 +1,8 @@ import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; export const useUserTimezone = () => { - const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); + const currentWorkspaceMember = useRecoilValueV2(currentWorkspaceMemberState); const systemTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; const userTimezone = diff --git a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownClickableComponent.tsx b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownClickableComponent.tsx index 155451b10b..4b41f99e66 100644 --- a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownClickableComponent.tsx +++ b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownClickableComponent.tsx @@ -6,14 +6,13 @@ import { StyledLabel, } from '@/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspacesDropdownStyles'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { useTheme } from '@emotion/react'; import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded'; -import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { Avatar } from 'twenty-ui/display'; export const MultiWorkspaceDropdownClickableComponent = () => { - const currentWorkspace = useRecoilValue(currentWorkspaceState); + const currentWorkspace = useRecoilValueV2(currentWorkspaceState); const theme = useTheme(); const isNavigationDrawerExpanded = useRecoilValueV2( diff --git a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx index 724b47fca9..247bb98c46 100644 --- a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx +++ b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx @@ -23,9 +23,9 @@ import { useColorScheme } from '@/ui/theme/hooks/useColorScheme'; import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; import { type ApolloError } from '@apollo/client'; import styled from '@emotion/styled'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { isNonEmptyString } from '@sniptt/guards'; import { useLingui } from '@lingui/react/macro'; -import { useRecoilValue } from 'recoil'; import { AppPath, SettingsPath } from 'twenty-shared/types'; import { getSettingsPath } from 'twenty-shared/utils'; import { @@ -56,11 +56,11 @@ const StyledDescription = styled.div` `; export const MultiWorkspaceDropdownDefaultComponents = () => { - const currentWorkspace = useRecoilValue(currentWorkspaceState); - const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); + const currentWorkspace = useRecoilValueV2(currentWorkspaceState); + const currentWorkspaceMember = useRecoilValueV2(currentWorkspaceMemberState); const { t } = useLingui(); const { redirectToWorkspaceDomain } = useRedirectToWorkspaceDomain(); - const availableWorkspaces = useRecoilValue(availableWorkspacesState); + const availableWorkspaces = useRecoilValueV2(availableWorkspacesState); const availableWorkspacesCount = countAvailableWorkspaces(availableWorkspaces); const { buildWorkspaceUrl } = useBuildWorkspaceUrl(); @@ -68,7 +68,7 @@ export const MultiWorkspaceDropdownDefaultComponents = () => { const { signOut } = useAuth(); const { enqueueErrorSnackBar } = useSnackBar(); const { colorScheme, colorSchemeList } = useColorScheme(); - const supportChat = useRecoilValue(supportChatState); + const supportChat = useRecoilValueV2(supportChatState); const isSupportChatConfigured = supportChat?.supportDriver === 'FRONT' && isNonEmptyString(supportChat.supportFrontChatId); diff --git a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownWorkspacesListComponents.tsx b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownWorkspacesListComponents.tsx index fbbda3ce81..b903c43de3 100644 --- a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownWorkspacesListComponents.tsx +++ b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownWorkspacesListComponents.tsx @@ -5,9 +5,9 @@ import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/Dropdow import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; import { multiWorkspaceDropdownStateV2 } from '@/ui/navigation/navigation-drawer/states/multiWorkspaceDropdownStateV2'; import { useLingui } from '@lingui/react/macro'; -import { useState } from 'react'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; +import { useState } from 'react'; import { IconChevronLeft } from 'twenty-ui/display'; import { WorkspacesForSignIn } from './components/WorkspacesForSignIn'; @@ -17,7 +17,7 @@ import { availableWorkspacesState } from '@/auth/states/availableWorkspacesState export const MultiWorkspaceDropdownWorkspacesListComponents = () => { const { t } = useLingui(); - const availableWorkspaces = useRecoilValue(availableWorkspacesState); + const availableWorkspaces = useRecoilValueV2(availableWorkspacesState); const setMultiWorkspaceDropdownState = useSetRecoilStateV2( multiWorkspaceDropdownStateV2, diff --git a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/components/WorkspacesForSignIn.tsx b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/components/WorkspacesForSignIn.tsx index 6874debc25..3ece498e6f 100644 --- a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/components/WorkspacesForSignIn.tsx +++ b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/components/WorkspacesForSignIn.tsx @@ -3,7 +3,7 @@ import { StyledDropdownMenuSubheader } from '@/ui/layout/dropdown/components/Sty import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { useFilteredAvailableWorkspaces } from '@/ui/navigation/navigation-drawer/hooks/useFilteredAvailableWorkspaces'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { availableWorkspacesState } from '@/auth/states/availableWorkspacesState'; import { AvailableWorkspaceItem } from '@/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/components/AvailableWorkspaceItem'; @@ -14,8 +14,8 @@ export const WorkspacesForSignIn = ({ }) => { const { t } = useLingui(); - const availableWorkspaces = useRecoilValue(availableWorkspacesState); - const currentWorkspace = useRecoilValue(currentWorkspaceState); + const availableWorkspaces = useRecoilValueV2(availableWorkspacesState); + const currentWorkspace = useRecoilValueV2(currentWorkspaceState); const { searchAvailableWorkspaces } = useFilteredAvailableWorkspaces(); diff --git a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/components/WorkspacesForSignUp.tsx b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/components/WorkspacesForSignUp.tsx index fbd1ed73b0..d2a7bc4b58 100644 --- a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/components/WorkspacesForSignUp.tsx +++ b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/components/WorkspacesForSignUp.tsx @@ -5,7 +5,7 @@ import { useFilteredAvailableWorkspaces } from '@/ui/navigation/navigation-drawe import { AvailableWorkspaceItem } from '@/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/components/AvailableWorkspaceItem'; import { availableWorkspacesState } from '@/auth/states/availableWorkspacesState'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; export const WorkspacesForSignUp = ({ searchValue, @@ -14,8 +14,8 @@ export const WorkspacesForSignUp = ({ }) => { const { t } = useLingui(); - const availableWorkspaces = useRecoilValue(availableWorkspacesState); - const currentWorkspace = useRecoilValue(currentWorkspaceState); + const availableWorkspaces = useRecoilValueV2(availableWorkspacesState); + const currentWorkspace = useRecoilValueV2(currentWorkspaceState); const { searchAvailableWorkspaces } = useFilteredAvailableWorkspaces(); diff --git a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitle.tsx b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitle.tsx index c055178273..fb3e810ffd 100644 --- a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitle.tsx +++ b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitle.tsx @@ -7,7 +7,6 @@ import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import styled from '@emotion/styled'; import React from 'react'; -import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { Label } from 'twenty-ui/display'; @@ -65,7 +64,7 @@ export const NavigationDrawerSectionTitle = ({ isNavigationDrawerExpandedState, ); const isSettingsPage = useIsSettingsPage(); - const currentUser = useRecoilValue(currentUserState); + const currentUser = useRecoilValueV2(currentUserState); const loading = useIsPrefetchLoading(); const handleTitleClick = (e: React.MouseEvent) => { e.stopPropagation(); diff --git a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/__stories__/NavigationDrawer.stories.tsx b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/__stories__/NavigationDrawer.stories.tsx index 7cacf1d516..0ca1550f46 100644 --- a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/__stories__/NavigationDrawer.stories.tsx +++ b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/__stories__/NavigationDrawer.stories.tsx @@ -1,10 +1,11 @@ import { type Meta, type StoryObj } from '@storybook/react-vite'; import { useEffect } from 'react'; -import { useSetRecoilState } from 'recoil'; import { expect, within } from 'storybook/test'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; +import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; import { SettingsPath } from 'twenty-shared/types'; import { ComponentWithRouterDecorator } from '~/testing/decorators/ComponentWithRouterDecorator'; import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator'; @@ -52,16 +53,16 @@ const meta: Meta = { ObjectMetadataItemsDecorator, PrefetchLoadedDecorator, (Story) => { - const setCurrentWorkspaceMember = useSetRecoilState( + const setCurrentWorkspaceMember = useSetRecoilStateV2( currentWorkspaceMemberState, ); - const setObjectMetadataItems = useSetRecoilState( - objectMetadataItemsState, - ); useEffect(() => { - setObjectMetadataItems(generatedMockObjectMetadataItems); + jotaiStore.set( + objectMetadataItemsState.atom, + generatedMockObjectMetadataItems, + ); setCurrentWorkspaceMember(mockedWorkspaceMemberData); - }, [setObjectMetadataItems, setCurrentWorkspaceMember]); + }, [setCurrentWorkspaceMember]); return ; }, ], diff --git a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/hooks/useFilteredAvailableWorkspaces.ts b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/hooks/useFilteredAvailableWorkspaces.ts index c24ed058a8..f6a990b858 100644 --- a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/hooks/useFilteredAvailableWorkspaces.ts +++ b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/hooks/useFilteredAvailableWorkspaces.ts @@ -1,9 +1,9 @@ import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { type AvailableWorkspace } from '~/generated-metadata/graphql'; export const useFilteredAvailableWorkspaces = () => { - const currentWorkspace = useRecoilValue(currentWorkspaceState); + const currentWorkspace = useRecoilValueV2(currentWorkspaceState); const searchAvailableWorkspaces = ( searchValue: string, diff --git a/packages/twenty-front/src/modules/ui/theme/hooks/__tests__/useColorScheme.test.tsx b/packages/twenty-front/src/modules/ui/theme/hooks/__tests__/useColorScheme.test.tsx index a362ec71d8..69a4907273 100644 --- a/packages/twenty-front/src/modules/ui/theme/hooks/__tests__/useColorScheme.test.tsx +++ b/packages/twenty-front/src/modules/ui/theme/hooks/__tests__/useColorScheme.test.tsx @@ -1,8 +1,11 @@ import { act, renderHook } from '@testing-library/react'; -import { RecoilRoot, useSetRecoilState } from 'recoil'; +import { Provider as JotaiProvider } from 'jotai'; +import { RecoilRoot } from 'recoil'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { useColorScheme } from '@/ui/theme/hooks/useColorScheme'; +import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { type WorkspaceMember } from '@/workspace-member/types/WorkspaceMember'; const updateOneRecordMock = jest.fn(); @@ -30,11 +33,17 @@ const workspaceMember: Omit< describe('useColorScheme', () => { it('should update color scheme', async () => { + const Wrapper = ({ children }: { children: React.ReactNode }) => ( + + {children} + + ); + const { result } = renderHook( () => { const colorScheme = useColorScheme(); - const setCurrentWorkspaceMember = useSetRecoilState( + const setCurrentWorkspaceMember = useSetRecoilStateV2( currentWorkspaceMemberState, ); @@ -43,7 +52,7 @@ describe('useColorScheme', () => { return colorScheme; }, { - wrapper: RecoilRoot, + wrapper: Wrapper, }, ); diff --git a/packages/twenty-front/src/modules/ui/theme/hooks/useColorScheme.ts b/packages/twenty-front/src/modules/ui/theme/hooks/useColorScheme.ts index 5785c02172..aab5997d8a 100644 --- a/packages/twenty-front/src/modules/ui/theme/hooks/useColorScheme.ts +++ b/packages/twenty-front/src/modules/ui/theme/hooks/useColorScheme.ts @@ -1,7 +1,6 @@ -import { useCallback } from 'react'; -import { useRecoilState } from 'recoil'; - import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; +import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2'; +import { useCallback } from 'react'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; import { persistedColorSchemeState } from '@/ui/theme/states/persistedColorSchemeState'; @@ -15,7 +14,7 @@ import { } from 'twenty-ui/display'; export const useColorScheme = () => { - const [currentWorkspaceMember, setCurrentWorkspaceMember] = useRecoilState( + const [currentWorkspaceMember, setCurrentWorkspaceMember] = useRecoilStateV2( currentWorkspaceMemberState, ); diff --git a/packages/twenty-front/src/modules/ui/utilities/page-favicon/components/PageFavicon.tsx b/packages/twenty-front/src/modules/ui/utilities/page-favicon/components/PageFavicon.tsx index aad5804ffe..54dcc5ff6b 100644 --- a/packages/twenty-front/src/modules/ui/utilities/page-favicon/components/PageFavicon.tsx +++ b/packages/twenty-front/src/modules/ui/utilities/page-favicon/components/PageFavicon.tsx @@ -1,12 +1,12 @@ import { workspacePublicDataState } from '@/auth/states/workspacePublicDataState'; import { DEFAULT_WORKSPACE_LOGO } from '@/ui/navigation/navigation-drawer/constants/DefaultWorkspaceLogo'; import { Helmet } from 'react-helmet-async'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { getImageAbsoluteURI } from 'twenty-shared/utils'; import { REACT_APP_SERVER_BASE_URL } from '~/config'; export const PageFavicon = () => { - const workspacePublicData = useRecoilValue(workspacePublicDataState); + const workspacePublicData = useRecoilValueV2(workspacePublicDataState); return ( (state: StateV2): ValueType; + (state: StateV2 | SelectorV2): ValueType; ( - familyState: FamilyStateV2, + familyState: + | FamilyStateV2 + | FamilySelectorV2, familyKey: FamilyKey, ): ValueType; }; 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 index f3f9812918..446c9684a8 100644 --- 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 @@ -1,12 +1,18 @@ import { type Getter } from 'jotai'; +import { type FamilySelectorV2 } from '@/ui/utilities/state/jotai/types/FamilySelectorV2'; import { type FamilyStateV2 } from '@/ui/utilities/state/jotai/types/FamilyStateV2'; +import { type SelectorV2 } from '@/ui/utilities/state/jotai/types/SelectorV2'; import { type StateV2 } from '@/ui/utilities/state/jotai/types/StateV2'; export const buildGetHelper = (jotaiGet: Getter) => ( - stateOrFamily: StateV2 | FamilyStateV2, + stateOrFamily: + | StateV2 + | SelectorV2 + | FamilyStateV2 + | FamilySelectorV2, familyKey?: FamilyKey, ): ValueType => { if (stateOrFamily.type === 'FamilyStateV2') { @@ -17,5 +23,15 @@ export const buildGetHelper = ); } - return jotaiGet((stateOrFamily as StateV2).atom); + if (stateOrFamily.type === 'FamilySelectorV2') { + return jotaiGet( + ( + stateOrFamily as FamilySelectorV2 + ).selectorFamily(familyKey as FamilyKey), + ); + } + + return jotaiGet( + (stateOrFamily as StateV2 | SelectorV2).atom, + ); }; diff --git a/packages/twenty-front/src/modules/ui/utilities/state/jotai/utils/createJotaiCookieStorage.ts b/packages/twenty-front/src/modules/ui/utilities/state/jotai/utils/createJotaiCookieStorage.ts new file mode 100644 index 0000000000..adb0b7eadd --- /dev/null +++ b/packages/twenty-front/src/modules/ui/utilities/state/jotai/utils/createJotaiCookieStorage.ts @@ -0,0 +1,93 @@ +import omit from 'lodash.omit'; +import { isDefined } from 'twenty-shared/utils'; + +import { cookieStorage } from '~/utils/cookie-storage'; + +type CookieAttributesLike = { + expires?: number | Date; + path?: string; + domain?: string; + secure?: boolean; +}; + +const hasCustomCookieAttributes = ( + value: unknown, +): value is { cookieAttributes: CookieAttributesLike } => + isDefined(value) && + typeof value === 'object' && + value !== null && + 'cookieAttributes' in value && + isDefined((value as Record).cookieAttributes); + +type JotaiSyncStorage = { + getItem: (key: string, initialValue: ValueType) => ValueType; + setItem: (key: string, newValue: ValueType) => void; + removeItem: (key: string) => void; +}; + +export const createJotaiCookieStorage = ({ + cookieKey, + attributes, + validateInitFn, +}: { + cookieKey: string; + attributes?: CookieAttributesLike; + validateInitFn?: (payload: NonNullable) => boolean; +}): JotaiSyncStorage => { + const defaultAttributes = { + expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 180), + ...(attributes ?? {}), + }; + + return { + getItem: (key: string, initialValue: ValueType): ValueType => { + const savedValue = cookieStorage.getItem(cookieKey); + + if (!isDefined(savedValue) || savedValue.length === 0) { + return initialValue; + } + + try { + const parsed = JSON.parse(savedValue); + + if (isDefined(validateInitFn) && !validateInitFn(parsed)) { + return initialValue; + } + + return parsed as ValueType; + } catch { + return initialValue; + } + }, + setItem: (key: string, newValue: ValueType): void => { + const cookieAttrs = { + ...defaultAttributes, + ...(hasCustomCookieAttributes(newValue) + ? newValue.cookieAttributes + : {}), + }; + + const valueToStore = hasCustomCookieAttributes(newValue) + ? omit(newValue, ['cookieAttributes']) + : newValue; + + if ( + !isDefined(valueToStore) || + (typeof valueToStore === 'object' && + Object.keys(valueToStore as object).length === 0) + ) { + cookieStorage.removeItem(cookieKey, cookieAttrs); + return; + } + + cookieStorage.setItem( + cookieKey, + JSON.stringify(valueToStore), + cookieAttrs, + ); + }, + removeItem: (_key: string): void => { + cookieStorage.removeItem(cookieKey, defaultAttributes); + }, + }; +}; 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 index 1052a14c55..d12bc3a522 100644 --- 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 @@ -1,20 +1,60 @@ -import { atom } from 'jotai'; +import { atom, type WritableAtom } from 'jotai'; import { atomWithStorage } from 'jotai/utils'; +import { isDefined } from 'twenty-shared/utils'; import { type StateV2 } from '@/ui/utilities/state/jotai/types/StateV2'; +import { createJotaiCookieStorage } from '@/ui/utilities/state/jotai/utils/createJotaiCookieStorage'; + +type CookieStorageConfig = { + cookieKey: string; + attributes?: { + expires?: number | Date; + path?: string; + domain?: string; + secure?: boolean; + }; + validateInitFn?: (payload: NonNullable) => boolean; +}; + +type StateAtom = WritableAtom< + ValueType, + [ValueType | ((prev: ValueType) => ValueType)], + void +>; export const createStateV2 = ({ key, defaultValue, useLocalStorage = false, + useCookieStorage, }: { key: string; defaultValue: ValueType; useLocalStorage?: boolean; + useCookieStorage?: CookieStorageConfig; }): StateV2 => { - const baseAtom = useLocalStorage - ? atomWithStorage(key, defaultValue) - : atom(defaultValue); + let baseAtom: StateAtom; + + if (isDefined(useCookieStorage)) { + const storage = createJotaiCookieStorage({ + cookieKey: useCookieStorage.cookieKey, + attributes: useCookieStorage.attributes, + validateInitFn: useCookieStorage.validateInitFn, + }); + baseAtom = atomWithStorage( + useCookieStorage.cookieKey, + defaultValue, + storage, + ) as StateAtom; + } else if (useLocalStorage) { + baseAtom = atomWithStorage( + key, + defaultValue, + ) as StateAtom; + } else { + baseAtom = atom(defaultValue); + } + baseAtom.debugLabel = key; return { diff --git a/packages/twenty-front/src/modules/users/components/EagerMetadataLoadEffect.tsx b/packages/twenty-front/src/modules/users/components/EagerMetadataLoadEffect.tsx new file mode 100644 index 0000000000..8f38384b66 --- /dev/null +++ b/packages/twenty-front/src/modules/users/components/EagerMetadataLoadEffect.tsx @@ -0,0 +1,121 @@ +import { useMetadataStore } from '@/app/hooks/useMetadataStore'; +import { metadataStoreState } from '@/app/states/metadataStoreState'; +import { useIsLogged } from '@/auth/hooks/useIsLogged'; +import { currentUserState } from '@/auth/states/currentUserState'; +import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; +import { useLoadMockedObjectMetadataItems } from '@/object-metadata/hooks/useLoadMockedObjectMetadataItems'; +import { useRefreshObjectMetadataItems } from '@/object-metadata/hooks/useRefreshObjectMetadataItems'; +import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { useFamilyRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilyRecoilValueV2'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; +import { coreViewsState } from '@/views/states/coreViewState'; +import { type CoreViewWithRelations } from '@/views/types/CoreViewWithRelations'; +import { useStore } from 'jotai'; +import { useCallback, useEffect } from 'react'; +import { useLocation } from 'react-router-dom'; +import { AppPath } from 'twenty-shared/types'; +import { isDefined } from 'twenty-shared/utils'; +import { isWorkspaceActiveOrSuspended } from 'twenty-shared/workspace'; +import { + ViewType as CoreViewType, + useFindAllCoreViewsQuery, +} from '~/generated-metadata/graphql'; +import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; +import { isMatchingLocation } from '~/utils/isMatchingLocation'; + +const INDEX_VIEW_TYPES = [ + CoreViewType.TABLE, + CoreViewType.KANBAN, + CoreViewType.CALENDAR, +]; + +export const EagerMetadataLoadEffect = () => { + const location = useLocation(); + const isLoggedIn = useIsLogged(); + const currentUser = useRecoilValueV2(currentUserState); + const currentWorkspace = useRecoilValueV2(currentWorkspaceState); + const store = useStore(); + + const objectsEntry = useFamilyRecoilValueV2(metadataStoreState, 'objects'); + const viewsEntry = useFamilyRecoilValueV2(metadataStoreState, 'views'); + + const { refreshObjectMetadataItems } = useRefreshObjectMetadataItems(); + const { loadMockedObjectMetadataItems } = useLoadMockedObjectMetadataItems(); + const { updateDraft, applyChanges } = useMetadataStore(); + + const isOnAuthPath = + isMatchingLocation(location, AppPath.Verify) || + isMatchingLocation(location, AppPath.VerifyEmail); + + const { data: queryDataCoreViews } = useFindAllCoreViewsQuery({ + skip: !isLoggedIn || viewsEntry.status !== 'empty' || isOnAuthPath, + variables: { viewTypes: INDEX_VIEW_TYPES }, + }); + + const setIndexCoreViews = useCallback( + (indexViews: CoreViewWithRelations[]) => { + const existingCoreViews = store.get(coreViewsState.atom); + const existingFieldsWidgetViews = existingCoreViews.filter( + (view) => view.type === CoreViewType.FIELDS_WIDGET, + ); + const mergedViews = [...indexViews, ...existingFieldsWidgetViews]; + + if (!isDeeplyEqual(existingCoreViews, mergedViews)) { + store.set(coreViewsState.atom, mergedViews); + } + }, + [store], + ); + + useEffect(() => { + if (objectsEntry.status !== 'empty') { + return; + } + + if (isLoggedIn && !isDefined(currentUser)) { + return; + } + + const loadObjectMetadata = async () => { + if (!isLoggedIn || !isWorkspaceActiveOrSuspended(currentWorkspace)) { + await loadMockedObjectMetadataItems(); + } else { + await refreshObjectMetadataItems(); + } + + const loadedItems = store.get(objectMetadataItemsState.atom); + updateDraft('objects', loadedItems); + }; + + loadObjectMetadata(); + }, [ + currentUser, + currentWorkspace, + isLoggedIn, + loadMockedObjectMetadataItems, + objectsEntry.status, + refreshObjectMetadataItems, + store, + updateDraft, + ]); + + useEffect(() => { + if (!isDefined(queryDataCoreViews?.getCoreViews)) { + return; + } + + setIndexCoreViews(queryDataCoreViews.getCoreViews); + updateDraft('views', queryDataCoreViews.getCoreViews); + }, [queryDataCoreViews?.getCoreViews, setIndexCoreViews, updateDraft]); + + useEffect(() => { + if ( + objectsEntry.status === 'draft_pending' && + viewsEntry.status === 'draft_pending' + ) { + applyChanges(); + } + }, [objectsEntry.status, viewsEntry.status, applyChanges]); + + return null; +}; diff --git a/packages/twenty-front/src/modules/users/components/LazyMetadataLoadEffect.tsx b/packages/twenty-front/src/modules/users/components/LazyMetadataLoadEffect.tsx new file mode 100644 index 0000000000..fd757270c2 --- /dev/null +++ b/packages/twenty-front/src/modules/users/components/LazyMetadataLoadEffect.tsx @@ -0,0 +1,122 @@ +import { metadataStoreState } from '@/app/states/metadataStoreState'; +import { useIsLogged } from '@/auth/hooks/useIsLogged'; +import { recordPageLayoutsState } from '@/page-layout/states/recordPageLayoutsState'; +import { type PageLayout } from '@/page-layout/types/PageLayout'; +import { transformPageLayout } from '@/page-layout/utils/transformPageLayout'; +import { logicFunctionsState } from '@/settings/logic-functions/states/logicFunctionsState'; +import { coreViewsState } from '@/views/states/coreViewState'; +import { type CoreViewWithRelations } from '@/views/types/CoreViewWithRelations'; +import { useStore } from 'jotai'; +import { useCallback, useEffect } from 'react'; +import { useRecoilCallback, useSetRecoilState } from 'recoil'; +import { useLocation } from 'react-router-dom'; +import { AppPath } from 'twenty-shared/types'; +import { isDefined } from 'twenty-shared/utils'; +import { + ViewType as CoreViewType, + useFindAllRecordPageLayoutsQuery, + useFindFieldsWidgetCoreViewsQuery, + useFindManyLogicFunctionsQuery, +} from '~/generated-metadata/graphql'; +import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; +import { isMatchingLocation } from '~/utils/isMatchingLocation'; + +const FIELDS_WIDGET_VIEW_TYPES = [CoreViewType.FIELDS_WIDGET]; + +export const LazyMetadataLoadEffect = () => { + const location = useLocation(); + const isLoggedIn = useIsLogged(); + const store = useStore(); + + const setLogicFunctions = useSetRecoilState(logicFunctionsState); + + const isOnAuthPath = + isMatchingLocation(location, AppPath.Verify) || + isMatchingLocation(location, AppPath.VerifyEmail); + + const shouldSkip = !isLoggedIn || isOnAuthPath; + + const { data: queryDataFieldsWidgetCoreViews } = + useFindFieldsWidgetCoreViewsQuery({ + skip: shouldSkip, + variables: { viewTypes: FIELDS_WIDGET_VIEW_TYPES }, + }); + + const { data: queryDataRecordPageLayouts } = useFindAllRecordPageLayoutsQuery( + { skip: shouldSkip }, + ); + + const { data: logicFunctionsData } = useFindManyLogicFunctionsQuery({ + skip: !isLoggedIn, + }); + + const setFieldsWidgetCoreViews = useCallback( + (fieldsWidgetViews: CoreViewWithRelations[]) => { + const existingCoreViews = store.get(coreViewsState.atom); + const existingIndexViews = existingCoreViews.filter( + (view) => view.type !== CoreViewType.FIELDS_WIDGET, + ); + const mergedViews = [...existingIndexViews, ...fieldsWidgetViews]; + + if (!isDeeplyEqual(existingCoreViews, mergedViews)) { + store.set(coreViewsState.atom, mergedViews); + } + }, + [store], + ); + + const setRecordPageLayouts = useRecoilCallback( + ({ set, snapshot }) => + (recordPageLayouts: PageLayout[]) => { + const existingRecordPageLayouts = snapshot + .getLoadable(recordPageLayoutsState) + .getValue(); + + if (!isDeeplyEqual(existingRecordPageLayouts, recordPageLayouts)) { + set(recordPageLayoutsState, recordPageLayouts); + } + + store.set(metadataStoreState.atomFamily('pageLayouts'), { + current: recordPageLayouts, + draft: [], + status: 'loaded', + }); + }, + [store], + ); + + useEffect(() => { + if (!isDefined(queryDataFieldsWidgetCoreViews?.getCoreViews)) { + return; + } + + setFieldsWidgetCoreViews(queryDataFieldsWidgetCoreViews.getCoreViews); + }, [queryDataFieldsWidgetCoreViews?.getCoreViews, setFieldsWidgetCoreViews]); + + useEffect(() => { + if (!isDefined(queryDataRecordPageLayouts?.getPageLayouts)) { + return; + } + + const transformedPageLayouts = + queryDataRecordPageLayouts.getPageLayouts.map(transformPageLayout); + + setRecordPageLayouts(transformedPageLayouts); + }, [queryDataRecordPageLayouts?.getPageLayouts, setRecordPageLayouts]); + + useEffect(() => { + if (!isDefined(logicFunctionsData?.findManyLogicFunctions)) { + return; + } + + setLogicFunctions(logicFunctionsData.findManyLogicFunctions); + + store.set(metadataStoreState.atomFamily('logicFunctions'), { + current: logicFunctionsData.findManyLogicFunctions, + draft: [], + status: 'loaded', + }); + }, [logicFunctionsData?.findManyLogicFunctions, setLogicFunctions, store]); + + return null; +}; diff --git a/packages/twenty-front/src/modules/users/components/MetadataProviderEffect.tsx b/packages/twenty-front/src/modules/users/components/MetadataProviderEffect.tsx index 448c9289bc..ae7f6403bc 100644 --- a/packages/twenty-front/src/modules/users/components/MetadataProviderEffect.tsx +++ b/packages/twenty-front/src/modules/users/components/MetadataProviderEffect.tsx @@ -1,4 +1,4 @@ -import { useRecoilCallback, useRecoilState, useSetRecoilState } from 'recoil'; +import { useRecoilCallback } from 'recoil'; import { useIsLogged } from '@/auth/hooks/useIsLogged'; import { availableWorkspacesState } from '@/auth/states/availableWorkspacesState'; @@ -8,62 +8,38 @@ import { currentWorkspaceDeletedMembersState } from '@/auth/states/currentWorksp import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { currentWorkspaceMembersState } from '@/auth/states/currentWorkspaceMembersState'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; -import { isCurrentUserLoadedState } from '@/auth/states/isCurrentUserLoadedState'; import { useInitializeFormatPreferences } from '@/localization/hooks/useInitializeFormatPreferences'; -import { recordPageLayoutsState } from '@/page-layout/states/recordPageLayoutsState'; -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 { useStore } from 'jotai'; -import { coreViewsState } from '@/views/states/coreViewState'; -import { type CoreViewWithRelations } from '@/views/types/CoreViewWithRelations'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; +import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; import { type ColorScheme } from '@/workspace-member/types/WorkspaceMember'; import { enUS } from 'date-fns/locale'; -import { useEffect, useState } from 'react'; +import { useStore } from 'jotai'; +import { useEffect } from 'react'; import { useLocation } from 'react-router-dom'; import { type APP_LOCALES, SOURCE_LOCALE } from 'twenty-shared/translations'; import { AppPath, type ObjectPermissions } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { - ViewType as CoreViewType, - useFindAllCoreViewsQuery, - useFindAllRecordPageLayoutsQuery, - useFindFieldsWidgetCoreViewsQuery, - useFindManyLogicFunctionsQuery, useGetCurrentUserQuery, type WorkspaceMember, } 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'; -const INDEX_VIEW_TYPES = [ - CoreViewType.TABLE, - CoreViewType.KANBAN, - CoreViewType.CALENDAR, -]; - -const FIELDS_WIDGET_VIEW_TYPES = [CoreViewType.FIELDS_WIDGET]; - export const MetadataProviderEffect = () => { const location = useLocation(); - const [isCurrentUserLoaded, setIsCurrentUserLoaded] = useRecoilState( - isCurrentUserLoadedState, + const currentUser = useRecoilValueV2(currentUserState); + + const setCurrentUser = useSetRecoilStateV2(currentUserState); + const setCurrentWorkspace = useSetRecoilStateV2(currentWorkspaceState); + const setCurrentUserWorkspace = useSetRecoilStateV2( + currentUserWorkspaceState, ); - - const [localIsCurrentUserLoaded, setLocalIsCurrentUserLoaded] = - useState(false); - const [localAreViewsLoaded, setLocalAreViewsLoaded] = useState(false); - - const setCurrentUser = useSetRecoilState(currentUserState); - const setCurrentWorkspace = useSetRecoilState(currentWorkspaceState); - const setCurrentUserWorkspace = useSetRecoilState(currentUserWorkspaceState); - const setAvailableWorkspaces = useSetRecoilState(availableWorkspacesState); - const setLogicFunctions = useSetRecoilState(logicFunctionsState); + const setAvailableWorkspaces = useSetRecoilStateV2(availableWorkspacesState); const { initializeFormatPreferences } = useInitializeFormatPreferences(); const isLoggedIn = useIsLogged(); @@ -87,73 +63,19 @@ export const MetadataProviderEffect = () => { [store], ); - const setIndexCoreViews = useRecoilCallback( - ({ set, snapshot }) => - (indexViews: CoreViewWithRelations[]) => { - const existingCoreViews = snapshot - .getLoadable(coreViewsState) - .getValue(); - - const existingFieldsWidgetViews = existingCoreViews.filter( - (view) => view.type === CoreViewType.FIELDS_WIDGET, - ); - - const mergedViews = [...indexViews, ...existingFieldsWidgetViews]; - - if (!isDeeplyEqual(existingCoreViews, mergedViews)) { - set(coreViewsState, mergedViews); - } - }, - [], - ); - - const setFieldsWidgetCoreViews = useRecoilCallback( - ({ set, snapshot }) => - (fieldsWidgetViews: CoreViewWithRelations[]) => { - const existingCoreViews = snapshot - .getLoadable(coreViewsState) - .getValue(); - - const existingIndexViews = existingCoreViews.filter( - (view) => view.type !== CoreViewType.FIELDS_WIDGET, - ); - - const mergedViews = [...existingIndexViews, ...fieldsWidgetViews]; - - if (!isDeeplyEqual(existingCoreViews, mergedViews)) { - set(coreViewsState, mergedViews); - } - }, - [], - ); - - const setRecordPageLayouts = useRecoilCallback( - ({ set, snapshot }) => - (recordPageLayouts: PageLayout[]) => { - const existingRecordPageLayouts = snapshot - .getLoadable(recordPageLayoutsState) - .getValue(); - - if (!isDeeplyEqual(existingRecordPageLayouts, recordPageLayouts)) { - set(recordPageLayoutsState, recordPageLayouts); - } - }, - [], - ); - - const setCurrentWorkspaceMember = useSetRecoilState( + const setCurrentWorkspaceMember = useSetRecoilStateV2( currentWorkspaceMemberState, ); - const setCurrentWorkspaceMembers = useSetRecoilState( + const setCurrentWorkspaceMembers = useSetRecoilStateV2( currentWorkspaceMembersState, ); - const setCurrentWorkspaceMembersWithDeleted = useSetRecoilState( + const setCurrentWorkspaceMembersWithDeleted = useSetRecoilStateV2( currentWorkspaceDeletedMembersState, ); const shouldSkip = !isLoggedIn || - isCurrentUserLoaded || + isDefined(currentUser) || isMatchingLocation(location, AppPath.Verify) || isMatchingLocation(location, AppPath.VerifyEmail); @@ -162,40 +84,10 @@ export const MetadataProviderEffect = () => { skip: shouldSkip, }); - const { data: queryDataCoreViews, loading: queryLoadingCoreViews } = - useFindAllCoreViewsQuery({ - skip: shouldSkip, - variables: { viewTypes: INDEX_VIEW_TYPES }, - }); - - const { data: queryDataFieldsWidgetCoreViews } = - useFindFieldsWidgetCoreViewsQuery({ - skip: shouldSkip, - variables: { viewTypes: FIELDS_WIDGET_VIEW_TYPES }, - }); - - const { data: queryDataRecordPageLayouts } = useFindAllRecordPageLayoutsQuery( - { - skip: shouldSkip, - }, - ); - - const { data: logicFunctionsData } = useFindManyLogicFunctionsQuery({ - skip: !isLoggedIn, - }); - useEffect(() => { - if (isDefined(logicFunctionsData?.findManyLogicFunctions)) { - setLogicFunctions(logicFunctionsData.findManyLogicFunctions); + if (userQueryLoading || !isDefined(userQueryData?.currentUser)) { + return; } - }, [logicFunctionsData?.findManyLogicFunctions, setLogicFunctions]); - - useEffect(() => { - if (!userQueryLoading) { - setLocalIsCurrentUserLoaded(true); - } - - if (!isDefined(userQueryData?.currentUser)) return; setCurrentUser(userQueryData.currentUser); @@ -250,7 +142,6 @@ export const MetadataProviderEffect = () => { updateLocaleCatalog(updatedWorkspaceMember.locale); - // Initialize format preferences from workspace member initializeFormatPreferences(updatedWorkspaceMember); dynamicActivate( @@ -278,47 +169,10 @@ export const MetadataProviderEffect = () => { setAvailableWorkspaces, setCurrentWorkspace, setCurrentWorkspaceMember, - setIsCurrentUserLoaded, initializeFormatPreferences, setCurrentWorkspaceMembersWithDeleted, updateLocaleCatalog, - setIndexCoreViews, ]); - useEffect(() => { - if (!queryLoadingCoreViews) { - setLocalAreViewsLoaded(true); - } - - if (!isDefined(queryDataCoreViews?.getCoreViews)) return; - - setIndexCoreViews(queryDataCoreViews.getCoreViews); - }, [ - queryDataCoreViews?.getCoreViews, - setIndexCoreViews, - queryLoadingCoreViews, - ]); - - useEffect(() => { - if (!isDefined(queryDataFieldsWidgetCoreViews?.getCoreViews)) return; - - setFieldsWidgetCoreViews(queryDataFieldsWidgetCoreViews.getCoreViews); - }, [queryDataFieldsWidgetCoreViews?.getCoreViews, setFieldsWidgetCoreViews]); - - useEffect(() => { - if (!isDefined(queryDataRecordPageLayouts?.getPageLayouts)) return; - - const transformedPageLayouts = - queryDataRecordPageLayouts.getPageLayouts.map(transformPageLayout); - - setRecordPageLayouts(transformedPageLayouts); - }, [queryDataRecordPageLayouts?.getPageLayouts, setRecordPageLayouts]); - - useEffect(() => { - if (localIsCurrentUserLoaded && localAreViewsLoaded) { - setIsCurrentUserLoaded(true); - } - }, [localIsCurrentUserLoaded, localAreViewsLoaded, setIsCurrentUserLoaded]); - return null; }; diff --git a/packages/twenty-front/src/modules/users/components/UserProvider.tsx b/packages/twenty-front/src/modules/users/components/UserProvider.tsx index 3199944e9b..f7f6031304 100644 --- a/packages/twenty-front/src/modules/users/components/UserProvider.tsx +++ b/packages/twenty-front/src/modules/users/components/UserProvider.tsx @@ -1,8 +1,9 @@ import React from 'react'; -import { useRecoilValue } from 'recoil'; -import { isCurrentUserLoadedState } from '@/auth/states/isCurrentUserLoadedState'; +import { isAppLoadingState } from '@/app/states/isAppLoadingState'; +import { useIsLogged } from '@/auth/hooks/useIsLogged'; import { useDateTimeFormat } from '@/localization/hooks/useDateTimeFormat'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { UserContext } from '@/users/contexts/UserContext'; import { useLocation } from 'react-router-dom'; import { AppPath } from 'twenty-shared/types'; @@ -10,15 +11,20 @@ import { UserOrMetadataLoader } from '~/loading/components/UserOrMetadataLoader' import { isMatchingLocation } from '~/utils/isMatchingLocation'; export const UserProvider = ({ children }: React.PropsWithChildren) => { - const isCurrentUserLoaded = useRecoilValue(isCurrentUserLoadedState); + const isAppLoading = useRecoilValueV2(isAppLoadingState); + const isLoggedIn = useIsLogged(); const location = useLocation(); const { dateFormat, timeFormat, timeZone } = useDateTimeFormat(); - return !isCurrentUserLoaded && + const shouldShowLoader = + isAppLoading && + isLoggedIn && !isMatchingLocation(location, AppPath.Verify) && !isMatchingLocation(location, AppPath.VerifyEmail) && - !isMatchingLocation(location, AppPath.CreateWorkspace) ? ( + !isMatchingLocation(location, AppPath.CreateWorkspace); + + return shouldShowLoader ? ( ) : ( { - const setCurrentUser = useSetRecoilState(currentUserState); - const setAvailableWorkspaces = useSetRecoilState(availableWorkspacesState); - const setCurrentWorkspaceMember = useSetRecoilState( + const setCurrentUser = useSetRecoilStateV2(currentUserState); + const setAvailableWorkspaces = useSetRecoilStateV2(availableWorkspacesState); + const setCurrentWorkspaceMember = useSetRecoilStateV2( currentWorkspaceMemberState, ); const { setLastAuthenticateWorkspaceDomain } = useLastAuthenticatedWorkspaceDomain(); - const setCurrentUserWorkspace = useSetRecoilState(currentUserWorkspaceState); - const setCurrentWorkspaceMembers = useSetRecoilState( + const setCurrentUserWorkspace = useSetRecoilStateV2( + currentUserWorkspaceState, + ); + const setCurrentWorkspaceMembers = useSetRecoilStateV2( currentWorkspaceMembersState, ); - const setCurrentWorkspace = useSetRecoilState(currentWorkspaceState); + const setCurrentWorkspace = useSetRecoilStateV2(currentWorkspaceState); const { initializeFormatPreferences } = useInitializeFormatPreferences(); - const setCoreViews = useSetRecoilState(coreViewsState); - const setWorkspaceAuthBypassProviders = useSetRecoilState( + const setCoreViews = useSetRecoilStateV2(coreViewsState); + const setWorkspaceAuthBypassProviders = useSetRecoilStateV2( workspaceAuthBypassProvidersState, ); const authProviders = useRecoilValueV2(authProvidersState); diff --git a/packages/twenty-front/src/modules/views/components/ViewBarAnyFieldFilterEffect.tsx b/packages/twenty-front/src/modules/views/components/ViewBarAnyFieldFilterEffect.tsx index 1fd442101c..e2f4a0ecec 100644 --- a/packages/twenty-front/src/modules/views/components/ViewBarAnyFieldFilterEffect.tsx +++ b/packages/twenty-front/src/modules/views/components/ViewBarAnyFieldFilterEffect.tsx @@ -4,10 +4,10 @@ import { useRecordIndexContextOrThrow } from '@/object-record/record-index/conte import { useRecoilComponentFamilyState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyState'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState'; +import { useFamilySelectorValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilySelectorValueV2'; import { hasInitializedAnyFieldFilterComponentFamilyState } from '@/views/states/hasInitializedAnyFieldFilterComponentFamilyState'; import { coreViewFromViewIdFamilySelector } from '@/views/states/selectors/coreViewFromViewIdFamilySelector'; import { useEffect } from 'react'; -import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; export const ViewBarAnyFieldFilterEffect = () => { @@ -17,10 +17,9 @@ export const ViewBarAnyFieldFilterEffect = () => { const { objectMetadataItem } = useRecordIndexContextOrThrow(); - const currentView = useRecoilValue( - coreViewFromViewIdFamilySelector({ - viewId: currentViewId ?? '', - }), + const currentView = useFamilySelectorValueV2( + coreViewFromViewIdFamilySelector, + { viewId: currentViewId ?? '' }, ); const [hasInitializedAnyFieldFilter, setHasInitializedAnyFieldFilter] = diff --git a/packages/twenty-front/src/modules/views/components/ViewBarFilterDropdownAdvancedFilterButton.tsx b/packages/twenty-front/src/modules/views/components/ViewBarFilterDropdownAdvancedFilterButton.tsx index 0a5d094e3b..ef4a261519 100644 --- a/packages/twenty-front/src/modules/views/components/ViewBarFilterDropdownAdvancedFilterButton.tsx +++ b/packages/twenty-front/src/modules/views/components/ViewBarFilterDropdownAdvancedFilterButton.tsx @@ -6,6 +6,7 @@ import { useUpsertRecordFilter } from '@/object-record/record-filter/hooks/useUp import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem'; import { isSelectedItemIdComponentFamilyState } from '@/ui/layout/selectable-list/states/isSelectedItemIdComponentFamilyState'; import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentFamilyValueV2'; +import { useFamilySelectorValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilySelectorValueV2'; import { VIEW_BAR_FILTER_BOTTOM_MENU_ITEM_IDS } from '@/views/constants/ViewBarFilterBottomMenuItemIds'; import { useSetRecordFilterUsedInAdvancedFilterDropdownRow } from '@/object-record/advanced-filter/hooks/useSetRecordFilterUsedInAdvancedFilterDropdownRow'; @@ -17,7 +18,6 @@ import { ViewBarFilterDropdownIds } from '@/views/constants/ViewBarFilterDropdow import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly'; import styled from '@emotion/styled'; import { useLingui } from '@lingui/react/macro'; -import { useRecoilValue } from 'recoil'; import { RecordFilterGroupLogicalOperator } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { Pill } from 'twenty-ui/components'; @@ -60,10 +60,11 @@ export const ViewBarFilterDropdownAdvancedFilterButton = () => { objectId: objectMetadataId ?? null, }); - const availableFieldMetadataItemsForFilter = useRecoilValue( - availableFieldMetadataItemsForFilterFamilySelector({ + const availableFieldMetadataItemsForFilter = useFamilySelectorValueV2( + availableFieldMetadataItemsForFilterFamilySelector, + { objectMetadataItemId: objectMetadataItem.id, - }), + }, ); const currentRecordFilterGroups = useRecoilComponentValue( diff --git a/packages/twenty-front/src/modules/views/components/ViewBarRecordFieldEffect.tsx b/packages/twenty-front/src/modules/views/components/ViewBarRecordFieldEffect.tsx index 6bce7f34bd..35ac2fdf17 100644 --- a/packages/twenty-front/src/modules/views/components/ViewBarRecordFieldEffect.tsx +++ b/packages/twenty-front/src/modules/views/components/ViewBarRecordFieldEffect.tsx @@ -4,11 +4,11 @@ import { useRecordIndexContextOrThrow } from '@/object-record/record-index/conte import { useRecoilComponentFamilyState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyState'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState'; +import { useFamilySelectorValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilySelectorValueV2'; import { hasInitializedCurrentRecordFieldsComponentFamilyState } from '@/views/states/hasInitializedCurrentRecordFieldsComponentFamilyState'; import { coreViewFromViewIdFamilySelector } from '@/views/states/selectors/coreViewFromViewIdFamilySelector'; import { mapViewFieldToRecordField } from '@/views/utils/mapViewFieldToRecordField'; import { useEffect } from 'react'; -import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; export const ViewBarRecordFieldEffect = () => { @@ -18,10 +18,9 @@ export const ViewBarRecordFieldEffect = () => { const { objectMetadataItem } = useRecordIndexContextOrThrow(); - const currentView = useRecoilValue( - coreViewFromViewIdFamilySelector({ - viewId: currentViewId ?? '', - }), + const currentView = useFamilySelectorValueV2( + coreViewFromViewIdFamilySelector, + { viewId: currentViewId ?? '' }, ); const [ diff --git a/packages/twenty-front/src/modules/views/components/ViewBarRecordFilterEffect.tsx b/packages/twenty-front/src/modules/views/components/ViewBarRecordFilterEffect.tsx index 09500dfd0b..fa2cf3e406 100644 --- a/packages/twenty-front/src/modules/views/components/ViewBarRecordFilterEffect.tsx +++ b/packages/twenty-front/src/modules/views/components/ViewBarRecordFilterEffect.tsx @@ -4,11 +4,11 @@ import { useRecordIndexContextOrThrow } from '@/object-record/record-index/conte import { useRecoilComponentFamilyState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyState'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState'; +import { useFamilySelectorValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilySelectorValueV2'; import { useMapViewFiltersToFilters } from '@/views/hooks/useMapViewFiltersToFilters'; import { hasInitializedCurrentRecordFiltersComponentFamilyState } from '@/views/states/hasInitializedCurrentRecordFiltersComponentFamilyState'; import { coreViewFromViewIdFamilySelector } from '@/views/states/selectors/coreViewFromViewIdFamilySelector'; import { useEffect } from 'react'; -import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; export const ViewBarRecordFilterEffect = () => { @@ -18,10 +18,9 @@ export const ViewBarRecordFilterEffect = () => { const { objectMetadataItem } = useRecordIndexContextOrThrow(); - const currentView = useRecoilValue( - coreViewFromViewIdFamilySelector({ - viewId: currentViewId ?? '', - }), + const currentView = useFamilySelectorValueV2( + coreViewFromViewIdFamilySelector, + { viewId: currentViewId ?? '' }, ); const [ diff --git a/packages/twenty-front/src/modules/views/components/ViewBarRecordFilterGroupEffect.tsx b/packages/twenty-front/src/modules/views/components/ViewBarRecordFilterGroupEffect.tsx index e554e1285f..2417248477 100644 --- a/packages/twenty-front/src/modules/views/components/ViewBarRecordFilterGroupEffect.tsx +++ b/packages/twenty-front/src/modules/views/components/ViewBarRecordFilterGroupEffect.tsx @@ -4,11 +4,11 @@ import { useRecordIndexContextOrThrow } from '@/object-record/record-index/conte import { useRecoilComponentFamilyState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyState'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState'; +import { useFamilySelectorValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilySelectorValueV2'; import { hasInitializedCurrentRecordFilterGroupsComponentFamilyState } from '@/views/states/hasInitializedCurrentRecordFilterGroupsComponentFamilyState'; import { coreViewFromViewIdFamilySelector } from '@/views/states/selectors/coreViewFromViewIdFamilySelector'; import { mapViewFilterGroupsToRecordFilterGroups } from '@/views/utils/mapViewFilterGroupsToRecordFilterGroups'; import { useEffect } from 'react'; -import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; export const ViewBarRecordFilterGroupEffect = () => { @@ -18,10 +18,9 @@ export const ViewBarRecordFilterGroupEffect = () => { const { objectMetadataItem } = useRecordIndexContextOrThrow(); - const currentView = useRecoilValue( - coreViewFromViewIdFamilySelector({ - viewId: currentViewId ?? '', - }), + const currentView = useFamilySelectorValueV2( + coreViewFromViewIdFamilySelector, + { viewId: currentViewId ?? '' }, ); const [ diff --git a/packages/twenty-front/src/modules/views/components/ViewBarRecordSortEffect.tsx b/packages/twenty-front/src/modules/views/components/ViewBarRecordSortEffect.tsx index d265ad1eb0..0eed62226e 100644 --- a/packages/twenty-front/src/modules/views/components/ViewBarRecordSortEffect.tsx +++ b/packages/twenty-front/src/modules/views/components/ViewBarRecordSortEffect.tsx @@ -4,10 +4,10 @@ import { currentRecordSortsComponentState } from '@/object-record/record-sort/st import { useRecoilComponentFamilyState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyState'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState'; +import { useFamilySelectorValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilySelectorValueV2'; import { hasInitializedCurrentRecordSortsComponentFamilyState } from '@/views/states/hasInitializedCurrentRecordSortsComponentFamilyState'; import { coreViewFromViewIdFamilySelector } from '@/views/states/selectors/coreViewFromViewIdFamilySelector'; import { useEffect } from 'react'; -import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; export const ViewBarRecordSortEffect = () => { @@ -17,10 +17,9 @@ export const ViewBarRecordSortEffect = () => { const { objectMetadataItem } = useRecordIndexContextOrThrow(); - const currentView = useRecoilValue( - coreViewFromViewIdFamilySelector({ - viewId: currentViewId ?? '', - }), + const currentView = useFamilySelectorValueV2( + coreViewFromViewIdFamilySelector, + { viewId: currentViewId ?? '' }, ); const [ diff --git a/packages/twenty-front/src/modules/views/components/__stories__/ViewBarFilterDropdown.stories.tsx b/packages/twenty-front/src/modules/views/components/__stories__/ViewBarFilterDropdown.stories.tsx index be1173d2df..a60bbb8638 100644 --- a/packages/twenty-front/src/modules/views/components/__stories__/ViewBarFilterDropdown.stories.tsx +++ b/packages/twenty-front/src/modules/views/components/__stories__/ViewBarFilterDropdown.stories.tsx @@ -16,8 +16,8 @@ import { currentRecordFieldsComponentState } from '@/object-record/record-field/ import { type RecordField } from '@/object-record/record-field/types/RecordField'; import { useRecordIndexFieldMetadataDerivedStates } from '@/object-record/record-index/hooks/useRecordIndexFieldMetadataDerivedStates'; import { ViewBarFilterDropdownIds } from '@/views/constants/ViewBarFilterDropdownIds'; +import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; import { coreViewsState } from '@/views/states/coreViewState'; -import { useSetRecoilState } from 'recoil'; import { userEvent, within } from 'storybook/test'; import { ComponentDecorator } from 'twenty-ui/testing'; import { ContextStoreDecorator } from '~/testing/decorators/ContextStoreDecorator'; @@ -42,7 +42,7 @@ const meta: Meta = { instanceId, ); - const setCoreViews = useSetRecoilState(coreViewsState); + const setCoreViews = useSetRecoilStateV2(coreViewsState); const mockCoreView = mockedCoreViewsData[0]; diff --git a/packages/twenty-front/src/modules/views/hooks/__tests__/useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups.test.tsx b/packages/twenty-front/src/modules/views/hooks/__tests__/useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups.test.tsx index 723a399119..32a8c7a670 100644 --- a/packages/twenty-front/src/modules/views/hooks/__tests__/useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups.test.tsx +++ b/packages/twenty-front/src/modules/views/hooks/__tests__/useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups.test.tsx @@ -4,6 +4,7 @@ import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/ import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState'; import { type RecordFilterGroup } from '@/object-record/record-filter-group/types/RecordFilterGroup'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups } from '@/views/hooks/useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups'; import { coreViewsState } from '@/views/states/coreViewState'; import { type CoreViewWithRelations } from '@/views/types/CoreViewWithRelations'; @@ -34,6 +35,10 @@ describe('useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups', () => { ); } + afterEach(() => { + jotaiStore.set(coreViewsState.atom, []); + }); + const mockViewFilterGroup: ViewFilterGroup = { __typename: 'ViewFilterGroup', id: 'filter-group-1', @@ -67,6 +72,8 @@ describe('useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups', () => { } satisfies CoreViewWithRelations; it('should apply view filter groups from current view', () => { + jotaiStore.set(coreViewsState.atom, [mockCoreView]); + const { result } = renderHook( () => { const { applyCurrentViewFilterGroupsToCurrentRecordFilterGroups } = @@ -88,9 +95,6 @@ describe('useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups', () => { contextStoreCurrentViewId: mockView.id, contextStoreCurrentObjectMetadataNameSingular: mockObjectMetadataItemNameSingular, - onInitializeRecoilSnapshot: (snapshot) => { - snapshot.set(coreViewsState, [mockCoreView]); - }, }), }, ); @@ -118,6 +122,8 @@ describe('useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups', () => { }); it('should not apply view filter groups when current view is not found', () => { + jotaiStore.set(coreViewsState.atom, []); + const { result } = renderHook( () => { const { applyCurrentViewFilterGroupsToCurrentRecordFilterGroups } = @@ -145,8 +151,6 @@ describe('useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups', () => { }), mockView.id, ); - - snapshot.set(coreViewsState, []); }, }), }, @@ -160,6 +164,10 @@ describe('useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups', () => { }); it('should handle view with empty view filter groups', () => { + jotaiStore.set(coreViewsState.atom, [ + { ...mockCoreView, viewFilterGroups: [] }, + ]); + const { result } = renderHook( () => { const { applyCurrentViewFilterGroupsToCurrentRecordFilterGroups } = @@ -187,10 +195,6 @@ describe('useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups', () => { }), mockView.id, ); - - snapshot.set(coreViewsState, [ - { ...mockCoreView, viewFilterGroups: [] }, - ]); }, }), }, diff --git a/packages/twenty-front/src/modules/views/hooks/__tests__/useApplyCurrentViewFiltersToCurrentRecordFilters.test.tsx b/packages/twenty-front/src/modules/views/hooks/__tests__/useApplyCurrentViewFiltersToCurrentRecordFilters.test.tsx index 7657ed8022..69ca7f7097 100644 --- a/packages/twenty-front/src/modules/views/hooks/__tests__/useApplyCurrentViewFiltersToCurrentRecordFilters.test.tsx +++ b/packages/twenty-front/src/modules/views/hooks/__tests__/useApplyCurrentViewFiltersToCurrentRecordFilters.test.tsx @@ -4,6 +4,7 @@ import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/ import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; import { type RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { coreViewsState } from '@/views/states/coreViewState'; import { type CoreViewWithRelations } from '@/views/types/CoreViewWithRelations'; import { type View } from '@/views/types/View'; @@ -36,6 +37,10 @@ describe('useApplyCurrentViewFiltersToCurrentRecordFilters', () => { ); } + afterEach(() => { + jotaiStore.set(coreViewsState.atom, []); + }); + const allCompaniesView = mockedViewsData[0]; const allCompaniesCoreView = mockedCoreViewsData[0]; @@ -77,18 +82,9 @@ describe('useApplyCurrentViewFiltersToCurrentRecordFilters', () => { viewFilters: [mockCoreViewFilter], } satisfies CoreViewWithRelations; - const wrapper = getJestMetadataAndApolloMocksAndActionMenuWrapper({ - apolloMocks: [], - componentInstanceId: 'instanceId', - contextStoreCurrentObjectMetadataNameSingular: - mockObjectMetadataItemNameSingular, - contextStoreCurrentViewId: mockView.id, - onInitializeRecoilSnapshot: (snapshot) => { - snapshot.set(coreViewsState, [mockCoreView]); - }, - }); - it('should apply filters from current view', () => { + jotaiStore.set(coreViewsState.atom, [mockCoreView]); + const { result } = renderHook( () => { const { applyCurrentViewFiltersToCurrentRecordFilters } = @@ -104,7 +100,13 @@ describe('useApplyCurrentViewFiltersToCurrentRecordFilters', () => { }; }, { - wrapper, + wrapper: getJestMetadataAndApolloMocksAndActionMenuWrapper({ + apolloMocks: [], + componentInstanceId: 'instanceId', + contextStoreCurrentObjectMetadataNameSingular: + mockObjectMetadataItemNameSingular, + contextStoreCurrentViewId: mockView.id, + }), }, ); @@ -129,6 +131,8 @@ describe('useApplyCurrentViewFiltersToCurrentRecordFilters', () => { }); it('should not apply filters when current view is not found', () => { + jotaiStore.set(coreViewsState.atom, []); + const { result } = renderHook( () => { const { applyCurrentViewFiltersToCurrentRecordFilters } = @@ -156,7 +160,6 @@ describe('useApplyCurrentViewFiltersToCurrentRecordFilters', () => { }), mockView.id, ); - snapshot.set(coreViewsState, []); }, }), }, @@ -170,6 +173,8 @@ describe('useApplyCurrentViewFiltersToCurrentRecordFilters', () => { }); it('should handle view with empty filters', () => { + jotaiStore.set(coreViewsState.atom, [{ ...mockCoreView, viewFilters: [] }]); + const { result } = renderHook( () => { const { applyCurrentViewFiltersToCurrentRecordFilters } = @@ -197,9 +202,6 @@ describe('useApplyCurrentViewFiltersToCurrentRecordFilters', () => { }), mockView.id, ); - snapshot.set(coreViewsState, [ - { ...mockCoreView, viewFilters: [] }, - ]); }, }), }, diff --git a/packages/twenty-front/src/modules/views/hooks/__tests__/useApplyCurrentViewSortsToCurrentRecordSorts.test.tsx b/packages/twenty-front/src/modules/views/hooks/__tests__/useApplyCurrentViewSortsToCurrentRecordSorts.test.tsx index 7fdde540dc..ab7b3d784c 100644 --- a/packages/twenty-front/src/modules/views/hooks/__tests__/useApplyCurrentViewSortsToCurrentRecordSorts.test.tsx +++ b/packages/twenty-front/src/modules/views/hooks/__tests__/useApplyCurrentViewSortsToCurrentRecordSorts.test.tsx @@ -6,6 +6,7 @@ import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/ho import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { coreViewsState } from '@/views/states/coreViewState'; import { type CoreViewSortEssential } from '@/views/types/CoreViewSortEssential'; import { type CoreViewWithRelations } from '@/views/types/CoreViewWithRelations'; @@ -33,6 +34,10 @@ describe('useApplyCurrentViewSortsToCurrentRecordSorts', () => { ); } + afterEach(() => { + jotaiStore.set(coreViewsState.atom, []); + }); + const mockFieldMetadataItem = mockObjectMetadataItem.fields.find( (field) => field.name === 'name', ); @@ -69,6 +74,8 @@ describe('useApplyCurrentViewSortsToCurrentRecordSorts', () => { } satisfies CoreViewWithRelations; it('should apply sorts from current view', () => { + jotaiStore.set(coreViewsState.atom, [mockCoreView]); + const { result } = renderHook( () => { const { applyCurrentViewSortsToCurrentRecordSorts } = @@ -90,9 +97,6 @@ describe('useApplyCurrentViewSortsToCurrentRecordSorts', () => { contextStoreCurrentObjectMetadataNameSingular: mockObjectMetadataItemNameSingular, contextStoreCurrentViewId: mockView.id, - onInitializeRecoilSnapshot: (snapshot) => { - snapshot.set(coreViewsState, [mockCoreView]); - }, }), }, ); @@ -111,6 +115,8 @@ describe('useApplyCurrentViewSortsToCurrentRecordSorts', () => { }); it('should not apply sorts when current view is not found', () => { + jotaiStore.set(coreViewsState.atom, []); + const { result } = renderHook( () => { const { applyCurrentViewSortsToCurrentRecordSorts } = @@ -138,8 +144,6 @@ describe('useApplyCurrentViewSortsToCurrentRecordSorts', () => { }), mockView.id, ); - - snapshot.set(coreViewsState, []); }, }), }, @@ -158,6 +162,8 @@ describe('useApplyCurrentViewSortsToCurrentRecordSorts', () => { viewSorts: [], }; + jotaiStore.set(coreViewsState.atom, [viewWithNoSorts]); + const { result } = renderHook( () => { const { applyCurrentViewSortsToCurrentRecordSorts } = @@ -185,8 +191,6 @@ describe('useApplyCurrentViewSortsToCurrentRecordSorts', () => { }), mockView.id, ); - - snapshot.set(coreViewsState, [viewWithNoSorts]); }, }), }, diff --git a/packages/twenty-front/src/modules/views/hooks/__tests__/useInitializeFilterOnFieldMetadataItemFromViewBarFilterDropdown.test.tsx b/packages/twenty-front/src/modules/views/hooks/__tests__/useInitializeFilterOnFieldMetadataItemFromViewBarFilterDropdown.test.tsx index 3f1e29808d..6f315f1e3d 100644 --- a/packages/twenty-front/src/modules/views/hooks/__tests__/useInitializeFilterOnFieldMetadataItemFromViewBarFilterDropdown.test.tsx +++ b/packages/twenty-front/src/modules/views/hooks/__tests__/useInitializeFilterOnFieldMetadataItemFromViewBarFilterDropdown.test.tsx @@ -1,5 +1,6 @@ import { renderHook } from '@testing-library/react'; import { act } from 'react'; +import { Provider as JotaiProvider } from 'jotai'; import { RecoilRoot } from 'recoil'; import { objectFilterDropdownFilterIsSelectedComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFilterIsSelectedComponentState'; @@ -7,6 +8,7 @@ import { selectedOperandInDropdownComponentState } from '@/object-record/object- import { useInitializeFilterOnFieldMetadataItemFromViewBarFilterDropdown } from '@/views/hooks/useInitializeFilterOnFieldMetadataItemFromViewBarFilterDropdown'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext'; import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemUsedInDropdownComponentSelector'; import { objectFilterDropdownCurrentRecordFilterComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownCurrentRecordFilterComponentState'; @@ -40,23 +42,27 @@ const personCreatedAtFieldMetadataItemMock = (field) => field.name === 'createdAt', ); -const wrapper = ({ children }: { children: React.ReactNode }) => ( - { - snapshot.set(objectMetadataItemsState, generatedMockObjectMetadataItems); - }} - > - - - {children} - - - -); +const wrapper = ({ children }: { children: React.ReactNode }) => { + jotaiStore.set( + objectMetadataItemsState.atom, + generatedMockObjectMetadataItems, + ); + return ( + + + + + {children} + + + + + ); +}; describe('useInitializeFilterOnFieldMetadataItemFromViewBarFilterDropdown', () => { beforeEach(() => { diff --git a/packages/twenty-front/src/modules/views/hooks/internal/usePerformViewAPIUpdate.ts b/packages/twenty-front/src/modules/views/hooks/internal/usePerformViewAPIUpdate.ts index 79a0ae278b..e2218d9191 100644 --- a/packages/twenty-front/src/modules/views/hooks/internal/usePerformViewAPIUpdate.ts +++ b/packages/twenty-front/src/modules/views/hooks/internal/usePerformViewAPIUpdate.ts @@ -6,7 +6,7 @@ import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { coreViewsState } from '@/views/states/coreViewState'; import { ApolloError } from '@apollo/client'; import { t } from '@lingui/core/macro'; -import { useSetRecoilState } from 'recoil'; +import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; import { CrudOperationType } from 'twenty-shared/types'; import { upsertPropertiesOfItemIntoArrayOfObjectsComparingId } from 'twenty-shared/utils'; import { @@ -21,7 +21,7 @@ export const usePerformViewAPIUpdate = () => { const { handleMetadataError } = useMetadataErrorHandler(); const { enqueueErrorSnackBar } = useSnackBar(); - const setCoreViews = useSetRecoilState(coreViewsState); + const setCoreViews = useSetRecoilStateV2(coreViewsState); const performViewAPIUpdate = useCallback( async ( diff --git a/packages/twenty-front/src/modules/views/hooks/useApplyCurrentViewAnyFieldFilterToAnyFieldFilter.ts b/packages/twenty-front/src/modules/views/hooks/useApplyCurrentViewAnyFieldFilterToAnyFieldFilter.ts index 277985f99f..89a4ed8da5 100644 --- a/packages/twenty-front/src/modules/views/hooks/useApplyCurrentViewAnyFieldFilterToAnyFieldFilter.ts +++ b/packages/twenty-front/src/modules/views/hooks/useApplyCurrentViewAnyFieldFilterToAnyFieldFilter.ts @@ -3,7 +3,8 @@ import { anyFieldFilterValueComponentState } from '@/object-record/record-filter import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState'; import { coreViewFromViewIdFamilySelector } from '@/views/states/selectors/coreViewFromViewIdFamilySelector'; -import { useRecoilCallback } from 'recoil'; +import { useStore } from 'jotai'; +import { useCallback } from 'react'; import { isDefined } from 'twenty-shared/utils'; export const useApplyCurrentViewAnyFieldFilterToAnyFieldFilter = () => { @@ -15,23 +16,19 @@ export const useApplyCurrentViewAnyFieldFilterToAnyFieldFilter = () => { anyFieldFilterValueComponentState, ); - const applyCurrentViewAnyFieldFilterToAnyFieldFilter = useRecoilCallback( - ({ snapshot }) => - () => { - const currentView = snapshot - .getLoadable( - coreViewFromViewIdFamilySelector({ - viewId: currentViewId ?? '', - }), - ) - .getValue(); + const store = useStore(); - if (isDefined(currentView)) { - setAnyFieldFilterValue(currentView.anyFieldFilterValue ?? ''); - } - }, - [currentViewId, setAnyFieldFilterValue], - ); + const applyCurrentViewAnyFieldFilterToAnyFieldFilter = useCallback(() => { + const currentView = store.get( + coreViewFromViewIdFamilySelector.selectorFamily({ + viewId: currentViewId ?? '', + }), + ); + + if (isDefined(currentView)) { + setAnyFieldFilterValue(currentView.anyFieldFilterValue ?? ''); + } + }, [currentViewId, setAnyFieldFilterValue, store]); return { applyCurrentViewAnyFieldFilterToAnyFieldFilter, diff --git a/packages/twenty-front/src/modules/views/hooks/useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups.ts b/packages/twenty-front/src/modules/views/hooks/useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups.ts index cc2f4e9dd8..a7f7c574bb 100644 --- a/packages/twenty-front/src/modules/views/hooks/useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups.ts +++ b/packages/twenty-front/src/modules/views/hooks/useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups.ts @@ -5,6 +5,7 @@ import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/ho import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState'; import { coreViewFromViewIdFamilySelector } from '@/views/states/selectors/coreViewFromViewIdFamilySelector'; import { mapViewFilterGroupsToRecordFilterGroups } from '@/views/utils/mapViewFilterGroupsToRecordFilterGroups'; +import { useStore } from 'jotai'; import { useRecoilCallback } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; @@ -22,17 +23,17 @@ export const useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups = const currentRecordFilterGroupsCallbackState = useRecoilComponentCallbackState(currentRecordFilterGroupsComponentState); + const store = useStore(); + const applyCurrentViewFilterGroupsToCurrentRecordFilterGroups = useRecoilCallback( ({ snapshot }) => () => { - const currentView = snapshot - .getLoadable( - coreViewFromViewIdFamilySelector({ - viewId: currentViewId ?? '', - }), - ) - .getValue(); + const currentView = store.get( + coreViewFromViewIdFamilySelector.selectorFamily({ + viewId: currentViewId ?? '', + }), + ); if (isDefined(currentView)) { const currentRecordFilterGroups = snapshot @@ -55,6 +56,7 @@ export const useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups = currentViewId, setCurrentRecordFilterGroups, currentRecordFilterGroupsCallbackState, + store, ], ); diff --git a/packages/twenty-front/src/modules/views/hooks/useApplyCurrentViewFiltersToCurrentRecordFilters.ts b/packages/twenty-front/src/modules/views/hooks/useApplyCurrentViewFiltersToCurrentRecordFilters.ts index c05cfb7843..03c82e6967 100644 --- a/packages/twenty-front/src/modules/views/hooks/useApplyCurrentViewFiltersToCurrentRecordFilters.ts +++ b/packages/twenty-front/src/modules/views/hooks/useApplyCurrentViewFiltersToCurrentRecordFilters.ts @@ -3,7 +3,8 @@ import { currentRecordFiltersComponentState } from '@/object-record/record-filte import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState'; import { coreViewFromViewIdFamilySelector } from '@/views/states/selectors/coreViewFromViewIdFamilySelector'; -import { useRecoilCallback } from 'recoil'; +import { useStore } from 'jotai'; +import { useCallback } from 'react'; import { isDefined } from 'twenty-shared/utils'; import { useMapViewFiltersToFilters } from './useMapViewFiltersToFilters'; @@ -18,25 +19,26 @@ export const useApplyCurrentViewFiltersToCurrentRecordFilters = () => { const { mapViewFiltersToRecordFilters } = useMapViewFiltersToFilters(); - const applyCurrentViewFiltersToCurrentRecordFilters = useRecoilCallback( - ({ snapshot }) => - () => { - const currentView = snapshot - .getLoadable( - coreViewFromViewIdFamilySelector({ - viewId: currentViewId ?? '', - }), - ) - .getValue(); + const store = useStore(); - if (isDefined(currentView)) { - setCurrentRecordFilters( - mapViewFiltersToRecordFilters(currentView.viewFilters), - ); - } - }, - [currentViewId, mapViewFiltersToRecordFilters, setCurrentRecordFilters], - ); + const applyCurrentViewFiltersToCurrentRecordFilters = useCallback(() => { + const currentView = store.get( + coreViewFromViewIdFamilySelector.selectorFamily({ + viewId: currentViewId ?? '', + }), + ); + + if (isDefined(currentView)) { + setCurrentRecordFilters( + mapViewFiltersToRecordFilters(currentView.viewFilters), + ); + } + }, [ + currentViewId, + mapViewFiltersToRecordFilters, + setCurrentRecordFilters, + store, + ]); return { applyCurrentViewFiltersToCurrentRecordFilters, diff --git a/packages/twenty-front/src/modules/views/hooks/useApplyCurrentViewSortsToCurrentRecordSorts.ts b/packages/twenty-front/src/modules/views/hooks/useApplyCurrentViewSortsToCurrentRecordSorts.ts index d3fcb505a1..a9cc847508 100644 --- a/packages/twenty-front/src/modules/views/hooks/useApplyCurrentViewSortsToCurrentRecordSorts.ts +++ b/packages/twenty-front/src/modules/views/hooks/useApplyCurrentViewSortsToCurrentRecordSorts.ts @@ -2,8 +2,8 @@ import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/ import { currentRecordSortsComponentState } from '@/object-record/record-sort/states/currentRecordSortsComponentState'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState'; +import { useFamilySelectorValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilySelectorValueV2'; import { coreViewFromViewIdFamilySelector } from '@/views/states/selectors/coreViewFromViewIdFamilySelector'; -import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; export const useApplyCurrentViewSortsToCurrentRecordSorts = () => { @@ -11,10 +11,9 @@ export const useApplyCurrentViewSortsToCurrentRecordSorts = () => { contextStoreCurrentViewIdComponentState, ); - const currentView = useRecoilValue( - coreViewFromViewIdFamilySelector({ - viewId: currentViewId ?? '', - }), + const currentView = useFamilySelectorValueV2( + coreViewFromViewIdFamilySelector, + { viewId: currentViewId ?? '' }, ); const setCurrentRecordSorts = useSetRecoilComponentState( diff --git a/packages/twenty-front/src/modules/views/hooks/useComputeRecordRelationFilterLabelValue.tsx b/packages/twenty-front/src/modules/views/hooks/useComputeRecordRelationFilterLabelValue.tsx index e4a79829b0..7117a632c5 100644 --- a/packages/twenty-front/src/modules/views/hooks/useComputeRecordRelationFilterLabelValue.tsx +++ b/packages/twenty-front/src/modules/views/hooks/useComputeRecordRelationFilterLabelValue.tsx @@ -12,8 +12,8 @@ import { isDefined, jsonRelationFilterValueSchema, } from 'twenty-shared/utils'; -import { useRecoilValue } from 'recoil'; import { allowRequestsToTwentyIconsState } from '@/client-config/states/allowRequestsToTwentyIcons'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; type ObjectFilterDropdownRecordSelectProps = { recordFilter: RecordFilter; @@ -23,7 +23,7 @@ type ObjectFilterDropdownRecordSelectProps = { export const useComputeRecordRelationFilterLabelValue = ({ recordFilter, }: ObjectFilterDropdownRecordSelectProps) => { - const allowRequestsToTwentyIcons = useRecoilValue( + const allowRequestsToTwentyIcons = useRecoilValueV2( allowRequestsToTwentyIconsState, ); diff --git a/packages/twenty-front/src/modules/views/hooks/useCreateViewFromCurrentView.ts b/packages/twenty-front/src/modules/views/hooks/useCreateViewFromCurrentView.ts index 5f28265c71..4d23d3c39f 100644 --- a/packages/twenty-front/src/modules/views/hooks/useCreateViewFromCurrentView.ts +++ b/packages/twenty-front/src/modules/views/hooks/useCreateViewFromCurrentView.ts @@ -22,6 +22,7 @@ import { duplicateViewFiltersAndViewFilterGroups } from '@/views/utils/duplicate import { mapRecordFilterGroupToViewFilterGroup } from '@/views/utils/mapRecordFilterGroupToViewFilterGroup'; import { mapRecordFilterToViewFilter } from '@/views/utils/mapRecordFilterToViewFilter'; import { mapRecordSortToViewSort } from '@/views/utils/mapRecordSortToViewSort'; +import { useStore } from 'jotai'; import { useRecoilCallback } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { v4 } from 'uuid'; @@ -51,6 +52,8 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => { const { objectMetadataItem } = useRecordIndexContextOrThrow(); + const store = useStore(); + const { refreshCoreViewsByObjectMetadataId } = useRefreshCoreViewsByObjectMetadataId(); @@ -100,13 +103,11 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => { return undefined; } - const sourceView = snapshot - .getLoadable( - coreViewFromViewIdFamilySelector({ - viewId: currentViewId, - }), - ) - .getValue(); + const sourceView = store.get( + coreViewFromViewIdFamilySelector.selectorFamily({ + viewId: currentViewId, + }), + ); if (!isDefined(sourceView)) { return undefined; @@ -244,6 +245,7 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => { objectMetadataItem, performViewFieldAPICreate, refreshCoreViewsByObjectMetadataId, + store, currentRecordFilterGroups, currentRecordFilters, currentRecordSorts, diff --git a/packages/twenty-front/src/modules/views/hooks/useGetCurrentViewOnly.ts b/packages/twenty-front/src/modules/views/hooks/useGetCurrentViewOnly.ts index f783f74420..cf7d92d556 100644 --- a/packages/twenty-front/src/modules/views/hooks/useGetCurrentViewOnly.ts +++ b/packages/twenty-front/src/modules/views/hooks/useGetCurrentViewOnly.ts @@ -1,18 +1,16 @@ import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; +import { useFamilySelectorValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilySelectorValueV2'; import { coreViewFromViewIdFamilySelector } from '@/views/states/selectors/coreViewFromViewIdFamilySelector'; -import { useRecoilValue } from 'recoil'; - export const useGetCurrentViewOnly = () => { const currentViewId = useRecoilComponentValue( contextStoreCurrentViewIdComponentState, ); - const currentView = useRecoilValue( - coreViewFromViewIdFamilySelector({ - viewId: currentViewId ?? '', - }), + const currentView = useFamilySelectorValueV2( + coreViewFromViewIdFamilySelector, + { viewId: currentViewId ?? '' }, ); return { diff --git a/packages/twenty-front/src/modules/views/hooks/useGetViewById.ts b/packages/twenty-front/src/modules/views/hooks/useGetViewById.ts index 717ab69399..1842a7fa30 100644 --- a/packages/twenty-front/src/modules/views/hooks/useGetViewById.ts +++ b/packages/twenty-front/src/modules/views/hooks/useGetViewById.ts @@ -1,12 +1,10 @@ +import { useFamilySelectorValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilySelectorValueV2'; import { coreViewFromViewIdFamilySelector } from '@/views/states/selectors/coreViewFromViewIdFamilySelector'; -import { useRecoilValue } from 'recoil'; export const useGetViewById = (viewId: string | null) => { - const view = useRecoilValue( - coreViewFromViewIdFamilySelector({ - viewId: viewId ?? '', - }), - ); + const view = useFamilySelectorValueV2(coreViewFromViewIdFamilySelector, { + viewId: viewId ?? '', + }); return { view: viewId ? view : undefined, diff --git a/packages/twenty-front/src/modules/views/hooks/useGetViewFromPrefetchState.ts b/packages/twenty-front/src/modules/views/hooks/useGetViewFromPrefetchState.ts index ba88eb8e12..fb57705a3a 100644 --- a/packages/twenty-front/src/modules/views/hooks/useGetViewFromPrefetchState.ts +++ b/packages/twenty-front/src/modules/views/hooks/useGetViewFromPrefetchState.ts @@ -1,21 +1,17 @@ import { coreViewFromViewIdFamilySelector } from '@/views/states/selectors/coreViewFromViewIdFamilySelector'; -import { useRecoilCallback } from 'recoil'; +import { useStore } from 'jotai'; +import { useCallback } from 'react'; export const useGetViewFromPrefetchState = () => { - const getViewFromPrefetchState = useRecoilCallback( - ({ snapshot }) => - (viewId: string) => { - const view = snapshot - .getLoadable( - coreViewFromViewIdFamilySelector({ - viewId: viewId, - }), - ) - .getValue(); + const store = useStore(); - return view; - }, - [], + const getViewFromPrefetchState = useCallback( + (viewId: string) => { + return store.get( + coreViewFromViewIdFamilySelector.selectorFamily({ viewId }), + ); + }, + [store], ); return { diff --git a/packages/twenty-front/src/modules/views/hooks/useRefreshAllCoreViews.ts b/packages/twenty-front/src/modules/views/hooks/useRefreshAllCoreViews.ts index da5564a3d6..ead5a1121c 100644 --- a/packages/twenty-front/src/modules/views/hooks/useRefreshAllCoreViews.ts +++ b/packages/twenty-front/src/modules/views/hooks/useRefreshAllCoreViews.ts @@ -1,7 +1,8 @@ import { FIND_ALL_CORE_VIEWS } from '@/views/graphql/queries/findAllCoreViews'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { coreViewsState } from '@/views/states/coreViewState'; import { type FetchPolicy, useApolloClient } from '@apollo/client'; -import { useRecoilCallback } from 'recoil'; +import { useCallback } from 'react'; import { isDefined } from 'twenty-shared/utils'; import { type FindAllCoreViewsQuery } from '~/generated-metadata/graphql'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; @@ -11,32 +12,26 @@ export const useRefreshAllCoreViews = ( ) => { const client = useApolloClient(); - const refreshAllCoreViews = useRecoilCallback( - ({ snapshot, set }) => - async () => { - const result = await client.query({ - query: FIND_ALL_CORE_VIEWS, - variables: {}, - fetchPolicy, - }); + const refreshAllCoreViews = useCallback(async () => { + const result = await client.query({ + query: FIND_ALL_CORE_VIEWS, + variables: {}, + fetchPolicy, + }); - const currentCoreViews = snapshot - .getLoadable(coreViewsState) - .getValue(); + const currentCoreViews = jotaiStore.get(coreViewsState.atom); - const coreViewsFromResult = result.data.getCoreViews; + const coreViewsFromResult = result.data.getCoreViews; - if ( - isDefined(result.data?.getCoreViews) && - !isDeeplyEqual(currentCoreViews, coreViewsFromResult) - ) { - set(coreViewsState, coreViewsFromResult); - } + if ( + isDefined(result.data?.getCoreViews) && + !isDeeplyEqual(currentCoreViews, coreViewsFromResult) + ) { + jotaiStore.set(coreViewsState.atom, coreViewsFromResult); + } - return result.data?.getCoreViews; - }, - [client, fetchPolicy], - ); + return result.data?.getCoreViews; + }, [client, fetchPolicy]); return { refreshAllCoreViews, diff --git a/packages/twenty-front/src/modules/views/hooks/useRefreshCoreViewsByObjectMetadataId.ts b/packages/twenty-front/src/modules/views/hooks/useRefreshCoreViewsByObjectMetadataId.ts index 78cc4cbf3b..ab610bcd67 100644 --- a/packages/twenty-front/src/modules/views/hooks/useRefreshCoreViewsByObjectMetadataId.ts +++ b/packages/twenty-front/src/modules/views/hooks/useRefreshCoreViewsByObjectMetadataId.ts @@ -4,6 +4,7 @@ import { currentRecordFiltersComponentState } from '@/object-record/record-filte import { recordIndexShouldHideEmptyRecordGroupsComponentState } from '@/object-record/record-index/states/recordIndexShouldHideEmptyRecordGroupsComponentState'; import { currentRecordSortsComponentState } from '@/object-record/record-sort/states/currentRecordSortsComponentState'; import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/utils/getRecordIndexIdFromObjectNamePluralAndViewId'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { coreViewsByObjectMetadataIdFamilySelector } from '@/views/states/selectors/coreViewsByObjectMetadataIdFamilySelector'; import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView'; import { getFilterableFields } from '@/views/utils/getFilterableFields'; @@ -19,7 +20,7 @@ export const useRefreshCoreViewsByObjectMetadataId = () => { const [findManyCoreViewsLazy] = useFindManyCoreViewsLazyQuery(); const refreshCoreViewsByObjectMetadataId = useRecoilCallback( - ({ snapshot, set }) => + ({ set }) => async (objectMetadataId: string) => { const result = await findManyCoreViewsLazy({ variables: { @@ -32,9 +33,9 @@ export const useRefreshCoreViewsByObjectMetadataId = () => { return; } - const objectMetadataItems = snapshot - .getLoadable(objectMetadataItemsState) - .getValue(); + const objectMetadataItems = jotaiStore.get( + objectMetadataItemsState.atom, + ); const objectMetadataItem = objectMetadataItems.find( (objectMetadataItem) => objectMetadataItem.id === objectMetadataId, @@ -44,11 +45,11 @@ export const useRefreshCoreViewsByObjectMetadataId = () => { return; } - const coreViewsForObjectMetadataId = snapshot - .getLoadable( - coreViewsByObjectMetadataIdFamilySelector(objectMetadataId), - ) - .getValue(); + const coreViewsForObjectMetadataId = jotaiStore.get( + coreViewsByObjectMetadataIdFamilySelector.selectorFamily( + objectMetadataId, + ), + ); const coreViewsFromResult = result.data.getCoreViews; @@ -56,8 +57,10 @@ export const useRefreshCoreViewsByObjectMetadataId = () => { return; } - set( - coreViewsByObjectMetadataIdFamilySelector(objectMetadataId), + jotaiStore.set( + coreViewsByObjectMetadataIdFamilySelector.selectorFamily( + objectMetadataId, + ), coreViewsFromResult, ); diff --git a/packages/twenty-front/src/modules/views/hooks/useUpdateCurrentView.ts b/packages/twenty-front/src/modules/views/hooks/useUpdateCurrentView.ts index 2384114ff5..f0cd1d4ae1 100644 --- a/packages/twenty-front/src/modules/views/hooks/useUpdateCurrentView.ts +++ b/packages/twenty-front/src/modules/views/hooks/useUpdateCurrentView.ts @@ -1,3 +1,4 @@ +import { useStore } from 'jotai'; import { useRecoilCallback, useSetRecoilState } from 'recoil'; import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow'; @@ -27,6 +28,8 @@ export const useUpdateCurrentView = () => { const { loadRecordIndexStates } = useLoadRecordIndexStates(); const setRecordIndexViewType = useSetRecoilState(recordIndexViewTypeState); + const store = useStore(); + const [updateOneCoreView] = useUpdateCoreViewMutation(); const { refreshCoreViewsByObjectMetadataId } = useRefreshCoreViewsByObjectMetadataId(); @@ -52,9 +55,12 @@ export const useUpdateCurrentView = () => { if (newMainGroupByFieldMetadataId !== null) { viewGroupsToCreate = objectMetadataItem.fields - ?.find((field) => field.id === newMainGroupByFieldMetadataId) + ?.find( + (field: { id: string }) => + field.id === newMainGroupByFieldMetadataId, + ) ?.options?.map( - (option, index) => + (option: { value: string }, index: number) => ({ id: v4(), __typename: 'ViewGroup', @@ -66,7 +72,8 @@ export const useUpdateCurrentView = () => { if ( objectMetadataItem.fields.find( - (field) => field.id === newMainGroupByFieldMetadataId, + (field: { id: string }) => + field.id === newMainGroupByFieldMetadataId, )?.isNullable === true ) { viewGroupsToCreate.push({ @@ -95,13 +102,11 @@ export const useUpdateCurrentView = () => { .getLoadable(currentViewIdCallbackState) .getValue(); - const currentView = snapshot - .getLoadable( - coreViewFromViewIdFamilySelector({ - viewId: currentViewId ?? '', - }), - ) - .getValue(); + const currentView = store.get( + coreViewFromViewIdFamilySelector.selectorFamily({ + viewId: currentViewId ?? '', + }), + ); if (!isDefined(currentView)) { return; @@ -154,6 +159,7 @@ export const useUpdateCurrentView = () => { objectMetadataItem, refreshCoreViewsByObjectMetadataId, setRecordIndexViewType, + store, updateOneCoreView, ], ); diff --git a/packages/twenty-front/src/modules/views/hooks/useUpdateViewAggregate.ts b/packages/twenty-front/src/modules/views/hooks/useUpdateViewAggregate.ts index 8a21b27bbe..89a883906a 100644 --- a/packages/twenty-front/src/modules/views/hooks/useUpdateViewAggregate.ts +++ b/packages/twenty-front/src/modules/views/hooks/useUpdateViewAggregate.ts @@ -4,11 +4,12 @@ import { useLoadRecordIndexStates } from '@/object-record/record-index/hooks/use import { type ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations'; import { convertExtendedAggregateOperationToAggregateOperation } from '@/object-record/utils/convertExtendedAggregateOperationToAggregateOperation'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { usePerformViewAPIUpdate } from '@/views/hooks/internal/usePerformViewAPIUpdate'; import { useCanPersistViewChanges } from '@/views/hooks/useCanPersistViewChanges'; import { coreViewsState } from '@/views/states/coreViewState'; import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView'; -import { useRecoilCallback } from 'recoil'; +import { useCallback } from 'react'; import { isDefined, upsertIntoArrayOfObjectsComparingId, @@ -23,61 +24,60 @@ export const useUpdateViewAggregate = () => { const { performViewAPIUpdate } = usePerformViewAPIUpdate(); const { loadRecordIndexStates } = useLoadRecordIndexStates(); - const updateViewAggregate = useRecoilCallback( - ({ set }) => - async ({ - kanbanAggregateOperationFieldMetadataId, + const updateViewAggregate = useCallback( + async ({ + kanbanAggregateOperationFieldMetadataId, + kanbanAggregateOperation, + objectMetadataItem, + }: { + kanbanAggregateOperationFieldMetadataId: string | null; + kanbanAggregateOperation: ExtendedAggregateOperations | null; + objectMetadataItem: ObjectMetadataItem; + }) => { + if (!canPersistChanges) { + return; + } + + const convertedKanbanAggregateOperation = isDefined( kanbanAggregateOperation, - objectMetadataItem, - }: { - kanbanAggregateOperationFieldMetadataId: string | null; - kanbanAggregateOperation: ExtendedAggregateOperations | null; - objectMetadataItem: ObjectMetadataItem; - }) => { - if (!canPersistChanges) { + ) + ? convertExtendedAggregateOperationToAggregateOperation( + kanbanAggregateOperation, + ) + : null; + + if (!isDefined(currentViewId)) { + return; + } + + const updatedViewResult = await performViewAPIUpdate({ + id: currentViewId, + input: { + kanbanAggregateOperationFieldMetadataId, + kanbanAggregateOperation: convertedKanbanAggregateOperation, + }, + }); + + if (updatedViewResult.status === 'successful') { + const updatedCoreView = updatedViewResult.response.data + ?.updateCoreView as CoreView; + + if (!isDefined(updatedCoreView)) { return; } - const convertedKanbanAggregateOperation = isDefined( - kanbanAggregateOperation, - ) - ? convertExtendedAggregateOperationToAggregateOperation( - kanbanAggregateOperation, - ) - : null; + jotaiStore.set(coreViewsState.atom, (currentCoreViews) => + upsertIntoArrayOfObjectsComparingId( + currentCoreViews, + updatedCoreView, + ), + ); - if (!isDefined(currentViewId)) { - return; - } + const updatedView = convertCoreViewToView(updatedCoreView); - const updatedViewResult = await performViewAPIUpdate({ - id: currentViewId, - input: { - kanbanAggregateOperationFieldMetadataId, - kanbanAggregateOperation: convertedKanbanAggregateOperation, - }, - }); - - if (updatedViewResult.status === 'successful') { - const updatedCoreView = updatedViewResult.response.data - ?.updateCoreView as CoreView; - - if (!isDefined(updatedCoreView)) { - return; - } - - set(coreViewsState, (currentCoreViews) => - upsertIntoArrayOfObjectsComparingId( - currentCoreViews, - updatedCoreView, - ), - ); - - const updatedView = convertCoreViewToView(updatedCoreView); - - loadRecordIndexStates(updatedView, objectMetadataItem); - } - }, + loadRecordIndexStates(updatedView, objectMetadataItem); + } + }, [ canPersistChanges, currentViewId, diff --git a/packages/twenty-front/src/modules/views/hooks/useViewOrDefaultViewFromPrefetchedViews.ts b/packages/twenty-front/src/modules/views/hooks/useViewOrDefaultViewFromPrefetchedViews.ts index 9c592c4063..ca8a653385 100644 --- a/packages/twenty-front/src/modules/views/hooks/useViewOrDefaultViewFromPrefetchedViews.ts +++ b/packages/twenty-front/src/modules/views/hooks/useViewOrDefaultViewFromPrefetchedViews.ts @@ -1,23 +1,20 @@ +import { useFamilySelectorValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilySelectorValueV2'; import { coreIndexViewIdFromObjectMetadataItemFamilySelector } from '@/views/states/selectors/coreIndexViewIdFromObjectMetadataItemFamilySelector'; import { coreViewFromViewIdFamilySelector } from '@/views/states/selectors/coreViewFromViewIdFamilySelector'; -import { useRecoilValue } from 'recoil'; export const useViewOrDefaultViewFromPrefetchedViews = ({ objectMetadataItemId, }: { objectMetadataItemId: string; }) => { - const indexViewId = useRecoilValue( - coreIndexViewIdFromObjectMetadataItemFamilySelector({ - objectMetadataItemId, - }), + const indexViewId = useFamilySelectorValueV2( + coreIndexViewIdFromObjectMetadataItemFamilySelector, + { objectMetadataItemId }, ); - const indexView = useRecoilValue( - coreViewFromViewIdFamilySelector({ - viewId: indexViewId ?? '', - }), - ); + const indexView = useFamilySelectorValueV2(coreViewFromViewIdFamilySelector, { + viewId: indexViewId ?? '', + }); return { view: indexView }; }; diff --git a/packages/twenty-front/src/modules/views/hooks/useViewsSideEffectsOnViewGroups.ts b/packages/twenty-front/src/modules/views/hooks/useViewsSideEffectsOnViewGroups.ts index f522a5ccc6..e74d81d74e 100644 --- a/packages/twenty-front/src/modules/views/hooks/useViewsSideEffectsOnViewGroups.ts +++ b/packages/twenty-front/src/modules/views/hooks/useViewsSideEffectsOnViewGroups.ts @@ -1,7 +1,7 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { useTriggerViewGroupOptimisticEffect } from '@/views/optimistic-effects/hooks/useTriggerViewGroupOptimisticEffect'; import { type ViewGroup } from '@/views/types/ViewGroup'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { isDefined } from 'twenty-shared/utils'; import { v4 } from 'uuid'; import { type CoreViewGroup } from '~/generated-metadata/graphql'; @@ -10,7 +10,7 @@ const useViewsSideEffectsOnViewGroups = () => { const { triggerViewGroupOptimisticEffect } = useTriggerViewGroupOptimisticEffect(); - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); const triggerViewGroupOptimisticEffectAtViewCreation = ({ newViewId, diff --git a/packages/twenty-front/src/modules/views/optimistic-effects/hooks/useTriggerViewFieldGroupOptimisticEffect.ts b/packages/twenty-front/src/modules/views/optimistic-effects/hooks/useTriggerViewFieldGroupOptimisticEffect.ts index bb4c8fa560..38a327577c 100644 --- a/packages/twenty-front/src/modules/views/optimistic-effects/hooks/useTriggerViewFieldGroupOptimisticEffect.ts +++ b/packages/twenty-front/src/modules/views/optimistic-effects/hooks/useTriggerViewFieldGroupOptimisticEffect.ts @@ -1,8 +1,8 @@ -import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { coreViewsState } from '@/views/states/coreViewState'; import { type CoreViewWithRelations } from '@/views/types/CoreViewWithRelations'; import { useApolloClient } from '@apollo/client'; -import { useRecoilCallback } from 'recoil'; +import { useCallback } from 'react'; import { isDefined } from 'twenty-shared/utils'; import { type CoreViewFieldGroup } from '~/generated-metadata/graphql'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; @@ -17,142 +17,139 @@ export const useTriggerViewFieldGroupOptimisticEffect = () => { const cache = apolloClient.cache; - const triggerViewFieldGroupOptimisticEffect = useRecoilCallback( - ({ set, snapshot }) => - ({ - createdViewFieldGroups = [], - updatedViewFieldGroups = [], - deletedViewFieldGroups = [], - }: { - createdViewFieldGroups?: ViewFieldGroupForOptimisticEffect[]; - updatedViewFieldGroups?: ViewFieldGroupForOptimisticEffect[]; - deletedViewFieldGroups?: Pick[]; - }) => { - const coreViews = getSnapshotValue(snapshot, coreViewsState); - let newCoreViews = [...coreViews]; + const triggerViewFieldGroupOptimisticEffect = useCallback( + ({ + createdViewFieldGroups = [], + updatedViewFieldGroups = [], + deletedViewFieldGroups = [], + }: { + createdViewFieldGroups?: ViewFieldGroupForOptimisticEffect[]; + updatedViewFieldGroups?: ViewFieldGroupForOptimisticEffect[]; + deletedViewFieldGroups?: Pick[]; + }) => { + const coreViews = jotaiStore.get(coreViewsState.atom); + let newCoreViews = [...coreViews]; - createdViewFieldGroups.forEach((createdViewFieldGroup) => { - cache.modify({ - id: cache.identify({ - __typename: 'CoreView', - id: createdViewFieldGroup.viewId, - }), - fields: { - viewFieldGroups: (existingViewFieldGroups, { toReference }) => [ - ...(existingViewFieldGroups ?? []), - toReference(createdViewFieldGroup), + createdViewFieldGroups.forEach((createdViewFieldGroup) => { + cache.modify({ + id: cache.identify({ + __typename: 'CoreView', + id: createdViewFieldGroup.viewId, + }), + fields: { + viewFieldGroups: (existingViewFieldGroups, { toReference }) => [ + ...(existingViewFieldGroups ?? []), + toReference(createdViewFieldGroup), + ], + }, + }); + const toBeModifiedCoreView = newCoreViews.find( + (coreView) => coreView.id === createdViewFieldGroup.viewId, + ); + if (isDefined(toBeModifiedCoreView)) { + newCoreViews = [ + ...newCoreViews.filter( + (coreView) => coreView.id !== createdViewFieldGroup.viewId, + ), + { + ...toBeModifiedCoreView, + viewFieldGroups: [ + ...(toBeModifiedCoreView.viewFieldGroups ?? []), + createdViewFieldGroup, ], }, - }); - const toBeModifiedCoreView = newCoreViews.find( - (coreView) => coreView.id === createdViewFieldGroup.viewId, - ); - if (isDefined(toBeModifiedCoreView)) { - newCoreViews = [ - ...newCoreViews.filter( - (coreView) => coreView.id !== createdViewFieldGroup.viewId, - ), - { - ...toBeModifiedCoreView, - viewFieldGroups: [ - ...(toBeModifiedCoreView.viewFieldGroups ?? []), - createdViewFieldGroup, - ], - }, - ]; - } - }); + ]; + } + }); - updatedViewFieldGroups.forEach((updatedViewFieldGroup) => { + updatedViewFieldGroups.forEach((updatedViewFieldGroup) => { + cache.modify({ + id: cache.identify({ + __typename: 'CoreView', + id: updatedViewFieldGroup.viewId, + }), + fields: { + viewFieldGroups: ( + existingViewFieldGroups, + { readField, toReference }, + ) => + existingViewFieldGroups?.map((viewFieldGroup) => { + const viewFieldGroupId = readField( + 'id', + viewFieldGroup, + ); + if (viewFieldGroupId === updatedViewFieldGroup.id) { + return toReference(updatedViewFieldGroup); + } + return viewFieldGroup; + }) ?? [], + }, + }); + const toBeModifiedCoreView = newCoreViews.find( + (coreView) => coreView.id === updatedViewFieldGroup.viewId, + ); + if (isDefined(toBeModifiedCoreView)) { + newCoreViews = [ + ...newCoreViews.filter( + (coreView) => coreView.id !== updatedViewFieldGroup.viewId, + ), + { + ...toBeModifiedCoreView, + viewFieldGroups: [ + ...(toBeModifiedCoreView.viewFieldGroups ?? []).filter( + (viewFieldGroup) => + viewFieldGroup.id !== updatedViewFieldGroup.id, + ), + updatedViewFieldGroup, + ], + }, + ]; + } + }); + + deletedViewFieldGroups.forEach( + (deletedViewFieldGroup: Pick) => { cache.modify({ id: cache.identify({ __typename: 'CoreView', - id: updatedViewFieldGroup.viewId, + id: deletedViewFieldGroup.viewId, }), fields: { - viewFieldGroups: ( - existingViewFieldGroups, - { readField, toReference }, - ) => - existingViewFieldGroups?.map((viewFieldGroup) => { - const viewFieldGroupId = readField( - 'id', - viewFieldGroup, - ); - if (viewFieldGroupId === updatedViewFieldGroup.id) { - return toReference(updatedViewFieldGroup); - } - return viewFieldGroup; - }) ?? [], + viewFieldGroups: (existingViewFieldGroups, { readField }) => + existingViewFieldGroups?.filter( + (viewFieldGroup) => + readField('id', viewFieldGroup) !== + deletedViewFieldGroup.id, + ) ?? [], }, }); const toBeModifiedCoreView = newCoreViews.find( - (coreView) => coreView.id === updatedViewFieldGroup.viewId, + (coreView) => coreView.id === deletedViewFieldGroup.viewId, ); + if (isDefined(toBeModifiedCoreView)) { newCoreViews = [ ...newCoreViews.filter( - (coreView) => coreView.id !== updatedViewFieldGroup.viewId, + (coreView) => coreView.id !== deletedViewFieldGroup.viewId, ), { ...toBeModifiedCoreView, - viewFieldGroups: [ - ...(toBeModifiedCoreView.viewFieldGroups ?? []).filter( - (viewFieldGroup) => - viewFieldGroup.id !== updatedViewFieldGroup.id, - ), - updatedViewFieldGroup, - ], + viewFieldGroups: ( + toBeModifiedCoreView.viewFieldGroups ?? [] + ).filter( + (viewFieldGroup) => + viewFieldGroup.id !== deletedViewFieldGroup.id, + ), }, ]; } - }); + }, + ); - deletedViewFieldGroups.forEach( - ( - deletedViewFieldGroup: Pick, - ) => { - cache.modify({ - id: cache.identify({ - __typename: 'CoreView', - id: deletedViewFieldGroup.viewId, - }), - fields: { - viewFieldGroups: (existingViewFieldGroups, { readField }) => - existingViewFieldGroups?.filter( - (viewFieldGroup) => - readField('id', viewFieldGroup) !== - deletedViewFieldGroup.id, - ) ?? [], - }, - }); - const toBeModifiedCoreView = newCoreViews.find( - (coreView) => coreView.id === deletedViewFieldGroup.viewId, - ); - - if (isDefined(toBeModifiedCoreView)) { - newCoreViews = [ - ...newCoreViews.filter( - (coreView) => coreView.id !== deletedViewFieldGroup.viewId, - ), - { - ...toBeModifiedCoreView, - viewFieldGroups: ( - toBeModifiedCoreView.viewFieldGroups ?? [] - ).filter( - (viewFieldGroup) => - viewFieldGroup.id !== deletedViewFieldGroup.id, - ), - }, - ]; - } - }, - ); - - if (!isDeeplyEqual(coreViews, newCoreViews)) { - set(coreViewsState, newCoreViews); - } - }, + if (!isDeeplyEqual(coreViews, newCoreViews)) { + jotaiStore.set(coreViewsState.atom, newCoreViews); + } + }, [cache], ); diff --git a/packages/twenty-front/src/modules/views/optimistic-effects/hooks/useTriggerViewFieldOptimisticEffect.ts b/packages/twenty-front/src/modules/views/optimistic-effects/hooks/useTriggerViewFieldOptimisticEffect.ts index 7c312bcabd..867e52e7b2 100644 --- a/packages/twenty-front/src/modules/views/optimistic-effects/hooks/useTriggerViewFieldOptimisticEffect.ts +++ b/packages/twenty-front/src/modules/views/optimistic-effects/hooks/useTriggerViewFieldOptimisticEffect.ts @@ -1,8 +1,8 @@ -import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { coreViewsState } from '@/views/states/coreViewState'; import { type CoreViewWithRelations } from '@/views/types/CoreViewWithRelations'; import { useApolloClient } from '@apollo/client'; -import { useRecoilCallback } from 'recoil'; +import { useCallback } from 'react'; import { isDefined } from 'twenty-shared/utils'; import { type CoreViewField } from '~/generated-metadata/graphql'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; @@ -12,129 +12,128 @@ export const useTriggerViewFieldOptimisticEffect = () => { const cache = apolloClient.cache; - const triggerViewFieldOptimisticEffect = useRecoilCallback( - ({ set, snapshot }) => - ({ - createdViewFields = [], - updatedViewFields = [], - deletedViewFields = [], - }: { - createdViewFields?: Omit[]; - updatedViewFields?: Omit[]; - deletedViewFields?: Pick[]; - }) => { - const coreViews = getSnapshotValue(snapshot, coreViewsState); - let newCoreViews = [...coreViews]; + const triggerViewFieldOptimisticEffect = useCallback( + ({ + createdViewFields = [], + updatedViewFields = [], + deletedViewFields = [], + }: { + createdViewFields?: Omit[]; + updatedViewFields?: Omit[]; + deletedViewFields?: Pick[]; + }) => { + const coreViews = jotaiStore.get(coreViewsState.atom); + let newCoreViews = [...coreViews]; - createdViewFields.forEach((createdViewField) => { - cache.modify({ - id: cache.identify({ - __typename: 'CoreView', - id: createdViewField.viewId, - }), - fields: { - viewFields: (existingViewFields, { toReference }) => [ - ...(existingViewFields ?? []), - toReference(createdViewField), + createdViewFields.forEach((createdViewField) => { + cache.modify({ + id: cache.identify({ + __typename: 'CoreView', + id: createdViewField.viewId, + }), + fields: { + viewFields: (existingViewFields, { toReference }) => [ + ...(existingViewFields ?? []), + toReference(createdViewField), + ], + }, + }); + const toBeModifiedCoreView = newCoreViews.find( + (coreView) => coreView.id === createdViewField.viewId, + ); + if (isDefined(toBeModifiedCoreView)) { + newCoreViews = [ + ...newCoreViews.filter( + (coreView) => coreView.id !== createdViewField.viewId, + ), + { + ...toBeModifiedCoreView, + viewFields: [ + ...toBeModifiedCoreView.viewFields, + createdViewField, ], }, - }); - const toBeModifiedCoreView = newCoreViews.find( - (coreView) => coreView.id === createdViewField.viewId, - ); - if (isDefined(toBeModifiedCoreView)) { - newCoreViews = [ - ...newCoreViews.filter( - (coreView) => coreView.id !== createdViewField.viewId, - ), - { - ...toBeModifiedCoreView, - viewFields: [ - ...toBeModifiedCoreView.viewFields, - createdViewField, - ], - }, - ]; - } - }); + ]; + } + }); - updatedViewFields.forEach((updatedViewField) => { + updatedViewFields.forEach((updatedViewField) => { + cache.modify({ + id: cache.identify({ + __typename: 'CoreView', + id: updatedViewField.viewId, + }), + fields: { + viewFields: (existingViewFields, { readField, toReference }) => + existingViewFields.map((viewField) => { + const viewFieldId = readField('id', viewField); + if (viewFieldId === updatedViewField.id) { + return toReference(updatedViewField); + } + return viewField; + }), + }, + }); + const toBeModifiedCoreView = newCoreViews.find( + (coreView) => coreView.id === updatedViewField.viewId, + ); + if (isDefined(toBeModifiedCoreView)) { + newCoreViews = [ + ...newCoreViews.filter( + (coreView) => coreView.id !== updatedViewField.viewId, + ), + { + ...toBeModifiedCoreView, + viewFields: [ + ...toBeModifiedCoreView.viewFields.filter( + (viewField) => viewField.id !== updatedViewField.id, + ), + updatedViewField, + ], + }, + ]; + } + }); + + deletedViewFields.forEach( + (deletedViewField: Pick) => { cache.modify({ id: cache.identify({ __typename: 'CoreView', - id: updatedViewField.viewId, + id: deletedViewField.viewId, }), fields: { - viewFields: (existingViewFields, { readField, toReference }) => - existingViewFields.map((viewField) => { - const viewFieldId = readField('id', viewField); - if (viewFieldId === updatedViewField.id) { - return toReference(updatedViewField); - } - return viewField; - }), + viewFields: (existingViewFields, { readField }) => + existingViewFields.filter( + (viewField) => + readField('id', viewField) !== deletedViewField.id, + ), }, }); const toBeModifiedCoreView = newCoreViews.find( - (coreView) => coreView.id === updatedViewField.viewId, + (coreView) => coreView.id === deletedViewField.viewId, ); + if (isDefined(toBeModifiedCoreView)) { newCoreViews = [ ...newCoreViews.filter( - (coreView) => coreView.id !== updatedViewField.viewId, + (coreView) => coreView.id !== deletedViewField.viewId, ), { ...toBeModifiedCoreView, - viewFields: [ - ...toBeModifiedCoreView.viewFields.filter( - (viewField) => viewField.id !== updatedViewField.id, - ), - updatedViewField, - ], + viewFields: toBeModifiedCoreView.viewFields.filter( + (viewField) => viewField.id !== deletedViewField.id, + ), }, ]; } - }); + }, + ); - deletedViewFields.forEach( - (deletedViewField: Pick) => { - cache.modify({ - id: cache.identify({ - __typename: 'CoreView', - id: deletedViewField.viewId, - }), - fields: { - viewFields: (existingViewFields, { readField }) => - existingViewFields.filter( - (viewField) => - readField('id', viewField) !== deletedViewField.id, - ), - }, - }); - const toBeModifiedCoreView = newCoreViews.find( - (coreView) => coreView.id === deletedViewField.viewId, - ); - - if (isDefined(toBeModifiedCoreView)) { - newCoreViews = [ - ...newCoreViews.filter( - (coreView) => coreView.id !== deletedViewField.viewId, - ), - { - ...toBeModifiedCoreView, - viewFields: toBeModifiedCoreView.viewFields.filter( - (viewField) => viewField.id !== deletedViewField.id, - ), - }, - ]; - } - }, - ); - - if (!isDeeplyEqual(coreViews, newCoreViews)) { - set(coreViewsState, newCoreViews); - } - }, + if (!isDeeplyEqual(coreViews, newCoreViews)) { + jotaiStore.set(coreViewsState.atom, newCoreViews); + } + }, [cache], ); diff --git a/packages/twenty-front/src/modules/views/optimistic-effects/hooks/useTriggerViewFilterOptimisticEffect.ts b/packages/twenty-front/src/modules/views/optimistic-effects/hooks/useTriggerViewFilterOptimisticEffect.ts index c2ff4ee303..b4750e1b00 100644 --- a/packages/twenty-front/src/modules/views/optimistic-effects/hooks/useTriggerViewFilterOptimisticEffect.ts +++ b/packages/twenty-front/src/modules/views/optimistic-effects/hooks/useTriggerViewFilterOptimisticEffect.ts @@ -1,8 +1,8 @@ -import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { coreViewsState } from '@/views/states/coreViewState'; import { type CoreViewWithRelations } from '@/views/types/CoreViewWithRelations'; import { useApolloClient } from '@apollo/client'; -import { useRecoilCallback } from 'recoil'; +import { useCallback } from 'react'; import { isDefined } from 'twenty-shared/utils'; import { type CoreViewFilter } from '~/generated-metadata/graphql'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; @@ -16,125 +16,124 @@ export const useTriggerViewFilterOptimisticEffect = () => { const cache = apolloClient.cache; - const triggerViewFilterOptimisticEffect = useRecoilCallback( - ({ set, snapshot }) => - ({ - createdViewFilters = [], - updatedViewFilters = [], - deletedViewFilters = [], - }: UpdatedDeletedCoreViewFilter) => { - const coreViews = getSnapshotValue(snapshot, coreViewsState); - let newCoreViews = [...coreViews]; + const triggerViewFilterOptimisticEffect = useCallback( + ({ + createdViewFilters = [], + updatedViewFilters = [], + deletedViewFilters = [], + }: UpdatedDeletedCoreViewFilter) => { + const coreViews = jotaiStore.get(coreViewsState.atom); + let newCoreViews = [...coreViews]; - createdViewFilters.forEach((createdViewFilter) => { - cache.modify({ - id: cache.identify({ - __typename: 'CoreView', - id: createdViewFilter.viewId, - }), - fields: { - viewFilters: (existingViewFilters, { toReference }) => [ - ...(existingViewFilters ?? []), - toReference(createdViewFilter), + createdViewFilters.forEach((createdViewFilter) => { + cache.modify({ + id: cache.identify({ + __typename: 'CoreView', + id: createdViewFilter.viewId, + }), + fields: { + viewFilters: (existingViewFilters, { toReference }) => [ + ...(existingViewFilters ?? []), + toReference(createdViewFilter), + ], + }, + }); + const toBeModifiedCoreView = newCoreViews.find( + (coreView) => coreView.id === createdViewFilter.viewId, + ); + if (isDefined(toBeModifiedCoreView)) { + newCoreViews = [ + ...newCoreViews.filter( + (coreView) => coreView.id !== createdViewFilter.viewId, + ), + { + ...toBeModifiedCoreView, + viewFilters: [ + ...toBeModifiedCoreView.viewFilters, + createdViewFilter, ], }, - }); - const toBeModifiedCoreView = newCoreViews.find( - (coreView) => coreView.id === createdViewFilter.viewId, - ); - if (isDefined(toBeModifiedCoreView)) { - newCoreViews = [ - ...newCoreViews.filter( - (coreView) => coreView.id !== createdViewFilter.viewId, - ), - { - ...toBeModifiedCoreView, - viewFilters: [ - ...toBeModifiedCoreView.viewFilters, - createdViewFilter, - ], - }, - ]; - } - }); + ]; + } + }); - updatedViewFilters.forEach((updatedViewFilter) => { + updatedViewFilters.forEach((updatedViewFilter) => { + cache.modify({ + id: cache.identify({ + __typename: 'CoreView', + id: updatedViewFilter.viewId, + }), + fields: { + viewFilters: (existingViewFilters, { readField, toReference }) => + existingViewFilters.map((viewFilter) => { + const viewFilterId = readField('id', viewFilter); + if (viewFilterId === updatedViewFilter.id) { + return toReference(updatedViewFilter); + } + return viewFilter; + }), + }, + }); + const toBeModifiedCoreView = newCoreViews.find( + (coreView) => coreView.id === updatedViewFilter.viewId, + ); + if (isDefined(toBeModifiedCoreView)) { + newCoreViews = [ + ...newCoreViews.filter( + (coreView) => coreView.id !== updatedViewFilter.viewId, + ), + { + ...toBeModifiedCoreView, + viewFilters: [ + ...toBeModifiedCoreView.viewFilters.filter( + (viewFilter) => viewFilter.id !== updatedViewFilter.id, + ), + updatedViewFilter, + ], + }, + ]; + } + }); + + deletedViewFilters.forEach( + (deletedViewFilter: Pick) => { cache.modify({ id: cache.identify({ __typename: 'CoreView', - id: updatedViewFilter.viewId, + id: deletedViewFilter.viewId, }), fields: { - viewFilters: (existingViewFilters, { readField, toReference }) => - existingViewFilters.map((viewFilter) => { - const viewFilterId = readField('id', viewFilter); - if (viewFilterId === updatedViewFilter.id) { - return toReference(updatedViewFilter); - } - return viewFilter; - }), + viewFilters: (existingViewFilters, { readField }) => + existingViewFilters.filter( + (viewFilter) => + readField('id', viewFilter) !== deletedViewFilter.id, + ), }, }); const toBeModifiedCoreView = newCoreViews.find( - (coreView) => coreView.id === updatedViewFilter.viewId, + (coreView) => coreView.id === deletedViewFilter.viewId, ); + if (isDefined(toBeModifiedCoreView)) { newCoreViews = [ ...newCoreViews.filter( - (coreView) => coreView.id !== updatedViewFilter.viewId, + (coreView) => coreView.id !== deletedViewFilter.viewId, ), { ...toBeModifiedCoreView, - viewFilters: [ - ...toBeModifiedCoreView.viewFilters.filter( - (viewFilter) => viewFilter.id !== updatedViewFilter.id, - ), - updatedViewFilter, - ], + viewFilters: toBeModifiedCoreView.viewFilters.filter( + (viewFilter) => viewFilter.id !== deletedViewFilter.id, + ), }, ]; } - }); + }, + ); - deletedViewFilters.forEach( - (deletedViewFilter: Pick) => { - cache.modify({ - id: cache.identify({ - __typename: 'CoreView', - id: deletedViewFilter.viewId, - }), - fields: { - viewFilters: (existingViewFilters, { readField }) => - existingViewFilters.filter( - (viewFilter) => - readField('id', viewFilter) !== deletedViewFilter.id, - ), - }, - }); - const toBeModifiedCoreView = newCoreViews.find( - (coreView) => coreView.id === deletedViewFilter.viewId, - ); - - if (isDefined(toBeModifiedCoreView)) { - newCoreViews = [ - ...newCoreViews.filter( - (coreView) => coreView.id !== deletedViewFilter.viewId, - ), - { - ...toBeModifiedCoreView, - viewFilters: toBeModifiedCoreView.viewFilters.filter( - (viewFilter) => viewFilter.id !== deletedViewFilter.id, - ), - }, - ]; - } - }, - ); - - if (!isDeeplyEqual(coreViews, newCoreViews)) { - set(coreViewsState, newCoreViews); - } - }, + if (!isDeeplyEqual(coreViews, newCoreViews)) { + jotaiStore.set(coreViewsState.atom, newCoreViews); + } + }, [cache], ); diff --git a/packages/twenty-front/src/modules/views/optimistic-effects/hooks/useTriggerViewGroupOptimisticEffect.ts b/packages/twenty-front/src/modules/views/optimistic-effects/hooks/useTriggerViewGroupOptimisticEffect.ts index 09ded1bdec..bf24ffce2e 100644 --- a/packages/twenty-front/src/modules/views/optimistic-effects/hooks/useTriggerViewGroupOptimisticEffect.ts +++ b/packages/twenty-front/src/modules/views/optimistic-effects/hooks/useTriggerViewGroupOptimisticEffect.ts @@ -1,8 +1,8 @@ -import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { coreViewsState } from '@/views/states/coreViewState'; import { type CoreViewWithRelations } from '@/views/types/CoreViewWithRelations'; import { useApolloClient } from '@apollo/client'; -import { useRecoilCallback } from 'recoil'; +import { useCallback } from 'react'; import { isDefined } from 'twenty-shared/utils'; import { type CoreViewGroup } from '~/generated-metadata/graphql'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; @@ -17,123 +17,122 @@ export const useTriggerViewGroupOptimisticEffect = () => { const cache = apolloClient.cache; - const triggerViewGroupOptimisticEffect = useRecoilCallback( - ({ set, snapshot }) => - ({ - createdViewGroups = [], - updatedViewGroups = [], - deletedViewGroups = [], - }: UpdatedDeletedCoreViewGroup) => { - const coreViews = getSnapshotValue(snapshot, coreViewsState); - let newCoreViews = [...coreViews]; + const triggerViewGroupOptimisticEffect = useCallback( + ({ + createdViewGroups = [], + updatedViewGroups = [], + deletedViewGroups = [], + }: UpdatedDeletedCoreViewGroup) => { + const coreViews = jotaiStore.get(coreViewsState.atom); + let newCoreViews = [...coreViews]; - createdViewGroups.forEach((createdViewGroup) => { - cache.modify({ - id: cache.identify({ - __typename: 'CoreView', - id: createdViewGroup.viewId, - }), - fields: { - viewGroups: (existingViewGroups, { toReference }) => [ - ...(existingViewGroups ?? []), - toReference(createdViewGroup), + createdViewGroups.forEach((createdViewGroup) => { + cache.modify({ + id: cache.identify({ + __typename: 'CoreView', + id: createdViewGroup.viewId, + }), + fields: { + viewGroups: (existingViewGroups, { toReference }) => [ + ...(existingViewGroups ?? []), + toReference(createdViewGroup), + ], + }, + }); + const toBeModifiedCoreView = newCoreViews.find( + (coreView) => coreView.id === createdViewGroup.viewId, + ); + if (isDefined(toBeModifiedCoreView)) { + newCoreViews = [ + ...newCoreViews.filter( + (coreView) => coreView.id !== createdViewGroup.viewId, + ), + { + ...toBeModifiedCoreView, + viewGroups: [ + ...toBeModifiedCoreView.viewGroups, + createdViewGroup, ], }, - }); - const toBeModifiedCoreView = newCoreViews.find( - (coreView) => coreView.id === createdViewGroup.viewId, - ); - if (isDefined(toBeModifiedCoreView)) { - newCoreViews = [ - ...newCoreViews.filter( - (coreView) => coreView.id !== createdViewGroup.viewId, - ), - { - ...toBeModifiedCoreView, - viewGroups: [ - ...toBeModifiedCoreView.viewGroups, - createdViewGroup, - ], - }, - ]; - } - }); - - updatedViewGroups.forEach((updatedViewGroup) => { - cache.modify({ - id: cache.identify({ - __typename: 'CoreView', - id: updatedViewGroup.viewId, - }), - fields: { - viewGroups: (existingViewGroups, { readField, toReference }) => - existingViewGroups.map((viewGroup) => { - const viewGroupId = readField('id', viewGroup); - if (viewGroupId === updatedViewGroup.id) { - return toReference(updatedViewGroup); - } - return viewGroup; - }), - }, - }); - const toBeModifiedCoreView = newCoreViews.find( - (coreView) => coreView.id === updatedViewGroup.viewId, - ); - if (isDefined(toBeModifiedCoreView)) { - newCoreViews = [ - ...newCoreViews.filter( - (coreView) => coreView.id !== updatedViewGroup.viewId, - ), - { - ...toBeModifiedCoreView, - viewGroups: [ - ...toBeModifiedCoreView.viewGroups.filter( - (viewGroup) => viewGroup.id !== updatedViewGroup.id, - ), - updatedViewGroup, - ], - }, - ]; - } - }); - - deletedViewGroups.forEach((deletedViewGroup) => { - cache.modify({ - id: cache.identify({ - __typename: 'CoreView', - id: deletedViewGroup.viewId, - }), - fields: { - viewGroups: (existingViewGroups, { readField }) => - existingViewGroups.filter( - (viewGroup) => - readField('id', viewGroup) !== deletedViewGroup.id, - ), - }, - }); - const toBeModifiedCoreView = newCoreViews.find( - (coreView) => coreView.id === deletedViewGroup.viewId, - ); - - if (isDefined(toBeModifiedCoreView)) { - newCoreViews = [ - ...newCoreViews.filter( - (coreView) => coreView.id !== deletedViewGroup.viewId, - ), - { - ...toBeModifiedCoreView, - viewGroups: toBeModifiedCoreView.viewGroups.filter( - (viewGroup) => viewGroup.id !== deletedViewGroup.id, - ), - }, - ]; - } - }); - - if (!isDeeplyEqual(coreViews, newCoreViews)) { - set(coreViewsState, newCoreViews); + ]; } - }, + }); + + updatedViewGroups.forEach((updatedViewGroup) => { + cache.modify({ + id: cache.identify({ + __typename: 'CoreView', + id: updatedViewGroup.viewId, + }), + fields: { + viewGroups: (existingViewGroups, { readField, toReference }) => + existingViewGroups.map((viewGroup) => { + const viewGroupId = readField('id', viewGroup); + if (viewGroupId === updatedViewGroup.id) { + return toReference(updatedViewGroup); + } + return viewGroup; + }), + }, + }); + const toBeModifiedCoreView = newCoreViews.find( + (coreView) => coreView.id === updatedViewGroup.viewId, + ); + if (isDefined(toBeModifiedCoreView)) { + newCoreViews = [ + ...newCoreViews.filter( + (coreView) => coreView.id !== updatedViewGroup.viewId, + ), + { + ...toBeModifiedCoreView, + viewGroups: [ + ...toBeModifiedCoreView.viewGroups.filter( + (viewGroup) => viewGroup.id !== updatedViewGroup.id, + ), + updatedViewGroup, + ], + }, + ]; + } + }); + + deletedViewGroups.forEach((deletedViewGroup) => { + cache.modify({ + id: cache.identify({ + __typename: 'CoreView', + id: deletedViewGroup.viewId, + }), + fields: { + viewGroups: (existingViewGroups, { readField }) => + existingViewGroups.filter( + (viewGroup) => + readField('id', viewGroup) !== deletedViewGroup.id, + ), + }, + }); + const toBeModifiedCoreView = newCoreViews.find( + (coreView) => coreView.id === deletedViewGroup.viewId, + ); + + if (isDefined(toBeModifiedCoreView)) { + newCoreViews = [ + ...newCoreViews.filter( + (coreView) => coreView.id !== deletedViewGroup.viewId, + ), + { + ...toBeModifiedCoreView, + viewGroups: toBeModifiedCoreView.viewGroups.filter( + (viewGroup) => viewGroup.id !== deletedViewGroup.id, + ), + }, + ]; + } + }); + + if (!isDeeplyEqual(coreViews, newCoreViews)) { + jotaiStore.set(coreViewsState.atom, newCoreViews); + } + }, [cache], ); diff --git a/packages/twenty-front/src/modules/views/optimistic-effects/hooks/useTriggerViewSortOptimisticEffect.ts b/packages/twenty-front/src/modules/views/optimistic-effects/hooks/useTriggerViewSortOptimisticEffect.ts index e371e8f75a..13a8a12fb5 100644 --- a/packages/twenty-front/src/modules/views/optimistic-effects/hooks/useTriggerViewSortOptimisticEffect.ts +++ b/packages/twenty-front/src/modules/views/optimistic-effects/hooks/useTriggerViewSortOptimisticEffect.ts @@ -1,8 +1,8 @@ -import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { coreViewsState } from '@/views/states/coreViewState'; import { type CoreViewWithRelations } from '@/views/types/CoreViewWithRelations'; import { useApolloClient } from '@apollo/client'; -import { useRecoilCallback } from 'recoil'; +import { useCallback } from 'react'; import { isDefined } from 'twenty-shared/utils'; import { type CoreViewSort } from '~/generated-metadata/graphql'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; @@ -12,126 +12,125 @@ export const useTriggerViewSortOptimisticEffect = () => { const cache = apolloClient.cache; - const triggerViewSortOptimisticEffect = useRecoilCallback( - ({ set, snapshot }) => - ({ - createdViewSorts = [], - updatedViewSorts = [], - deletedViewSorts = [], - }: { - createdViewSorts?: Omit[]; - updatedViewSorts?: Omit[]; - deletedViewSorts?: Pick[]; - }) => { - const coreViews = getSnapshotValue(snapshot, coreViewsState); - let newCoreViews = [...coreViews]; + const triggerViewSortOptimisticEffect = useCallback( + ({ + createdViewSorts = [], + updatedViewSorts = [], + deletedViewSorts = [], + }: { + createdViewSorts?: Omit[]; + updatedViewSorts?: Omit[]; + deletedViewSorts?: Pick[]; + }) => { + const coreViews = jotaiStore.get(coreViewsState.atom); + let newCoreViews = [...coreViews]; - createdViewSorts.forEach((createdViewSort) => { - cache.modify({ - id: cache.identify({ - __typename: 'CoreView', - id: createdViewSort.viewId, - }), - fields: { - viewSorts: (existingViewSorts, { toReference }) => [ - ...(existingViewSorts ?? []), - toReference(createdViewSort), + createdViewSorts.forEach((createdViewSort) => { + cache.modify({ + id: cache.identify({ + __typename: 'CoreView', + id: createdViewSort.viewId, + }), + fields: { + viewSorts: (existingViewSorts, { toReference }) => [ + ...(existingViewSorts ?? []), + toReference(createdViewSort), + ], + }, + }); + const toBeModifiedCoreView = newCoreViews.find( + (coreView) => coreView.id === createdViewSort.viewId, + ); + if (isDefined(toBeModifiedCoreView)) { + newCoreViews = [ + ...newCoreViews.filter( + (coreView) => coreView.id !== createdViewSort.viewId, + ), + { + ...toBeModifiedCoreView, + viewSorts: [...toBeModifiedCoreView.viewSorts, createdViewSort], + }, + ]; + } + }); + + updatedViewSorts.forEach((updatedViewSort) => { + cache.modify({ + id: cache.identify({ + __typename: 'CoreView', + id: updatedViewSort.viewId, + }), + fields: { + viewSorts: (existingViewSorts, { readField, toReference }) => + existingViewSorts.map((viewSort) => { + const viewSortId = readField('id', viewSort); + if (viewSortId === updatedViewSort.id) { + return toReference(updatedViewSort); + } + return viewSort; + }), + }, + }); + const toBeModifiedCoreView = newCoreViews.find( + (coreView) => coreView.id === updatedViewSort.viewId, + ); + if (isDefined(toBeModifiedCoreView)) { + newCoreViews = [ + ...newCoreViews.filter( + (coreView) => coreView.id !== updatedViewSort.viewId, + ), + { + ...toBeModifiedCoreView, + viewSorts: [ + ...toBeModifiedCoreView.viewSorts.filter( + (viewSort) => viewSort.id !== updatedViewSort.id, + ), + updatedViewSort, ], }, - }); - const toBeModifiedCoreView = newCoreViews.find( - (coreView) => coreView.id === createdViewSort.viewId, - ); - if (isDefined(toBeModifiedCoreView)) { - newCoreViews = [ - ...newCoreViews.filter( - (coreView) => coreView.id !== createdViewSort.viewId, - ), - { - ...toBeModifiedCoreView, - viewSorts: [...toBeModifiedCoreView.viewSorts, createdViewSort], - }, - ]; - } - }); + ]; + } + }); - updatedViewSorts.forEach((updatedViewSort) => { + deletedViewSorts.forEach( + (deletedViewSort: Pick) => { cache.modify({ id: cache.identify({ __typename: 'CoreView', - id: updatedViewSort.viewId, + id: deletedViewSort.viewId, }), fields: { - viewSorts: (existingViewSorts, { readField, toReference }) => - existingViewSorts.map((viewSort) => { - const viewSortId = readField('id', viewSort); - if (viewSortId === updatedViewSort.id) { - return toReference(updatedViewSort); - } - return viewSort; - }), + viewSorts: (existingViewSorts, { readField }) => + existingViewSorts.filter( + (viewSort) => + readField('id', viewSort) !== deletedViewSort.id, + ), }, }); const toBeModifiedCoreView = newCoreViews.find( - (coreView) => coreView.id === updatedViewSort.viewId, + (coreView) => coreView.id === deletedViewSort.viewId, ); + if (isDefined(toBeModifiedCoreView)) { newCoreViews = [ ...newCoreViews.filter( - (coreView) => coreView.id !== updatedViewSort.viewId, + (coreView) => coreView.id !== deletedViewSort.viewId, ), { ...toBeModifiedCoreView, - viewSorts: [ - ...toBeModifiedCoreView.viewSorts.filter( - (viewSort) => viewSort.id !== updatedViewSort.id, - ), - updatedViewSort, - ], + viewSorts: toBeModifiedCoreView.viewSorts.filter( + (viewSort) => viewSort.id !== deletedViewSort.id, + ), }, ]; } - }); + }, + ); - deletedViewSorts.forEach( - (deletedViewSort: Pick) => { - cache.modify({ - id: cache.identify({ - __typename: 'CoreView', - id: deletedViewSort.viewId, - }), - fields: { - viewSorts: (existingViewSorts, { readField }) => - existingViewSorts.filter( - (viewSort) => - readField('id', viewSort) !== deletedViewSort.id, - ), - }, - }); - const toBeModifiedCoreView = newCoreViews.find( - (coreView) => coreView.id === deletedViewSort.viewId, - ); - - if (isDefined(toBeModifiedCoreView)) { - newCoreViews = [ - ...newCoreViews.filter( - (coreView) => coreView.id !== deletedViewSort.viewId, - ), - { - ...toBeModifiedCoreView, - viewSorts: toBeModifiedCoreView.viewSorts.filter( - (viewSort) => viewSort.id !== deletedViewSort.id, - ), - }, - ]; - } - }, - ); - - if (!isDeeplyEqual(coreViews, newCoreViews)) { - set(coreViewsState, newCoreViews); - } - }, + if (!isDeeplyEqual(coreViews, newCoreViews)) { + jotaiStore.set(coreViewsState.atom, newCoreViews); + } + }, [cache], ); diff --git a/packages/twenty-front/src/modules/views/states/coreViewState.ts b/packages/twenty-front/src/modules/views/states/coreViewState.ts index 3ebbc0bc37..b2b641df42 100644 --- a/packages/twenty-front/src/modules/views/states/coreViewState.ts +++ b/packages/twenty-front/src/modules/views/states/coreViewState.ts @@ -1,7 +1,7 @@ import { type CoreViewWithRelations } from '@/views/types/CoreViewWithRelations'; -import { createState } from '@/ui/utilities/state/utils/createState'; +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; -export const coreViewsState = createState({ +export const coreViewsState = createStateV2({ key: 'coreViewsState', defaultValue: [], }); diff --git a/packages/twenty-front/src/modules/views/states/selectors/coreIndexViewIdFromObjectMetadataItemFamilySelector.ts b/packages/twenty-front/src/modules/views/states/selectors/coreIndexViewIdFromObjectMetadataItemFamilySelector.ts index f20167ba57..2c39356b91 100644 --- a/packages/twenty-front/src/modules/views/states/selectors/coreIndexViewIdFromObjectMetadataItemFamilySelector.ts +++ b/packages/twenty-front/src/modules/views/states/selectors/coreIndexViewIdFromObjectMetadataItemFamilySelector.ts @@ -1,18 +1,16 @@ import { coreViewsState } from '@/views/states/coreViewState'; import { ViewKey } from '@/views/types/ViewKey'; import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView'; -import { selectorFamily } from 'recoil'; +import { createFamilySelectorV2 } from '@/ui/utilities/state/jotai/utils/createFamilySelectorV2'; export const coreIndexViewIdFromObjectMetadataItemFamilySelector = - selectorFamily({ + createFamilySelectorV2({ key: 'coreIndexViewIdFromObjectMetadataItemFamilySelector', get: ({ objectMetadataItemId }) => ({ get }) => { const coreViews = get(coreViewsState); - const views = coreViews.map(convertCoreViewToView); - return views?.find( (view) => view.objectMetadataId === objectMetadataItemId && diff --git a/packages/twenty-front/src/modules/views/states/selectors/coreViewFromViewIdFamilySelector.ts b/packages/twenty-front/src/modules/views/states/selectors/coreViewFromViewIdFamilySelector.ts index 123c936da2..4327a4f8d0 100644 --- a/packages/twenty-front/src/modules/views/states/selectors/coreViewFromViewIdFamilySelector.ts +++ b/packages/twenty-front/src/modules/views/states/selectors/coreViewFromViewIdFamilySelector.ts @@ -1,9 +1,9 @@ +import { createFamilySelectorV2 } from '@/ui/utilities/state/jotai/utils/createFamilySelectorV2'; import { coreViewsState } from '@/views/states/coreViewState'; import { type View } from '@/views/types/View'; import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView'; -import { selectorFamily } from 'recoil'; -export const coreViewFromViewIdFamilySelector = selectorFamily< +export const coreViewFromViewIdFamilySelector = createFamilySelectorV2< View | undefined, { viewId: string } >({ @@ -12,7 +12,6 @@ export const coreViewFromViewIdFamilySelector = selectorFamily< ({ viewId }) => ({ get }) => { const coreViews = get(coreViewsState); - const views = coreViews.map(convertCoreViewToView); return views?.find((view) => view.id === viewId); }, diff --git a/packages/twenty-front/src/modules/views/states/selectors/coreViewIdsFromObjectMetadataItemFamilySelector.ts b/packages/twenty-front/src/modules/views/states/selectors/coreViewIdsFromObjectMetadataItemFamilySelector.ts index 5403c7733b..4664b2ede6 100644 --- a/packages/twenty-front/src/modules/views/states/selectors/coreViewIdsFromObjectMetadataItemFamilySelector.ts +++ b/packages/twenty-front/src/modules/views/states/selectors/coreViewIdsFromObjectMetadataItemFamilySelector.ts @@ -1,21 +1,17 @@ import { coreViewsState } from '@/views/states/coreViewState'; import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView'; -import { selectorFamily } from 'recoil'; +import { createFamilySelectorV2 } from '@/ui/utilities/state/jotai/utils/createFamilySelectorV2'; -export const coreViewIdsFromObjectMetadataItemFamilySelector = selectorFamily< - string[], - { objectMetadataItemId: string } ->({ - key: 'coreViewIdsFromObjectMetadataItemFamilySelector', - get: - ({ objectMetadataItemId }) => - ({ get }) => { - const coreViews = get(coreViewsState); - - const views = coreViews.map(convertCoreViewToView); - - return views - .filter((view) => view.objectMetadataId === objectMetadataItemId) - .map((view) => view.id); - }, -}); +export const coreViewIdsFromObjectMetadataItemFamilySelector = + createFamilySelectorV2({ + key: 'coreViewIdsFromObjectMetadataItemFamilySelector', + get: + ({ objectMetadataItemId }) => + ({ get }) => { + const coreViews = get(coreViewsState); + const views = coreViews.map(convertCoreViewToView); + return views + .filter((view) => view.objectMetadataId === objectMetadataItemId) + .map((view) => view.id); + }, + }); diff --git a/packages/twenty-front/src/modules/views/states/selectors/coreViewLengthFamilySelector.ts b/packages/twenty-front/src/modules/views/states/selectors/coreViewLengthFamilySelector.ts index cd8effa2a4..34b33d4fac 100644 --- a/packages/twenty-front/src/modules/views/states/selectors/coreViewLengthFamilySelector.ts +++ b/packages/twenty-front/src/modules/views/states/selectors/coreViewLengthFamilySelector.ts @@ -1,11 +1,10 @@ import { coreViewsState } from '@/views/states/coreViewState'; -import { selector } from 'recoil'; +import { createSelectorV2 } from '@/ui/utilities/state/jotai/utils/createSelectorV2'; -export const coreViewLengthFamilySelector = selector({ +export const coreViewLengthFamilySelector = createSelectorV2({ key: 'coreViewLengthFamilySelector', get: ({ get }) => { const coreViews = get(coreViewsState); - return coreViews?.length ?? 0; }, }); diff --git a/packages/twenty-front/src/modules/views/states/selectors/coreViewsByObjectMetadataIdFamilySelector.ts b/packages/twenty-front/src/modules/views/states/selectors/coreViewsByObjectMetadataIdFamilySelector.ts index 15b59b0cf7..552d29f4b8 100644 --- a/packages/twenty-front/src/modules/views/states/selectors/coreViewsByObjectMetadataIdFamilySelector.ts +++ b/packages/twenty-front/src/modules/views/states/selectors/coreViewsByObjectMetadataIdFamilySelector.ts @@ -1,41 +1,29 @@ -import { selectorFamily } from 'recoil'; - import { coreViewsState } from '@/views/states/coreViewState'; import { type CoreViewWithRelations } from '@/views/types/CoreViewWithRelations'; +import { createWritableFamilySelectorV2 } from '@/ui/utilities/state/jotai/utils/createWritableFamilySelectorV2'; -export const coreViewsByObjectMetadataIdFamilySelector = selectorFamily< - CoreViewWithRelations[], - string ->({ - key: 'coreViewsByObjectMetadataIdFamilySelector', - get: - (objectMetadataId: string) => - ({ get }) => { - const coreViews = get(coreViewsState); - - return coreViews.filter( - (view) => view.objectMetadataId === objectMetadataId, - ); - }, - set: - (objectMetadataId: string) => - ({ get, set }, newViewsForObject) => { - const currentCoreViews = get(coreViewsState); - - const viewsFromOtherObjects = currentCoreViews.filter( - (view) => view.objectMetadataId !== objectMetadataId, - ); - - let updatedViewsForObject: CoreViewWithRelations[]; - - if (Array.isArray(newViewsForObject)) { - updatedViewsForObject = newViewsForObject; - } else { - updatedViewsForObject = []; - } - - const newCoreViews = [...viewsFromOtherObjects, ...updatedViewsForObject]; - - set(coreViewsState, newCoreViews); - }, -}); +export const coreViewsByObjectMetadataIdFamilySelector = + createWritableFamilySelectorV2({ + key: 'coreViewsByObjectMetadataIdFamilySelector', + get: + (objectMetadataId) => + ({ get }) => { + const coreViews = get(coreViewsState); + return coreViews.filter( + (view) => view.objectMetadataId === objectMetadataId, + ); + }, + set: + (objectMetadataId) => + ({ get, set }, newViewsForObject) => { + const currentCoreViews = get(coreViewsState); + const viewsFromOtherObjects = currentCoreViews.filter( + (view) => view.objectMetadataId !== objectMetadataId, + ); + const newCoreViews = [ + ...viewsFromOtherObjects, + ...(Array.isArray(newViewsForObject) ? newViewsForObject : []), + ]; + set(coreViewsState, newCoreViews); + }, + }); diff --git a/packages/twenty-front/src/modules/views/states/selectors/coreViewsFromObjectMetadataItemFamilySelector.ts b/packages/twenty-front/src/modules/views/states/selectors/coreViewsFromObjectMetadataItemFamilySelector.ts index 0719d922ab..dacecb6cad 100644 --- a/packages/twenty-front/src/modules/views/states/selectors/coreViewsFromObjectMetadataItemFamilySelector.ts +++ b/packages/twenty-front/src/modules/views/states/selectors/coreViewsFromObjectMetadataItemFamilySelector.ts @@ -1,24 +1,18 @@ import { coreViewsState } from '@/views/states/coreViewState'; import { type View } from '@/views/types/View'; import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView'; -import { selectorFamily } from 'recoil'; +import { createFamilySelectorV2 } from '@/ui/utilities/state/jotai/utils/createFamilySelectorV2'; -export const coreViewsFromObjectMetadataItemFamilySelector = selectorFamily< - View[], - { objectMetadataItemId: string } ->({ - key: 'coreViewsFromObjectMetadataItemFamilySelector', - get: - ({ objectMetadataItemId }) => - ({ get }) => { - const coreViews = get(coreViewsState); - - const views = coreViews.map(convertCoreViewToView); - - const filteredViews = views.filter( - (view) => view.objectMetadataId === objectMetadataItemId, - ); - - return filteredViews.sort((a, b) => a.position - b.position); - }, -}); +export const coreViewsFromObjectMetadataItemFamilySelector = + createFamilySelectorV2({ + key: 'coreViewsFromObjectMetadataItemFamilySelector', + get: + ({ objectMetadataItemId }) => + ({ get }) => { + const coreViews = get(coreViewsState); + const views = coreViews.map(convertCoreViewToView); + return views + .filter((view) => view.objectMetadataId === objectMetadataItemId) + .sort((a, b) => a.position - b.position); + }, + }); diff --git a/packages/twenty-front/src/modules/views/view-picker/components/ViewPickerContentEffect.tsx b/packages/twenty-front/src/modules/views/view-picker/components/ViewPickerContentEffect.tsx index cc4baff298..4dcc9e78a9 100644 --- a/packages/twenty-front/src/modules/views/view-picker/components/ViewPickerContentEffect.tsx +++ b/packages/twenty-front/src/modules/views/view-picker/components/ViewPickerContentEffect.tsx @@ -5,6 +5,7 @@ import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFla import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState'; +import { useFamilySelectorValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilySelectorValueV2'; import { coreViewsFromObjectMetadataItemFamilySelector } from '@/views/states/selectors/coreViewsFromObjectMetadataItemFamilySelector'; import { viewTypeIconMapping } from '@/views/types/ViewType'; import { useGetAvailableFieldsForCalendar } from '@/views/view-picker/hooks/useGetAvailableFieldsForCalendar'; @@ -19,7 +20,6 @@ import { viewPickerReferenceViewIdComponentState } from '@/views/view-picker/sta import { viewPickerSelectedIconComponentState } from '@/views/view-picker/states/viewPickerSelectedIconComponentState'; import { viewPickerTypeComponentState } from '@/views/view-picker/states/viewPickerTypeComponentState'; import { viewPickerVisibilityComponentState } from '@/views/view-picker/states/viewPickerVisibilityComponentState'; -import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { ViewVisibility, @@ -67,10 +67,9 @@ export const ViewPickerContentEffect = () => { ); const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow(); - const viewsOnCurrentObject = useRecoilValue( - coreViewsFromObjectMetadataItemFamilySelector({ - objectMetadataItemId: objectMetadataItem.id, - }), + const viewsOnCurrentObject = useFamilySelectorValueV2( + coreViewsFromObjectMetadataItemFamilySelector, + { objectMetadataItemId: objectMetadataItem.id }, ); const referenceView = viewsOnCurrentObject.find( diff --git a/packages/twenty-front/src/modules/views/view-picker/components/ViewPickerFavoriteFoldersDropdown.tsx b/packages/twenty-front/src/modules/views/view-picker/components/ViewPickerFavoriteFoldersDropdown.tsx index 58311a3ddf..89534545aa 100644 --- a/packages/twenty-front/src/modules/views/view-picker/components/ViewPickerFavoriteFoldersDropdown.tsx +++ b/packages/twenty-front/src/modules/views/view-picker/components/ViewPickerFavoriteFoldersDropdown.tsx @@ -2,21 +2,19 @@ import { FavoriteFolderPicker } from '@/favorites/favorite-folder-picker/compone import { FavoriteFolderPickerEffect } from '@/favorites/favorite-folder-picker/components/FavoriteFolderPickerEffect'; import { FavoriteFolderPickerInstanceContext } from '@/favorites/favorite-folder-picker/states/context/FavoriteFolderPickerInstanceContext'; import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState'; +import { useFamilySelectorValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilySelectorValueV2'; import { coreViewFromViewIdFamilySelector } from '@/views/states/selectors/coreViewFromViewIdFamilySelector'; import { VIEW_PICKER_DROPDOWN_ID } from '@/views/view-picker/constants/ViewPickerDropdownId'; import { viewPickerReferenceViewIdComponentState } from '@/views/view-picker/states/viewPickerReferenceViewIdComponentState'; -import { useRecoilValue } from 'recoil'; export const ViewPickerFavoriteFoldersDropdown = () => { const [viewPickerReferenceViewId] = useRecoilComponentState( viewPickerReferenceViewIdComponentState, ); - const view = useRecoilValue( - coreViewFromViewIdFamilySelector({ - viewId: viewPickerReferenceViewId ?? '', - }), - ); + const view = useFamilySelectorValueV2(coreViewFromViewIdFamilySelector, { + viewId: viewPickerReferenceViewId ?? '', + }); return ( { const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow(); - const viewsOnCurrentObject = useRecoilValue( - coreViewsFromObjectMetadataItemFamilySelector({ - objectMetadataItemId: objectMetadataItem.id, - }), + const viewsOnCurrentObject = useFamilySelectorValueV2( + coreViewsFromObjectMetadataItemFamilySelector, + { objectMetadataItemId: objectMetadataItem.id }, ); const workspaceViews = viewsOnCurrentObject.filter( diff --git a/packages/twenty-front/src/modules/views/view-picker/hooks/__tests__/useGetAvailableFieldsForCalendar.test.tsx b/packages/twenty-front/src/modules/views/view-picker/hooks/__tests__/useGetAvailableFieldsForCalendar.test.tsx index 7cb38e477a..a81ca2a544 100644 --- a/packages/twenty-front/src/modules/views/view-picker/hooks/__tests__/useGetAvailableFieldsForCalendar.test.tsx +++ b/packages/twenty-front/src/modules/views/view-picker/hooks/__tests__/useGetAvailableFieldsForCalendar.test.tsx @@ -1,11 +1,13 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext'; import { viewObjectMetadataIdComponentState } from '@/views/states/viewObjectMetadataIdComponentState'; import { useGetAvailableFieldsForCalendar } from '@/views/view-picker/hooks/useGetAvailableFieldsForCalendar'; import { renderHook } from '@testing-library/react'; import { type ReactNode } from 'react'; +import { Provider as JotaiProvider } from 'jotai'; import { MemoryRouter } from 'react-router-dom'; -import { RecoilRoot, type MutableSnapshot } from 'recoil'; +import { RecoilRoot } from 'recoil'; import { FieldMetadataType } from '~/generated-metadata/graphql'; const mockObjectMetadataId = 'test-object-id'; @@ -20,27 +22,31 @@ const createMockObjectMetadataItem = (fields: any[]) => ({ }); const createWrapper = (objectMetadataItems: any[]) => { - return ({ children }: { children: ReactNode }) => ( - { - snapshot.set(objectMetadataItemsState, objectMetadataItems); - snapshot.set( - viewObjectMetadataIdComponentState.atomFamily({ - instanceId: mockViewInstanceId, - }), - mockObjectMetadataId, - ); - }} - > - - { + jotaiStore.set(objectMetadataItemsState.atom, objectMetadataItems); + return ( + + { + snapshot.set( + viewObjectMetadataIdComponentState.atomFamily({ + instanceId: mockViewInstanceId, + }), + mockObjectMetadataId, + ); + }} > - {children} - - - - ); + + + {children} + + + + + ); + }; }; describe('useGetAvailableFieldsForCalendar', () => { diff --git a/packages/twenty-front/src/modules/views/view-picker/hooks/__tests__/useGetAvailableFieldsToGroupRecordsBy.test.tsx b/packages/twenty-front/src/modules/views/view-picker/hooks/__tests__/useGetAvailableFieldsToGroupRecordsBy.test.tsx index 764526d65c..a06839e68d 100644 --- a/packages/twenty-front/src/modules/views/view-picker/hooks/__tests__/useGetAvailableFieldsToGroupRecordsBy.test.tsx +++ b/packages/twenty-front/src/modules/views/view-picker/hooks/__tests__/useGetAvailableFieldsToGroupRecordsBy.test.tsx @@ -1,11 +1,13 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext'; import { viewObjectMetadataIdComponentState } from '@/views/states/viewObjectMetadataIdComponentState'; import { useGetAvailableFieldsToGroupRecordsBy } from '@/views/view-picker/hooks/useGetAvailableFieldsToGroupRecordsBy'; import { renderHook } from '@testing-library/react'; import { type ReactNode } from 'react'; +import { Provider as JotaiProvider } from 'jotai'; import { MemoryRouter } from 'react-router-dom'; -import { RecoilRoot, type MutableSnapshot } from 'recoil'; +import { RecoilRoot } from 'recoil'; import { FieldMetadataType } from '~/generated-metadata/graphql'; const mockObjectMetadataId = 'test-object-id'; @@ -20,27 +22,31 @@ const createMockObjectMetadataItem = (fields: any[]) => ({ }); const createWrapper = (objectMetadataItems: any[]) => { - return ({ children }: { children: ReactNode }) => ( - { - snapshot.set(objectMetadataItemsState, objectMetadataItems); - snapshot.set( - viewObjectMetadataIdComponentState.atomFamily({ - instanceId: mockViewInstanceId, - }), - mockObjectMetadataId, - ); - }} - > - - { + jotaiStore.set(objectMetadataItemsState.atom, objectMetadataItems); + return ( + + { + snapshot.set( + viewObjectMetadataIdComponentState.atomFamily({ + instanceId: mockViewInstanceId, + }), + mockObjectMetadataId, + ); + }} > - {children} - - - - ); + + + {children} + + + + + ); + }; }; describe('useGetAvailableFieldsToGroupRecordsBy', () => { diff --git a/packages/twenty-front/src/modules/views/view-picker/hooks/useDestroyViewFromCurrentState.ts b/packages/twenty-front/src/modules/views/view-picker/hooks/useDestroyViewFromCurrentState.ts index cf3a683378..159be0740c 100644 --- a/packages/twenty-front/src/modules/views/view-picker/hooks/useDestroyViewFromCurrentState.ts +++ b/packages/twenty-front/src/modules/views/view-picker/hooks/useDestroyViewFromCurrentState.ts @@ -1,13 +1,15 @@ -import { useRecoilCallback, useRecoilValue } from 'recoil'; +import { useRecoilCallback } from 'recoil'; import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow'; import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState'; +import { useFamilySelectorValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilySelectorValueV2'; import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; import { usePerformViewAPIPersist } from '@/views/hooks/internal/usePerformViewAPIPersist'; import { useChangeView } from '@/views/hooks/useChangeView'; import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly'; import { coreViewsByObjectMetadataIdFamilySelector } from '@/views/states/selectors/coreViewsByObjectMetadataIdFamilySelector'; import { coreViewsFromObjectMetadataItemFamilySelector } from '@/views/states/selectors/coreViewsFromObjectMetadataItemFamilySelector'; +import { useStore } from 'jotai'; import { useCloseAndResetViewPicker } from '@/views/view-picker/hooks/useCloseAndResetViewPicker'; import { viewPickerIsDirtyComponentState } from '@/views/view-picker/states/viewPickerIsDirtyComponentState'; import { viewPickerIsPersistingComponentState } from '@/views/view-picker/states/viewPickerIsPersistingComponentState'; @@ -34,10 +36,9 @@ export const useDestroyViewFromCurrentState = (viewBarInstanceId?: string) => { const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow(); - const viewsOnCurrentObject = useRecoilValue( - coreViewsFromObjectMetadataItemFamilySelector({ - objectMetadataItemId: objectMetadataItem.id, - }), + const viewsOnCurrentObject = useFamilySelectorValueV2( + coreViewsFromObjectMetadataItemFamilySelector, + { objectMetadataItemId: objectMetadataItem.id }, ); const { currentView } = useGetCurrentViewOnly(); @@ -46,6 +47,8 @@ export const useDestroyViewFromCurrentState = (viewBarInstanceId?: string) => { const { performViewAPIDestroy } = usePerformViewAPIPersist(); + const store = useStore(); + const destroyViewFromCurrentState = useRecoilCallback( ({ set, snapshot }) => async () => { @@ -68,8 +71,10 @@ export const useDestroyViewFromCurrentState = (viewBarInstanceId?: string) => { ); } - set( - coreViewsByObjectMetadataIdFamilySelector(objectMetadataItem.id), + store.set( + coreViewsByObjectMetadataIdFamilySelector.selectorFamily( + objectMetadataItem.id, + ), (views) => views.filter((view) => view.id !== viewPickerReferenceViewId), ); @@ -82,6 +87,7 @@ export const useDestroyViewFromCurrentState = (viewBarInstanceId?: string) => { objectMetadataItem.id, changeView, performViewAPIDestroy, + store, viewPickerIsDirtyCallbackState, viewPickerIsPersistingCallbackState, viewPickerReferenceViewIdCallbackState, diff --git a/packages/twenty-front/src/modules/views/view-picker/hooks/useGetAvailableFieldsForCalendar.ts b/packages/twenty-front/src/modules/views/view-picker/hooks/useGetAvailableFieldsForCalendar.ts index 6490667f88..7a7ef19134 100644 --- a/packages/twenty-front/src/modules/views/view-picker/hooks/useGetAvailableFieldsForCalendar.ts +++ b/packages/twenty-front/src/modules/views/view-picker/hooks/useGetAvailableFieldsForCalendar.ts @@ -1,11 +1,11 @@ import { useCallback } from 'react'; import { useLocation } from 'react-router-dom'; -import { useRecoilValue } from 'recoil'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState'; import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { viewObjectMetadataIdComponentState } from '@/views/states/viewObjectMetadataIdComponentState'; import { FieldMetadataType, SettingsPath } from 'twenty-shared/types'; import { isDefined, isFieldMetadataDateKind } from 'twenty-shared/utils'; @@ -15,7 +15,7 @@ export const useGetAvailableFieldsForCalendar = () => { const viewObjectMetadataId = useRecoilComponentValue( viewObjectMetadataIdComponentState, ); - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); const setNavigationMemorizedUrl = useSetRecoilStateV2( navigationMemorizedUrlState, ); diff --git a/packages/twenty-front/src/modules/views/view-picker/hooks/useGetAvailableFieldsToGroupRecordsBy.ts b/packages/twenty-front/src/modules/views/view-picker/hooks/useGetAvailableFieldsToGroupRecordsBy.ts index 93a6f99332..d3c3144217 100644 --- a/packages/twenty-front/src/modules/views/view-picker/hooks/useGetAvailableFieldsToGroupRecordsBy.ts +++ b/packages/twenty-front/src/modules/views/view-picker/hooks/useGetAvailableFieldsToGroupRecordsBy.ts @@ -1,11 +1,11 @@ import { useCallback } from 'react'; import { useLocation } from 'react-router-dom'; -import { useRecoilValue } from 'recoil'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState'; import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { viewObjectMetadataIdComponentState } from '@/views/states/viewObjectMetadataIdComponentState'; import { SettingsPath } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; @@ -16,7 +16,7 @@ export const useGetAvailableFieldsToGroupRecordsBy = () => { const viewObjectMetadataId = useRecoilComponentValue( viewObjectMetadataIdComponentState, ); - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); const setNavigationMemorizedUrl = useSetRecoilStateV2( navigationMemorizedUrlState, ); diff --git a/packages/twenty-front/src/modules/workflow/hooks/useRunWorkflowRunOpeningInCommandMenuSideEffects.ts b/packages/twenty-front/src/modules/workflow/hooks/useRunWorkflowRunOpeningInCommandMenuSideEffects.ts index 0c9343ea77..af7637b669 100644 --- a/packages/twenty-front/src/modules/workflow/hooks/useRunWorkflowRunOpeningInCommandMenuSideEffects.ts +++ b/packages/twenty-front/src/modules/workflow/hooks/useRunWorkflowRunOpeningInCommandMenuSideEffects.ts @@ -4,7 +4,7 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadat import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { getRecordFromCache } from '@/object-record/cache/utils/getRecordFromCache'; import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions'; -import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { flowComponentState } from '@/workflow/states/flowComponentState'; import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState'; import { workflowVisualizerWorkflowRunIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowRunIdComponentState'; @@ -26,7 +26,7 @@ export const useRunWorkflowRunOpeningInCommandMenuSideEffects = () => { const { objectPermissionsByObjectMetadataId } = useObjectPermissions(); const runWorkflowRunOpeningInCommandMenuSideEffects = useRecoilCallback( - ({ snapshot, set }) => + ({ set }) => ({ objectMetadataItem, recordId, @@ -34,9 +34,8 @@ export const useRunWorkflowRunOpeningInCommandMenuSideEffects = () => { objectMetadataItem: ObjectMetadataItem; recordId: string; }) => { - const objectMetadataItems = getSnapshotValue( - snapshot, - objectMetadataItemsState, + const objectMetadataItems = jotaiStore.get( + objectMetadataItemsState.atom, ); const workflowRunRecord = getRecordFromCache({ diff --git a/packages/twenty-front/src/modules/workflow/hooks/useRunWorkflowVersion.tsx b/packages/twenty-front/src/modules/workflow/hooks/useRunWorkflowVersion.tsx index 594e84f234..557c0146ee 100644 --- a/packages/twenty-front/src/modules/workflow/hooks/useRunWorkflowVersion.tsx +++ b/packages/twenty-front/src/modules/workflow/hooks/useRunWorkflowVersion.tsx @@ -17,8 +17,9 @@ import { computeOptimisticCreateRecordBaseRecordInput } from '@/object-record/ut import { computeOptimisticRecordFromInput } from '@/object-record/utils/computeOptimisticRecordFromInput'; import { RUN_WORKFLOW_VERSION } from '@/workflow/graphql/mutations/runWorkflowVersion'; import { type WorkflowRun } from '@/workflow/types/Workflow'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { useMutation } from '@apollo/client'; -import { useRecoilCallback, useRecoilValue } from 'recoil'; +import { useRecoilCallback } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { v4 } from 'uuid'; import { @@ -39,7 +40,7 @@ export const useRunWorkflowVersion = () => { const createOneRecordInCache = useCreateOneRecordInCache({ objectMetadataItem, }); - const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); + const currentWorkspaceMember = useRecoilValueV2(currentWorkspaceMemberState); const [mutate] = useMutation< RunWorkflowVersionMutation, diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionEmailBase.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionEmailBase.tsx index 9ca250a467..fec2e7a4e0 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionEmailBase.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionEmailBase.tsx @@ -26,8 +26,8 @@ import { useEmailForm } from '@/workflow/workflow-steps/workflow-actions/hooks/u import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker'; import { useTheme } from '@emotion/react'; import { t } from '@lingui/core/macro'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { useEffect, useState } from 'react'; -import { useRecoilValue } from 'recoil'; import { ConnectedAccountProvider, SettingsPath } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { type WorkflowEmailAction } from '@/workflow/types/WorkflowEmailAction'; @@ -57,7 +57,7 @@ export const WorkflowEditActionEmailBase = ({ actionOptions, }: WorkflowEditActionEmailBaseProps) => { const theme = useTheme(); - const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); + const currentWorkspaceMember = useRecoilValueV2(currentWorkspaceMemberState); const { triggerApisOAuth } = useTriggerApisOAuth(); const { enqueueErrorSnackBar } = useSnackBar(); const { uploadAttachmentFile } = useUploadAttachmentFile(); diff --git a/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerWebhookForm.tsx b/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerWebhookForm.tsx index 7b1ac6cbec..dc99ef01d6 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerWebhookForm.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerWebhookForm.tsx @@ -14,8 +14,8 @@ import { WEBHOOK_TRIGGER_HTTP_METHOD_OPTIONS } from '@/workflow/workflow-trigger import { getWebhookTriggerDefaultSettings } from '@/workflow/workflow-trigger/utils/getWebhookTriggerDefaultSettings'; import { useTheme } from '@emotion/react'; import { isNonEmptyString } from '@sniptt/guards'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { useState } from 'react'; -import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { getOutputSchemaFromValue } from 'twenty-shared/logic-function'; import { TRIGGER_STEP_ID } from 'twenty-shared/workflow'; @@ -57,7 +57,7 @@ export const WorkflowEditTriggerWebhookForm = ({ const workflowVisualizerWorkflowId = useRecoilComponentValue( workflowVisualizerWorkflowIdComponentState, ); - const currentWorkspace = useRecoilValue(currentWorkspaceState); + const currentWorkspace = useRecoilValueV2(currentWorkspaceState); const onBlur = () => { setErrorMessagesVisible(true); diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/hooks/useStepsOutputSchema.ts b/packages/twenty-front/src/modules/workflow/workflow-variables/hooks/useStepsOutputSchema.ts index 181945a412..c081e14124 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-variables/hooks/useStepsOutputSchema.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-variables/hooks/useStepsOutputSchema.ts @@ -14,17 +14,18 @@ import { computeStepOutputSchema, shouldComputeOutputSchemaOnFrontend, } from '@/workflow/workflow-variables/utils/generate/computeStepOutputSchema'; +import { useStore } from 'jotai'; import { useRecoilCallback } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { TRIGGER_STEP_ID } from 'twenty-shared/workflow'; export const useStepsOutputSchema = () => { + const store = useStore(); + const populateStepsOutputSchema = useRecoilCallback( ({ set, snapshot }) => (workflowVersion: WorkflowVersion) => { - const objectMetadataItems = snapshot - .getLoadable(objectMetadataItemsState) - .getValue(); + const objectMetadataItems = store.get(objectMetadataItemsState.atom); workflowVersion.steps?.forEach((step) => { const stepKey = getStepOutputSchemaFamilyStateKey( @@ -108,7 +109,7 @@ export const useStepsOutputSchema = () => { set(shouldRecomputeOutputSchemaFamilyState(triggerKey), false); } }, - [], + [store], ); const markStepForRecomputation = useRecoilCallback( diff --git a/packages/twenty-front/src/modules/workspace/components/WorkspaceProviderEffect.tsx b/packages/twenty-front/src/modules/workspace/components/WorkspaceProviderEffect.tsx index 1dc9346f33..b9d2f85643 100644 --- a/packages/twenty-front/src/modules/workspace/components/WorkspaceProviderEffect.tsx +++ b/packages/twenty-front/src/modules/workspace/components/WorkspaceProviderEffect.tsx @@ -1,9 +1,8 @@ -import { useRecoilValue } from 'recoil'; - import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState'; import { useReadWorkspaceUrlFromCurrentLocation } from '@/domain-manager/hooks/useReadWorkspaceUrlFromCurrentLocation'; import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain'; import { lastAuthenticatedWorkspaceDomainState } from '@/domain-manager/states/lastAuthenticatedWorkspaceDomainState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { useEffect, useCallback } from 'react'; import { useInitializeQueryParamState } from '@/app/hooks/useInitializeQueryParamState'; @@ -16,7 +15,7 @@ import { getWorkspaceUrl } from '~/utils/getWorkspaceUrl'; export const WorkspaceProviderEffect = () => { const { data: getPublicWorkspaceData } = useGetPublicWorkspaceDataByDomain(); - const lastAuthenticatedWorkspaceDomain = useRecoilValue( + const lastAuthenticatedWorkspaceDomain = useRecoilValueV2( lastAuthenticatedWorkspaceDomainState, ); @@ -25,7 +24,9 @@ export const WorkspaceProviderEffect = () => { const { currentLocationHostname } = useReadWorkspaceUrlFromCurrentLocation(); - const isMultiWorkspaceEnabled = useRecoilValue(isMultiWorkspaceEnabledState); + const isMultiWorkspaceEnabled = useRecoilValueV2( + isMultiWorkspaceEnabledState, + ); const { initializeQueryParamState } = useInitializeQueryParamState(); diff --git a/packages/twenty-front/src/modules/workspace/hooks/__tests__/useSubscriptionStatus.test.ts b/packages/twenty-front/src/modules/workspace/hooks/__tests__/useSubscriptionStatus.test.ts index 4d76277aee..0b3f251fa3 100644 --- a/packages/twenty-front/src/modules/workspace/hooks/__tests__/useSubscriptionStatus.test.ts +++ b/packages/twenty-front/src/modules/workspace/hooks/__tests__/useSubscriptionStatus.test.ts @@ -1,11 +1,15 @@ import { act, renderHook } from '@testing-library/react'; -import { RecoilRoot, useSetRecoilState } from 'recoil'; +import { createElement } from 'react'; +import { Provider as JotaiProvider } from 'jotai'; +import { RecoilRoot } from 'recoil'; import { v4 } from 'uuid'; import { type CurrentWorkspace, currentWorkspaceState, } from '@/auth/states/currentWorkspaceState'; +import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; +import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore'; import { useSubscriptionStatus } from '@/workspace/hooks/useSubscriptionStatus'; import { SubscriptionStatus, @@ -19,11 +23,18 @@ const currentWorkspace = { allowImpersonation: true, } as CurrentWorkspace; +const Wrapper = ({ children }: { children: React.ReactNode }) => + createElement( + JotaiProvider, + { store: jotaiStore }, + createElement(RecoilRoot, null, children), + ); + const renderHooks = () => { const { result } = renderHook( () => { const subscriptionStatus = useSubscriptionStatus(); - const setCurrentWorkspace = useSetRecoilState(currentWorkspaceState); + const setCurrentWorkspace = useSetRecoilStateV2(currentWorkspaceState); return { subscriptionStatus, @@ -31,7 +42,7 @@ const renderHooks = () => { }; }, { - wrapper: RecoilRoot, + wrapper: Wrapper, }, ); return { result }; diff --git a/packages/twenty-front/src/modules/workspace/hooks/useFeatureFlagsMap.ts b/packages/twenty-front/src/modules/workspace/hooks/useFeatureFlagsMap.ts index 038f5bc7ec..d9c4b0a50e 100644 --- a/packages/twenty-front/src/modules/workspace/hooks/useFeatureFlagsMap.ts +++ b/packages/twenty-front/src/modules/workspace/hooks/useFeatureFlagsMap.ts @@ -1,10 +1,10 @@ import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { extractFeatureFlagMapFromWorkspace } from '@/workspace/utils/extractFeatureFlagMapFromWorkspace'; -import { useRecoilValue } from 'recoil'; import { type FeatureFlagKey } from '~/generated-metadata/graphql'; export const useFeatureFlagsMap = (): Record => { - const currentWorkspace = useRecoilValue(currentWorkspaceState); + const currentWorkspace = useRecoilValueV2(currentWorkspaceState); return extractFeatureFlagMapFromWorkspace(currentWorkspace); }; diff --git a/packages/twenty-front/src/modules/workspace/hooks/useIsFeatureEnabled.ts b/packages/twenty-front/src/modules/workspace/hooks/useIsFeatureEnabled.ts index 7c38faf950..82efd616bc 100644 --- a/packages/twenty-front/src/modules/workspace/hooks/useIsFeatureEnabled.ts +++ b/packages/twenty-front/src/modules/workspace/hooks/useIsFeatureEnabled.ts @@ -1,10 +1,9 @@ -import { useRecoilValue } from 'recoil'; - import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { type FeatureFlagKey } from '~/generated-metadata/graphql'; export const useIsFeatureEnabled = (featureKey: FeatureFlagKey | null) => { - const currentWorkspace = useRecoilValue(currentWorkspaceState); + const currentWorkspace = useRecoilValueV2(currentWorkspaceState); if (!featureKey) { return false; diff --git a/packages/twenty-front/src/modules/workspace/hooks/useIsSomeMeteredProductCapReached.ts b/packages/twenty-front/src/modules/workspace/hooks/useIsSomeMeteredProductCapReached.ts index 2a65cb1f6b..c3bfd3c5a3 100644 --- a/packages/twenty-front/src/modules/workspace/hooks/useIsSomeMeteredProductCapReached.ts +++ b/packages/twenty-front/src/modules/workspace/hooks/useIsSomeMeteredProductCapReached.ts @@ -1,9 +1,8 @@ -import { useRecoilValue } from 'recoil'; - import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; export const useIsSomeMeteredProductCapReached = (): boolean => { - const billingSubscriptionItems = useRecoilValue(currentWorkspaceState) + const billingSubscriptionItems = useRecoilValueV2(currentWorkspaceState) ?.currentBillingSubscription?.billingSubscriptionItems; if (!billingSubscriptionItems) { diff --git a/packages/twenty-front/src/modules/workspace/hooks/useIsWorkspaceActivationStatusEqualsTo.ts b/packages/twenty-front/src/modules/workspace/hooks/useIsWorkspaceActivationStatusEqualsTo.ts index 56ffc0a656..6be98b747f 100644 --- a/packages/twenty-front/src/modules/workspace/hooks/useIsWorkspaceActivationStatusEqualsTo.ts +++ b/packages/twenty-front/src/modules/workspace/hooks/useIsWorkspaceActivationStatusEqualsTo.ts @@ -1,11 +1,10 @@ -import { useRecoilValue } from 'recoil'; - import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { type WorkspaceActivationStatus } from 'twenty-shared/workspace'; export const useIsWorkspaceActivationStatusEqualsTo = ( activationStatus: WorkspaceActivationStatus, ): boolean => { - const currentWorkspace = useRecoilValue(currentWorkspaceState); + const currentWorkspace = useRecoilValueV2(currentWorkspaceState); return currentWorkspace?.activationStatus === activationStatus; }; diff --git a/packages/twenty-front/src/modules/workspace/hooks/useSubscriptionStatus.ts b/packages/twenty-front/src/modules/workspace/hooks/useSubscriptionStatus.ts index ad1016942a..150d4f93ec 100644 --- a/packages/twenty-front/src/modules/workspace/hooks/useSubscriptionStatus.ts +++ b/packages/twenty-front/src/modules/workspace/hooks/useSubscriptionStatus.ts @@ -1,9 +1,8 @@ -import { useRecoilValue } from 'recoil'; - import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { type SubscriptionStatus } from '~/generated-metadata/graphql'; export const useSubscriptionStatus = (): SubscriptionStatus | undefined => { - const currentWorkspace = useRecoilValue(currentWorkspaceState); + const currentWorkspace = useRecoilValueV2(currentWorkspaceState); return currentWorkspace?.currentBillingSubscription?.status; }; diff --git a/packages/twenty-front/src/modules/workspace/states/workspaceAuthBypassProvidersState.ts b/packages/twenty-front/src/modules/workspace/states/workspaceAuthBypassProvidersState.ts index b5d6e2c7bb..99e18d0251 100644 --- a/packages/twenty-front/src/modules/workspace/states/workspaceAuthBypassProvidersState.ts +++ b/packages/twenty-front/src/modules/workspace/states/workspaceAuthBypassProvidersState.ts @@ -1,8 +1,8 @@ import { type AuthBypassProviders } from '~/generated-metadata/graphql'; -import { createState } from '@/ui/utilities/state/utils/createState'; +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; export const workspaceAuthBypassProvidersState = - createState({ + createStateV2({ key: 'workspaceAuthBypassProvidersState', defaultValue: null, }); diff --git a/packages/twenty-front/src/modules/workspace/states/workspaceAuthProvidersState.ts b/packages/twenty-front/src/modules/workspace/states/workspaceAuthProvidersState.ts index db0553e7da..c76c2380cf 100644 --- a/packages/twenty-front/src/modules/workspace/states/workspaceAuthProvidersState.ts +++ b/packages/twenty-front/src/modules/workspace/states/workspaceAuthProvidersState.ts @@ -1,7 +1,7 @@ import { type AuthProviders } from '~/generated-metadata/graphql'; -import { createState } from '@/ui/utilities/state/utils/createState'; +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; -export const workspaceAuthProvidersState = createState({ +export const workspaceAuthProvidersState = createStateV2({ key: 'workspaceAuthProvidersState', defaultValue: null, }); diff --git a/packages/twenty-front/src/modules/workspace/states/workspaceBypassModeState.ts b/packages/twenty-front/src/modules/workspace/states/workspaceBypassModeState.ts index 48fad7b753..ca24c15365 100644 --- a/packages/twenty-front/src/modules/workspace/states/workspaceBypassModeState.ts +++ b/packages/twenty-front/src/modules/workspace/states/workspaceBypassModeState.ts @@ -1,6 +1,6 @@ -import { createState } from '@/ui/utilities/state/utils/createState'; +import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2'; -export const workspaceBypassModeState = createState({ +export const workspaceBypassModeState = createStateV2({ key: 'workspaceBypassModeState', defaultValue: false, }); diff --git a/packages/twenty-front/src/pages/auth/PasswordReset.tsx b/packages/twenty-front/src/pages/auth/PasswordReset.tsx index 88376c7de5..1126cd7cc0 100644 --- a/packages/twenty-front/src/pages/auth/PasswordReset.tsx +++ b/packages/twenty-front/src/pages/auth/PasswordReset.tsx @@ -25,7 +25,8 @@ import { useState } from 'react'; import { Controller, useForm } from 'react-hook-form'; import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; import { useParams } from 'react-router-dom'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; +import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; import { AppPath } from 'twenty-shared/types'; import { MainButton } from 'twenty-ui/input'; import { AnimatedEaseIn } from 'twenty-ui/utilities'; @@ -87,8 +88,8 @@ export const PasswordReset = () => { const { t } = useLingui(); const { enqueueErrorSnackBar, enqueueSuccessSnackBar } = useSnackBar(); - const workspacePublicData = useRecoilValue(workspacePublicDataState); - const setCurrentUser = useSetRecoilState(currentUserState); + const workspacePublicData = useRecoilValueV2(workspacePublicDataState); + const setCurrentUser = useSetRecoilStateV2(currentUserState); const navigate = useNavigateApp(); const { redirect } = useRedirect(); diff --git a/packages/twenty-front/src/pages/auth/SignInUp.tsx b/packages/twenty-front/src/pages/auth/SignInUp.tsx index 2a1a3ea191..979264f573 100644 --- a/packages/twenty-front/src/pages/auth/SignInUp.tsx +++ b/packages/twenty-front/src/pages/auth/SignInUp.tsx @@ -6,7 +6,9 @@ import { } from '@/auth/states/signInUpStepState'; import { workspacePublicDataState } from '@/auth/states/workspacePublicDataState'; import styled from '@emotion/styled'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; + +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; +import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; import { Logo } from '@/auth/components/Logo'; import { Title } from '@/auth/components/Title'; @@ -80,17 +82,19 @@ const StandardContent = ({ export const SignInUp = () => { const { t } = useLingui(); - const setSignInUpStep = useSetRecoilState(signInUpStepState); - const clientConfigApiStatus = useRecoilValue(clientConfigApiStatusState); + const setSignInUpStep = useSetRecoilStateV2(signInUpStepState); + const clientConfigApiStatus = useRecoilValueV2(clientConfigApiStatusState); const { form } = useSignInUpForm(); const { signInUpStep } = useSignInUp(form); const { isDefaultDomain } = useIsCurrentLocationOnDefaultDomain(); const { isOnAWorkspace } = useIsCurrentLocationOnAWorkspace(); - const workspacePublicData = useRecoilValue(workspacePublicDataState); + const workspacePublicData = useRecoilValueV2(workspacePublicDataState); const { loading: getPublicWorkspaceDataLoading } = useGetPublicWorkspaceDataByDomain(); - const isMultiWorkspaceEnabled = useRecoilValue(isMultiWorkspaceEnabledState); + const isMultiWorkspaceEnabled = useRecoilValueV2( + isMultiWorkspaceEnabledState, + ); const { workspaceInviteHash, workspace: workspaceFromInviteHash } = useWorkspaceFromInviteHash(); diff --git a/packages/twenty-front/src/pages/auth/__stories__/SignInUp.stories.tsx b/packages/twenty-front/src/pages/auth/__stories__/SignInUp.stories.tsx index fc78afae2d..74335df6c7 100644 --- a/packages/twenty-front/src/pages/auth/__stories__/SignInUp.stories.tsx +++ b/packages/twenty-front/src/pages/auth/__stories__/SignInUp.stories.tsx @@ -2,10 +2,10 @@ import { getOperationName } from '@apollo/client/utilities'; import { type Meta, type StoryObj } from '@storybook/react-vite'; import { HttpResponse, graphql } from 'msw'; import { useEffect } from 'react'; -import { useSetRecoilState } from 'recoil'; import { fireEvent, within } from 'storybook/test'; import { captchaTokenState } from '@/captcha/states/captchaTokenState'; +import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser'; import { PageDecorator, @@ -17,7 +17,7 @@ import { AppPath } from 'twenty-shared/types'; import { SignInUp } from '~/pages/auth/SignInUp'; const CaptchaTokenSetterEffect = () => { - const setCaptchaToken = useSetRecoilState(captchaTokenState); + const setCaptchaToken = useSetRecoilStateV2(captchaTokenState); useEffect(() => { setCaptchaToken('MOCKED_CAPTCHA_TOKEN'); diff --git a/packages/twenty-front/src/pages/auth/__stories__/SignInUpWithInvite.stories.tsx b/packages/twenty-front/src/pages/auth/__stories__/SignInUpWithInvite.stories.tsx index b763fc894c..ba031398eb 100644 --- a/packages/twenty-front/src/pages/auth/__stories__/SignInUpWithInvite.stories.tsx +++ b/packages/twenty-front/src/pages/auth/__stories__/SignInUpWithInvite.stories.tsx @@ -2,10 +2,10 @@ import { getOperationName } from '@apollo/client/utilities'; import { type Meta, type StoryObj } from '@storybook/react-vite'; import { HttpResponse, graphql } from 'msw'; import { useEffect } from 'react'; -import { useSetRecoilState } from 'recoil'; import { fireEvent, within } from 'storybook/test'; import { captchaTokenState } from '@/captcha/states/captchaTokenState'; +import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser'; import { GET_WORKSPACE_FROM_INVITE_HASH } from '@/workspace/graphql/queries/getWorkspaceFromInviteHash'; import { @@ -18,7 +18,7 @@ import { AppPath } from 'twenty-shared/types'; import { SignInUp } from '~/pages/auth/SignInUp'; const CaptchaTokenSetterEffect = () => { - const setCaptchaToken = useSetRecoilState(captchaTokenState); + const setCaptchaToken = useSetRecoilStateV2(captchaTokenState); useEffect(() => { setCaptchaToken('MOCKED_CAPTCHA_TOKEN'); diff --git a/packages/twenty-front/src/pages/onboarding/BookCall.tsx b/packages/twenty-front/src/pages/onboarding/BookCall.tsx index f1bd45f48d..75391b617f 100644 --- a/packages/twenty-front/src/pages/onboarding/BookCall.tsx +++ b/packages/twenty-front/src/pages/onboarding/BookCall.tsx @@ -9,7 +9,7 @@ import { Modal } from '@/ui/layout/modal/components/Modal'; import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper'; import { useTheme } from '@emotion/react'; import { useLingui } from '@lingui/react/macro'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { AppPath } from 'twenty-shared/types'; import { IconChevronLeft, IconChevronRightPipe } from 'twenty-ui/display'; import { LightButton } from 'twenty-ui/input'; @@ -37,9 +37,9 @@ const StyledScrollWrapper = styled(ScrollWrapper)<{ isMobile: boolean }>` export const BookCall = () => { const { t } = useLingui(); const theme = useTheme(); - const calendarBookingPageId = useRecoilValue(calendarBookingPageIdState); + const calendarBookingPageId = useRecoilValueV2(calendarBookingPageIdState); const setNextOnboardingStatus = useSetNextOnboardingStatus(); - const currentUser = useRecoilValue(currentUserState); + const currentUser = useRecoilValueV2(currentUserState); const [skipBookOnboardingStepMutation] = useSkipBookOnboardingStepMutation(); const isMobile = useIsMobile(); diff --git a/packages/twenty-front/src/pages/onboarding/ChooseYourPlan.tsx b/packages/twenty-front/src/pages/onboarding/ChooseYourPlan.tsx index 3f5fea8bcb..a19145c4a3 100644 --- a/packages/twenty-front/src/pages/onboarding/ChooseYourPlan.tsx +++ b/packages/twenty-front/src/pages/onboarding/ChooseYourPlan.tsx @@ -2,8 +2,8 @@ import { Modal } from '@/ui/layout/modal/components/Modal'; import styled from '@emotion/styled'; import { isDefined } from 'twenty-shared/utils'; import { ChooseYourPlanContent } from '~/pages/onboarding/internal/ChooseYourPlanContent'; -import { useRecoilValue } from 'recoil'; import { billingState } from '@/client-config/states/billingState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { usePlans } from '@/billing/hooks/usePlans'; const StyledChooseYourPlanPlaceholder = styled.div` @@ -12,7 +12,7 @@ const StyledChooseYourPlanPlaceholder = styled.div` export const ChooseYourPlan = () => { const { isPlansLoaded } = usePlans(); - const billing = useRecoilValue(billingState); + const billing = useRecoilValueV2(billingState); return ( {isDefined(billing) && isPlansLoaded ? ( diff --git a/packages/twenty-front/src/pages/onboarding/CreateProfile.tsx b/packages/twenty-front/src/pages/onboarding/CreateProfile.tsx index 4b87e5ad8b..b3140336c2 100644 --- a/packages/twenty-front/src/pages/onboarding/CreateProfile.tsx +++ b/packages/twenty-front/src/pages/onboarding/CreateProfile.tsx @@ -2,7 +2,8 @@ import styled from '@emotion/styled'; import { zodResolver } from '@hookform/resolvers/zod'; import { useCallback, useState } from 'react'; import { Controller, type SubmitHandler, useForm } from 'react-hook-form'; -import { useRecoilState, useSetRecoilState } from 'recoil'; +import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2'; +import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; import { Key } from 'ts-key-enum'; import { z } from 'zod'; @@ -68,10 +69,10 @@ export const CreateProfile = () => { const { t } = useLingui(); const setNextOnboardingStatus = useSetNextOnboardingStatus(); const { enqueueErrorSnackBar } = useSnackBar(); - const [currentWorkspaceMember, setCurrentWorkspaceMember] = useRecoilState( + const [currentWorkspaceMember, setCurrentWorkspaceMember] = useRecoilStateV2( currentWorkspaceMemberState, ); - const setCurrentUser = useSetRecoilState(currentUserState); + const setCurrentUser = useSetRecoilStateV2(currentUserState); const { updateOneRecord } = useUpdateOneRecord(); // Form diff --git a/packages/twenty-front/src/pages/onboarding/CreateWorkspace.tsx b/packages/twenty-front/src/pages/onboarding/CreateWorkspace.tsx index ac501e1927..b9a48d9020 100644 --- a/packages/twenty-front/src/pages/onboarding/CreateWorkspace.tsx +++ b/packages/twenty-front/src/pages/onboarding/CreateWorkspace.tsx @@ -20,7 +20,7 @@ import { ApolloError } from '@apollo/client'; import { Trans, useLingui } from '@lingui/react/macro'; import { isNonEmptyString } from '@sniptt/guards'; import { motion } from 'framer-motion'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { isDefined } from 'twenty-shared/utils'; import { H2Title } from 'twenty-ui/display'; import { Loader } from 'twenty-ui/feedback'; @@ -74,7 +74,7 @@ export const CreateWorkspace = () => { const [pendingCreationLoaderStep, setPendingCreationLoaderStep] = useState( PendingCreationLoaderStep.None, ); - const currentWorkspace = useRecoilValue(currentWorkspaceState); + const currentWorkspace = useRecoilValueV2(currentWorkspaceState); const validationSchema = z .object({ diff --git a/packages/twenty-front/src/pages/onboarding/InviteTeam.tsx b/packages/twenty-front/src/pages/onboarding/InviteTeam.tsx index e2ef9ca442..182c8d787f 100644 --- a/packages/twenty-front/src/pages/onboarding/InviteTeam.tsx +++ b/packages/twenty-front/src/pages/onboarding/InviteTeam.tsx @@ -18,7 +18,7 @@ import { useFieldArray, useForm, } from 'react-hook-form'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { Key } from 'ts-key-enum'; import { isDefined } from 'twenty-shared/utils'; import { IconCopy, SeparatorLineText } from 'twenty-ui/display'; @@ -65,8 +65,8 @@ export const InviteTeam = () => { const { enqueueSuccessSnackBar } = useSnackBar(); const { sendInvitation } = useCreateWorkspaceInvitation(); const setNextOnboardingStatus = useSetNextOnboardingStatus(); - const currentWorkspace = useRecoilValue(currentWorkspaceState); - const calendarBookingPageId = useRecoilValue(calendarBookingPageIdState); + const currentWorkspace = useRecoilValueV2(currentWorkspaceState); + const calendarBookingPageId = useRecoilValueV2(calendarBookingPageIdState); const hasCalendarBooking = isDefined(calendarBookingPageId); const { diff --git a/packages/twenty-front/src/pages/onboarding/PaymentSuccess.tsx b/packages/twenty-front/src/pages/onboarding/PaymentSuccess.tsx index da88c60533..ab85db35d9 100644 --- a/packages/twenty-front/src/pages/onboarding/PaymentSuccess.tsx +++ b/packages/twenty-front/src/pages/onboarding/PaymentSuccess.tsx @@ -7,7 +7,7 @@ import { useSubscriptionStatus } from '@/workspace/hooks/useSubscriptionStatus'; import styled from '@emotion/styled'; import { t } from '@lingui/core/macro'; import { useState } from 'react'; -import { useSetRecoilState } from 'recoil'; +import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; import { AppPath } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { IconCheck } from 'twenty-ui/display'; @@ -32,7 +32,7 @@ export const PaymentSuccess = () => { const navigate = useNavigateApp(); const subscriptionStatus = useSubscriptionStatus(); const [getCurrentUser] = useGetCurrentUserLazyQuery(); - const setCurrentUser = useSetRecoilState(currentUserState); + const setCurrentUser = useSetRecoilStateV2(currentUserState); const [isLoading, setIsLoading] = useState(false); const navigateWithSubscriptionCheck = async () => { if (isLoading) return; diff --git a/packages/twenty-front/src/pages/onboarding/SyncEmails.tsx b/packages/twenty-front/src/pages/onboarding/SyncEmails.tsx index e7959e2c85..4e4368779f 100644 --- a/packages/twenty-front/src/pages/onboarding/SyncEmails.tsx +++ b/packages/twenty-front/src/pages/onboarding/SyncEmails.tsx @@ -1,7 +1,8 @@ import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { useState } from 'react'; -import { useRecoilState, useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; +import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2'; import { Key } from 'ts-key-enum'; import { SubTitle } from '@/auth/components/SubTitle'; @@ -58,7 +59,7 @@ export const SyncEmails = () => { const [visibility, setVisibility] = useState( MessageChannelVisibility.SHARE_EVERYTHING, ); - const [lastAuthenticatedMethod] = useRecoilState( + const [lastAuthenticatedMethod] = useRecoilStateV2( lastAuthenticatedMethodState, ); const [skipSyncEmailOnboardingStatusMutation] = @@ -86,16 +87,18 @@ export const SyncEmails = () => { const userAuthenticatedWithSSO = lastAuthenticatedMethod === AuthenticatedMethod.SSO; - const isGoogleMessagingEnabled = useRecoilValue( + const isGoogleMessagingEnabled = useRecoilValueV2( isGoogleMessagingEnabledState, ); - const isMicrosoftMessagingEnabled = useRecoilValue( + const isMicrosoftMessagingEnabled = useRecoilValueV2( isMicrosoftMessagingEnabledState, ); - const isGoogleCalendarEnabled = useRecoilValue(isGoogleCalendarEnabledState); + const isGoogleCalendarEnabled = useRecoilValueV2( + isGoogleCalendarEnabledState, + ); - const isMicrosoftCalendarEnabled = useRecoilValue( + const isMicrosoftCalendarEnabled = useRecoilValueV2( isMicrosoftCalendarEnabledState, ); diff --git a/packages/twenty-front/src/pages/onboarding/internal/ChooseYourPlanContent.tsx b/packages/twenty-front/src/pages/onboarding/internal/ChooseYourPlanContent.tsx index 89e20d8959..7e2184f7cd 100644 --- a/packages/twenty-front/src/pages/onboarding/internal/ChooseYourPlanContent.tsx +++ b/packages/twenty-front/src/pages/onboarding/internal/ChooseYourPlanContent.tsx @@ -12,8 +12,9 @@ import { useHandleCheckoutSession } from '@/billing/hooks/useHandleCheckoutSessi import { calendarBookingPageIdState } from '@/client-config/states/calendarBookingPageIdState'; import styled from '@emotion/styled'; import { Trans, useLingui } from '@lingui/react/macro'; -import { useRecoilState, useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { AppPath } from 'twenty-shared/types'; +import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2'; import { isDefined } from 'twenty-shared/utils'; import { Loader } from 'twenty-ui/feedback'; import { CardPicker, MainButton } from 'twenty-ui/input'; @@ -95,15 +96,14 @@ export const ChooseYourPlanContent = ({ billing }: { billing: Billing }) => { const { getBaseLicensedPriceByPlanKeyAndInterval } = useBaseLicensedPriceByPlanKeyAndInterval(); - const [billingCheckoutSession, setBillingCheckoutSession] = useRecoilState( + const [billingCheckoutSession, setBillingCheckoutSession] = useRecoilStateV2( billingCheckoutSessionState, ); - const calendarBookingPageId = useRecoilValue(calendarBookingPageIdState); + const calendarBookingPageId = useRecoilValueV2(calendarBookingPageIdState); - const [verifyEmailRedirectPath, setVerifyEmailRedirectPath] = useRecoilState( - verifyEmailRedirectPathState, - ); + const [verifyEmailRedirectPath, setVerifyEmailRedirectPath] = + useRecoilStateV2(verifyEmailRedirectPathState); if (isDefined(verifyEmailRedirectPath)) { setVerifyEmailRedirectPath(undefined); } diff --git a/packages/twenty-front/src/pages/settings/SettingsBilling.tsx b/packages/twenty-front/src/pages/settings/SettingsBilling.tsx index bee3a43b4b..f274c6b075 100644 --- a/packages/twenty-front/src/pages/settings/SettingsBilling.tsx +++ b/packages/twenty-front/src/pages/settings/SettingsBilling.tsx @@ -1,7 +1,6 @@ import { Trans, useLingui } from '@lingui/react/macro'; -import { useRecoilValue } from 'recoil'; - import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; import { SettingsBillingContent } from '@/billing/components/SettingsBillingContent'; import { getSettingsPath } from 'twenty-shared/utils'; @@ -11,7 +10,7 @@ import { usePlans } from '@/billing/hooks/usePlans'; export const SettingsBilling = () => { const { t } = useLingui(); - const currentWorkspace = useRecoilValue(currentWorkspaceState); + const currentWorkspace = useRecoilValueV2(currentWorkspaceState); const { isPlansLoaded } = usePlans(); diff --git a/packages/twenty-front/src/pages/settings/SettingsProfile.tsx b/packages/twenty-front/src/pages/settings/SettingsProfile.tsx index 2cd98661f7..b172f008ff 100644 --- a/packages/twenty-front/src/pages/settings/SettingsProfile.tsx +++ b/packages/twenty-front/src/pages/settings/SettingsProfile.tsx @@ -10,7 +10,7 @@ import { useCanChangePassword } from '@/settings/profile/hooks/useCanChangePassw import { useCurrentUserWorkspaceTwoFactorAuthentication } from '@/settings/two-factor-authentication/hooks/useCurrentUserWorkspaceTwoFactorAuthentication'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; import { Trans, useLingui } from '@lingui/react/macro'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { SettingsPath } from 'twenty-shared/types'; import { getSettingsPath } from 'twenty-shared/utils'; import { H2Title, IconShield, Status } from 'twenty-ui/display'; @@ -19,7 +19,7 @@ import { UndecoratedLink } from 'twenty-ui/navigation'; export const SettingsProfile = () => { const { t } = useLingui(); - const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); + const currentWorkspaceMember = useRecoilValueV2(currentWorkspaceMemberState); const { currentUserWorkspaceTwoFactorAuthenticationMethods } = useCurrentUserWorkspaceTwoFactorAuthentication(); diff --git a/packages/twenty-front/src/pages/settings/accounts/SettingsAccounts.tsx b/packages/twenty-front/src/pages/settings/accounts/SettingsAccounts.tsx index dd8eef4d36..7ff538ad23 100644 --- a/packages/twenty-front/src/pages/settings/accounts/SettingsAccounts.tsx +++ b/packages/twenty-front/src/pages/settings/accounts/SettingsAccounts.tsx @@ -10,7 +10,7 @@ import { SettingsAccountsSettingsSection } from '@/settings/accounts/components/ import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; import { useLingui } from '@lingui/react/macro'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { SettingsPath } from 'twenty-shared/types'; import { getSettingsPath } from 'twenty-shared/utils'; import { H2Title } from 'twenty-ui/display'; @@ -18,7 +18,7 @@ import { Section } from 'twenty-ui/layout'; export const SettingsAccounts = () => { const { t } = useLingui(); - const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); + const currentWorkspaceMember = useRecoilValueV2(currentWorkspaceMemberState); const { recordGqlFields } = useGenerateDepthRecordGqlFieldsFromObject({ objectNameSingular: CoreObjectNameSingular.ConnectedAccount, diff --git a/packages/twenty-front/src/pages/settings/admin-panel/SettingsAdminConfigVariableDetails.tsx b/packages/twenty-front/src/pages/settings/admin-panel/SettingsAdminConfigVariableDetails.tsx index 0c7ab8cd86..804561081b 100644 --- a/packages/twenty-front/src/pages/settings/admin-panel/SettingsAdminConfigVariableDetails.tsx +++ b/packages/twenty-front/src/pages/settings/admin-panel/SettingsAdminConfigVariableDetails.tsx @@ -13,7 +13,7 @@ import { SettingsSkeletonLoader } from '@/settings/components/SettingsSkeletonLo import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal'; import { useModal } from '@/ui/layout/modal/hooks/useModal'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { SettingsPath, type ConfigVariableValue } from 'twenty-shared/types'; import { getSettingsPath, isDefined } from 'twenty-shared/utils'; import { H3Title, IconCheck, IconPencil, IconX } from 'twenty-ui/display'; @@ -54,7 +54,7 @@ export const SettingsAdminConfigVariableDetails = () => { const { t } = useLingui(); const [isEditing, setIsEditing] = useState(false); const { openModal } = useModal(); - const isConfigVariablesInDbEnabled = useRecoilValue( + const isConfigVariablesInDbEnabled = useRecoilValueV2( isConfigVariablesInDbEnabledState, ); diff --git a/packages/twenty-front/src/pages/settings/ai/SettingsAIPrompts.tsx b/packages/twenty-front/src/pages/settings/ai/SettingsAIPrompts.tsx index 36d3b2cbe9..4b02534278 100644 --- a/packages/twenty-front/src/pages/settings/ai/SettingsAIPrompts.tsx +++ b/packages/twenty-front/src/pages/settings/ai/SettingsAIPrompts.tsx @@ -10,7 +10,8 @@ import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; import { t } from '@lingui/core/macro'; import { useState } from 'react'; -import { useRecoilState, useRecoilValue } from 'recoil'; +import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { SettingsPath } from 'twenty-shared/types'; import { getSettingsPath, isDefined } from 'twenty-shared/utils'; import { H2Title } from 'twenty-ui/display'; @@ -38,10 +39,10 @@ const StyledTokenBadge = styled.span` export const SettingsAIPrompts = () => { const { enqueueErrorSnackBar } = useSnackBar(); - const [currentWorkspace, setCurrentWorkspace] = useRecoilState( + const [currentWorkspace, setCurrentWorkspace] = useRecoilStateV2( currentWorkspaceState, ); - const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); + const currentWorkspaceMember = useRecoilValueV2(currentWorkspaceMemberState); const [updateWorkspace] = useUpdateWorkspaceMutation(); const { data: previewData, loading: previewLoading } = diff --git a/packages/twenty-front/src/pages/settings/ai/components/SettingsAIModelsTab.tsx b/packages/twenty-front/src/pages/settings/ai/components/SettingsAIModelsTab.tsx index 2ca1972c11..c6e9cb5372 100644 --- a/packages/twenty-front/src/pages/settings/ai/components/SettingsAIModelsTab.tsx +++ b/packages/twenty-front/src/pages/settings/ai/components/SettingsAIModelsTab.tsx @@ -1,5 +1,3 @@ -import { useRecoilState } from 'recoil'; - import { DEFAULT_FAST_MODEL } from '@/ai/constants/DefaultFastModel'; import { DEFAULT_SMART_MODEL } from '@/ai/constants/DefaultSmartModel'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; @@ -7,6 +5,7 @@ import { aiModelsState } from '@/client-config/states/aiModelsState'; import { SettingsOptionCardContentSelect } from '@/settings/components/SettingsOptions/SettingsOptionCardContentSelect'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { Select } from '@/ui/input/components/Select'; +import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2'; import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { t } from '@lingui/core/macro'; import { H2Title, IconBolt } from 'twenty-ui/display'; @@ -21,7 +20,7 @@ const VIRTUAL_MODEL_IDS: Set = new Set([ export const SettingsAIModelsTab = () => { const { enqueueSuccessSnackBar, enqueueErrorSnackBar } = useSnackBar(); - const [currentWorkspace, setCurrentWorkspace] = useRecoilState( + const [currentWorkspace, setCurrentWorkspace] = useRecoilStateV2( currentWorkspaceState, ); const [updateWorkspace] = useUpdateWorkspaceMutation(); diff --git a/packages/twenty-front/src/pages/settings/applications/tabs/SettingsApplicationDetailContentTab.tsx b/packages/twenty-front/src/pages/settings/applications/tabs/SettingsApplicationDetailContentTab.tsx index 038b865cb3..b4fb826b1c 100644 --- a/packages/twenty-front/src/pages/settings/applications/tabs/SettingsApplicationDetailContentTab.tsx +++ b/packages/twenty-front/src/pages/settings/applications/tabs/SettingsApplicationDetailContentTab.tsx @@ -3,7 +3,7 @@ import { isHiddenSystemField } from '@/object-metadata/utils/isHiddenSystemField import { SettingsLogicFunctionsTable } from '@/settings/logic-functions/components/SettingsLogicFunctionsTable'; import { t } from '@lingui/core/macro'; import { useMemo } from 'react'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { SettingsPath } from 'twenty-shared/types'; import { getSettingsPath, isDefined } from 'twenty-shared/utils'; import { H2Title } from 'twenty-ui/display'; @@ -23,7 +23,7 @@ export const SettingsApplicationDetailContentTab = ({ objects: { id: string }[]; }; }) => { - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); const applicationObjectIds = useMemo( () => application?.objects.map((object) => object.id) ?? [], diff --git a/packages/twenty-front/src/pages/settings/applications/tabs/SettingsApplicationPermissionsTab.tsx b/packages/twenty-front/src/pages/settings/applications/tabs/SettingsApplicationPermissionsTab.tsx index e67ea2c566..f4ad3275b5 100644 --- a/packages/twenty-front/src/pages/settings/applications/tabs/SettingsApplicationPermissionsTab.tsx +++ b/packages/twenty-front/src/pages/settings/applications/tabs/SettingsApplicationPermissionsTab.tsx @@ -7,7 +7,8 @@ import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDr import { type RoleWithPartialMembers } from '@/settings/roles/types/RoleWithPartialMembers'; import { t } from '@lingui/core/macro'; import { useCallback, useEffect, useMemo, useState } from 'react'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; +import { useSetRecoilState } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { STANDARD_OBJECTS } from 'twenty-shared/metadata'; import { isDefined } from 'twenty-shared/utils'; import { v4 as uuidv4 } from 'uuid'; @@ -280,7 +281,7 @@ const MarketplaceRoleEffect = ({ items: ObjectMetadataItem[], ) => void; }) => { - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); const setDraftRole = useSetRecoilState( settingsDraftRoleFamilyState(defaultRole.id), ); diff --git a/packages/twenty-front/src/pages/settings/applications/tabs/SettingsApplicationsCreateTab.tsx b/packages/twenty-front/src/pages/settings/applications/tabs/SettingsApplicationsCreateTab.tsx index 6597d1bf5a..fdd803ba17 100644 --- a/packages/twenty-front/src/pages/settings/applications/tabs/SettingsApplicationsCreateTab.tsx +++ b/packages/twenty-front/src/pages/settings/applications/tabs/SettingsApplicationsCreateTab.tsx @@ -2,7 +2,7 @@ import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMembe import { getDocumentationUrl } from '@/support/utils/getDocumentationUrl'; import styled from '@emotion/styled'; import { useLingui } from '@lingui/react/macro'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { CommandBlock, H2Title, @@ -20,7 +20,7 @@ const StyledButtonContainer = styled.div` export const SettingsApplicationsCreateTab = () => { const { t } = useLingui(); - const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); + const currentWorkspaceMember = useRecoilValueV2(currentWorkspaceMemberState); const { copyToClipboard } = useCopyToClipboard(); diff --git a/packages/twenty-front/src/pages/settings/applications/tabs/SettingsAvailableApplicationDetailContentTab.tsx b/packages/twenty-front/src/pages/settings/applications/tabs/SettingsAvailableApplicationDetailContentTab.tsx index ffcd9cfcfc..31e4bfdbbb 100644 --- a/packages/twenty-front/src/pages/settings/applications/tabs/SettingsAvailableApplicationDetailContentTab.tsx +++ b/packages/twenty-front/src/pages/settings/applications/tabs/SettingsAvailableApplicationDetailContentTab.tsx @@ -1,7 +1,7 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { t } from '@lingui/core/macro'; import { useMemo } from 'react'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { SettingsPath } from 'twenty-shared/types'; import { getSettingsPath, isDefined } from 'twenty-shared/utils'; import { type MarketplaceApp } from '~/generated-metadata/graphql'; @@ -18,7 +18,7 @@ export const SettingsAvailableApplicationDetailContentTab = ({ }: { application: MarketplaceApp; }) => { - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); const { objects, fields, logicFunctions, frontComponents } = application; diff --git a/packages/twenty-front/src/pages/settings/data-model/SettingsNewObject.tsx b/packages/twenty-front/src/pages/settings/data-model/SettingsNewObject.tsx index 423fd6b997..241b53d61f 100644 --- a/packages/twenty-front/src/pages/settings/data-model/SettingsNewObject.tsx +++ b/packages/twenty-front/src/pages/settings/data-model/SettingsNewObject.tsx @@ -14,7 +14,7 @@ import { zodResolver } from '@hookform/resolvers/zod'; import { useLingui } from '@lingui/react/macro'; import { useState } from 'react'; import { FormProvider, useForm } from 'react-hook-form'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { SettingsPath } from 'twenty-shared/types'; import { getSettingsPath, isDefined } from 'twenty-shared/utils'; import { H2Title } from 'twenty-ui/display'; @@ -26,7 +26,7 @@ export const SettingsNewObject = () => { const navigate = useNavigateSettings(); const [isLoading, setIsLoading] = useState(false); const { createOneObjectMetadataItem } = useCreateOneObjectMetadataItem(); - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); const formConfig = useForm({ mode: 'onChange', diff --git a/packages/twenty-front/src/pages/settings/data-model/SettingsObjectTable.tsx b/packages/twenty-front/src/pages/settings/data-model/SettingsObjectTable.tsx index 2489fa117f..aae79687b1 100644 --- a/packages/twenty-front/src/pages/settings/data-model/SettingsObjectTable.tsx +++ b/packages/twenty-front/src/pages/settings/data-model/SettingsObjectTable.tsx @@ -21,7 +21,6 @@ import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { useLingui } from '@lingui/react/macro'; import { type ReactNode, useMemo, useState } from 'react'; -import { useRecoilValue } from 'recoil'; import { SettingsPath } from 'twenty-shared/types'; import { getSettingsPath } from 'twenty-shared/utils'; import { IconArchive, IconChevronRight, IconSettings } from 'twenty-ui/display'; @@ -63,7 +62,7 @@ export const SettingsObjectTable = ({ const { totalCountByObjectMetadataItemNamePlural } = useCombinedGetTotalCount(); - const currentWorkspace = useRecoilValue(currentWorkspaceState); + const currentWorkspace = useRecoilValueV2(currentWorkspaceState); const allObjectSettingsArray = useMemo( () => diff --git a/packages/twenty-front/src/pages/settings/domains/SettingsDomain.tsx b/packages/twenty-front/src/pages/settings/domains/SettingsDomain.tsx index 3127361eb4..8d9fcd401a 100644 --- a/packages/twenty-front/src/pages/settings/domains/SettingsDomain.tsx +++ b/packages/twenty-front/src/pages/settings/domains/SettingsDomain.tsx @@ -13,7 +13,8 @@ import { ApolloError } from '@apollo/client'; import { zodResolver } from '@hookform/resolvers/zod'; import { Trans, useLingui } from '@lingui/react/macro'; import { FormProvider, useForm } from 'react-hook-form'; -import { useRecoilState, useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; +import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2'; import { SettingsPath } from 'twenty-shared/types'; import { getSettingsPath, isDefined } from 'twenty-shared/utils'; import { z } from 'zod'; @@ -34,7 +35,7 @@ export const SettingsDomain = () => { const navigate = useNavigateSettings(); const { checkCustomDomainRecords } = useCheckCustomDomainValidRecords(); const { t } = useLingui(); - const isCloudflareIntegrationEnabled = useRecoilValue( + const isCloudflareIntegrationEnabled = useRecoilValueV2( isCloudflareIntegrationEnabledState, ); @@ -50,7 +51,7 @@ export const SettingsDomain = () => { const { redirectToWorkspaceDomain } = useRedirectToWorkspaceDomain(); const [isSubmitting, setIsSubmitting] = useState(false); - const [currentWorkspace, setCurrentWorkspace] = useRecoilState( + const [currentWorkspace, setCurrentWorkspace] = useRecoilStateV2( currentWorkspaceState, ); diff --git a/packages/twenty-front/src/pages/settings/logic-functions/SettingsLogicFunctionDetail.tsx b/packages/twenty-front/src/pages/settings/logic-functions/SettingsLogicFunctionDetail.tsx index 6bc131d9b1..150458924c 100644 --- a/packages/twenty-front/src/pages/settings/logic-functions/SettingsLogicFunctionDetail.tsx +++ b/packages/twenty-front/src/pages/settings/logic-functions/SettingsLogicFunctionDetail.tsx @@ -21,7 +21,7 @@ import { } from 'twenty-ui/display'; import { useFindOneApplicationQuery } from '~/generated-metadata/graphql'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { SettingsLogicFunctionCodeEditorTab } from '@/settings/logic-functions/components/tabs/SettingsLogicFunctionCodeEditorTab'; const LOGIC_FUNCTION_DETAIL_ID = 'logic-function-detail'; @@ -30,7 +30,7 @@ export const SettingsLogicFunctionDetail = () => { const { logicFunctionId = '', applicationId = '' } = useParams(); const navigate = useNavigate(); - const currentWorkspace = useRecoilValue(currentWorkspaceState); + const currentWorkspace = useRecoilValueV2(currentWorkspaceState); const { data, loading: applicationLoading } = useFindOneApplicationQuery({ variables: { id: applicationId }, diff --git a/packages/twenty-front/src/pages/settings/members/SettingsWorkspaceMember.tsx b/packages/twenty-front/src/pages/settings/members/SettingsWorkspaceMember.tsx index 8e4a3c3b83..8894a78262 100644 --- a/packages/twenty-front/src/pages/settings/members/SettingsWorkspaceMember.tsx +++ b/packages/twenty-front/src/pages/settings/members/SettingsWorkspaceMember.tsx @@ -15,7 +15,7 @@ import { TabList } from '@/ui/layout/tab-list/components/TabList'; import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2'; import { t } from '@lingui/core/macro'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { SettingsPath } from 'twenty-shared/types'; import { getSettingsPath } from 'twenty-shared/utils'; import { IconInfoCircle, IconLockOpen } from 'twenty-ui/display'; @@ -49,10 +49,10 @@ export const SettingsWorkspaceMember = () => { const navigateSettings = useNavigateSettings(); const { enqueueErrorSnackBar, enqueueSuccessSnackBar } = useSnackBar(); const { openModal, closeModal } = useModal(); - const currentWorkspace = useRecoilValue(currentWorkspaceState); + const currentWorkspace = useRecoilValueV2(currentWorkspaceState); const { executeImpersonationAuth } = useImpersonationAuth(); const [impersonate] = useImpersonateMutation(); - const isImpersonating = useRecoilValue(isImpersonatingState); + const isImpersonating = useRecoilValueV2(isImpersonatingState); const canImpersonate = useHasPermissionFlag(PermissionFlagType.IMPERSONATE) && !isImpersonating; diff --git a/packages/twenty-front/src/pages/settings/members/SettingsWorkspaceMembers.tsx b/packages/twenty-front/src/pages/settings/members/SettingsWorkspaceMembers.tsx index eb93f39192..b12dd9e778 100644 --- a/packages/twenty-front/src/pages/settings/members/SettingsWorkspaceMembers.tsx +++ b/packages/twenty-front/src/pages/settings/members/SettingsWorkspaceMembers.tsx @@ -4,9 +4,9 @@ import { Trans, useLingui } from '@lingui/react/macro'; import { isNonEmptyArray } from '@sniptt/guards'; import { useMemo, useState } from 'react'; import { useInView } from 'react-intersection-observer'; -import { useRecoilValue } from 'recoil'; import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; import { useDebounce } from 'use-debounce'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; @@ -52,7 +52,6 @@ import { TableRow } from '@/ui/layout/table/components/TableRow'; import { useDeleteWorkspaceInvitation } from '@/workspace-invitation/hooks/useDeleteWorkspaceInvitation'; import { useResendWorkspaceInvitation } from '@/workspace-invitation/hooks/useResendWorkspaceInvitation'; import { workspaceInvitationsState } from '@/workspace-invitation/states/workspaceInvitationsStates'; -import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; const StyledButtonContainer = styled.div` align-items: center; @@ -117,7 +116,7 @@ export const SettingsWorkspaceMembers = () => { const navigateSettings = useNavigateSettings(); const [isFetchingMore, setIsFetchingMore] = useState(false); const [searchFilter, setSearchFilter] = useState(''); - const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); + const currentWorkspaceMember = useRecoilValueV2(currentWorkspaceMemberState); const [debouncedSearchFilter] = useDebounce(searchFilter, 300); @@ -152,7 +151,7 @@ export const SettingsWorkspaceMembers = () => { const { resendInvitation } = useResendWorkspaceInvitation(); const { deleteWorkspaceInvitation } = useDeleteWorkspaceInvitation(); - const currentWorkspace = useRecoilValue(currentWorkspaceState); + const currentWorkspace = useRecoilValueV2(currentWorkspaceState); const workspaceInvitations = useRecoilValueV2(workspaceInvitationsState); const setWorkspaceInvitations = useSetRecoilStateV2( 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 b46eb47704..7aa5760df5 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 @@ -1,7 +1,8 @@ import styled from '@emotion/styled'; -import { useRecoilState, useSetRecoilState } from 'recoil'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; +import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2'; +import { useSetRecoilState } from 'recoil'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; import { getDateFnsLocale } from '@/ui/field/display/utils/getDateFnsLocale.util'; @@ -28,7 +29,7 @@ const StyledContainer = styled.div` export const LocalePicker = () => { const { t } = useLingui(); const store = useStore(); - const [currentWorkspaceMember, setCurrentWorkspaceMember] = useRecoilState( + const [currentWorkspaceMember, setCurrentWorkspaceMember] = useRecoilStateV2( currentWorkspaceMemberState, ); const setDateLocale = useSetRecoilState(dateLocaleState); diff --git a/packages/twenty-front/src/pages/settings/security/SettingsSecurity.tsx b/packages/twenty-front/src/pages/settings/security/SettingsSecurity.tsx index 5d0c80fc70..da170fa01a 100644 --- a/packages/twenty-front/src/pages/settings/security/SettingsSecurity.tsx +++ b/packages/twenty-front/src/pages/settings/security/SettingsSecurity.tsx @@ -21,7 +21,7 @@ import { ToggleImpersonate } from '@/settings/workspace/components/ToggleImperso import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; import { ApolloError } from '@apollo/client'; -import { useRecoilState, useRecoilValue } from 'recoil'; +import { useRecoilValue } from 'recoil'; import { SettingsPath } from 'twenty-shared/types'; import { getSettingsPath } from 'twenty-shared/utils'; import { Tag } from 'twenty-ui/components'; @@ -36,6 +36,7 @@ import { Button } from 'twenty-ui/input'; import { Card, Section } from 'twenty-ui/layout'; import { useUpdateWorkspaceMutation } from '~/generated-metadata/graphql'; import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; +import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2'; const StyledContainer = styled.div` width: 100%; @@ -63,11 +64,13 @@ export const SettingsSecurity = () => { const { t } = useLingui(); const { enqueueErrorSnackBar } = useSnackBar(); - const isMultiWorkspaceEnabled = useRecoilValue(isMultiWorkspaceEnabledState); - const isClickHouseConfigured = useRecoilValue(isClickHouseConfiguredState); + const isMultiWorkspaceEnabled = useRecoilValueV2( + isMultiWorkspaceEnabledState, + ); + const isClickHouseConfigured = useRecoilValueV2(isClickHouseConfiguredState); const authProviders = useRecoilValueV2(authProvidersState); const SSOIdentitiesProviders = useRecoilValue(SSOIdentitiesProvidersState); - const [currentWorkspace, setCurrentWorkspace] = useRecoilState( + const [currentWorkspace, setCurrentWorkspace] = useRecoilStateV2( currentWorkspaceState, ); const [updateWorkspace] = useUpdateWorkspaceMutation(); diff --git a/packages/twenty-front/src/pages/settings/security/event-logs/components/EventLogFilters.tsx b/packages/twenty-front/src/pages/settings/security/event-logs/components/EventLogFilters.tsx index 356dbc4fd5..fd8e8b1608 100644 --- a/packages/twenty-front/src/pages/settings/security/event-logs/components/EventLogFilters.tsx +++ b/packages/twenty-front/src/pages/settings/security/event-logs/components/EventLogFilters.tsx @@ -1,8 +1,7 @@ import styled from '@emotion/styled'; import { useLingui } from '@lingui/react/macro'; -import { useRecoilValue } from 'recoil'; - import { currentWorkspaceMembersState } from '@/auth/states/currentWorkspaceMembersState'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; import { Select } from '@/ui/input/components/Select'; import { TextInput } from '@/ui/input/components/TextInput'; @@ -39,7 +38,9 @@ export const EventLogFilters = ({ onChange, }: EventLogFiltersProps) => { const { t } = useLingui(); - const currentWorkspaceMembers = useRecoilValue(currentWorkspaceMembersState); + const currentWorkspaceMembers = useRecoilValueV2( + currentWorkspaceMembersState, + ); const { objectMetadataItems } = useObjectMetadataItems(); const { getIcon } = useIcons(); diff --git a/packages/twenty-front/src/testing/decorators/ObjectMetadataItemsDecorator.tsx b/packages/twenty-front/src/testing/decorators/ObjectMetadataItemsDecorator.tsx index e6a903568e..e3a718e4e9 100644 --- a/packages/twenty-front/src/testing/decorators/ObjectMetadataItemsDecorator.tsx +++ b/packages/twenty-front/src/testing/decorators/ObjectMetadataItemsDecorator.tsx @@ -1,10 +1,11 @@ import { type Decorator } from '@storybook/react-vite'; import { useEffect } from 'react'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; import { currentUserState } from '@/auth/states/currentUserState'; import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; +import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { PreComputedChipGeneratorsProvider } from '@/object-metadata/components/PreComputedChipGeneratorsProvider'; import { useLoadMockedObjectMetadataItems } from '@/object-metadata/hooks/useLoadMockedObjectMetadataItems'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; @@ -12,12 +13,14 @@ import { mockedUserData } from '~/testing/mock-data/users'; import { mockWorkspaceMembers } from '~/testing/mock-data/workspace-members'; export const ObjectMetadataItemsDecorator: Decorator = (Story) => { - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); - const setCurrentWorkspaceMember = useSetRecoilState( + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); + const setCurrentWorkspaceMember = useSetRecoilStateV2( currentWorkspaceMemberState, ); - const setCurrentUser = useSetRecoilState(currentUserState); - const setCurrentUserWorkspace = useSetRecoilState(currentUserWorkspaceState); + const setCurrentUser = useSetRecoilStateV2(currentUserState); + const setCurrentUserWorkspace = useSetRecoilStateV2( + currentUserWorkspaceState, + ); const { loadMockedObjectMetadataItems } = useLoadMockedObjectMetadataItems(); diff --git a/packages/twenty-front/src/testing/decorators/PrefetchLoadedDecorator.tsx b/packages/twenty-front/src/testing/decorators/PrefetchLoadedDecorator.tsx index 5a1b976898..78877e50a0 100644 --- a/packages/twenty-front/src/testing/decorators/PrefetchLoadedDecorator.tsx +++ b/packages/twenty-front/src/testing/decorators/PrefetchLoadedDecorator.tsx @@ -1,15 +1,17 @@ import { type Decorator } from '@storybook/react-vite'; -import { useSetRecoilState } from 'recoil'; import { prefetchIsLoadedFamilyState } from '@/prefetch/states/prefetchIsLoadedFamilyState'; import { PrefetchKey } from '@/prefetch/types/PrefetchKey'; +import { useSetFamilyRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetFamilyRecoilStateV2'; export const PrefetchLoadedDecorator: Decorator = (Story) => { - const setAreFavoritesPrefetched = useSetRecoilState( - prefetchIsLoadedFamilyState(PrefetchKey.AllFavorites), + const setAreFavoritesPrefetched = useSetFamilyRecoilStateV2( + prefetchIsLoadedFamilyState, + PrefetchKey.AllFavorites, ); - const setAreFavoritesFoldersPrefetched = useSetRecoilState( - prefetchIsLoadedFamilyState(PrefetchKey.AllFavoritesFolders), + const setAreFavoritesFoldersPrefetched = useSetFamilyRecoilStateV2( + prefetchIsLoadedFamilyState, + PrefetchKey.AllFavoritesFolders, ); setAreFavoritesPrefetched(true); diff --git a/packages/twenty-front/src/testing/decorators/PrefetchLoadingDecorator.tsx b/packages/twenty-front/src/testing/decorators/PrefetchLoadingDecorator.tsx index b05504fa08..cc5f29ff29 100644 --- a/packages/twenty-front/src/testing/decorators/PrefetchLoadingDecorator.tsx +++ b/packages/twenty-front/src/testing/decorators/PrefetchLoadingDecorator.tsx @@ -1,8 +1,8 @@ import { type Decorator } from '@storybook/react-vite'; -import { useSetRecoilState } from 'recoil'; import { prefetchIsLoadedFamilyState } from '@/prefetch/states/prefetchIsLoadedFamilyState'; import { PrefetchKey } from '@/prefetch/types/PrefetchKey'; +import { useSetFamilyRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetFamilyRecoilStateV2'; import { useEffect, useState } from 'react'; export const PrefetchLoadingDecorator: Decorator = (Story, context) => { @@ -10,12 +10,14 @@ export const PrefetchLoadingDecorator: Decorator = (Story, context) => { const prefetchLoadingSetDelay = parameters.prefetchLoadingSetDelay ?? 0; - const setAreFavoritesFoldersPrefetched = useSetRecoilState( - prefetchIsLoadedFamilyState(PrefetchKey.AllFavoritesFolders), + const setAreFavoritesFoldersPrefetched = useSetFamilyRecoilStateV2( + prefetchIsLoadedFamilyState, + PrefetchKey.AllFavoritesFolders, ); - const setAreFavoritesPrefetched = useSetRecoilState( - prefetchIsLoadedFamilyState(PrefetchKey.AllFavorites), + const setAreFavoritesPrefetched = useSetFamilyRecoilStateV2( + prefetchIsLoadedFamilyState, + PrefetchKey.AllFavorites, ); const [isInitialized, setIsInitialized] = useState(false); diff --git a/packages/twenty-front/src/testing/decorators/RecordTableDecorator.tsx b/packages/twenty-front/src/testing/decorators/RecordTableDecorator.tsx index 0980b27eba..d59d7788eb 100644 --- a/packages/twenty-front/src/testing/decorators/RecordTableDecorator.tsx +++ b/packages/twenty-front/src/testing/decorators/RecordTableDecorator.tsx @@ -1,11 +1,13 @@ import { type Decorator } from '@storybook/react-vite'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; +import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext'; import { getActionMenuIdFromRecordIndexId } from '@/action-menu/utils/getActionMenuIdFromRecordIndexId'; import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState'; import { labelIdentifierFieldMetadataItemSelector } from '@/object-metadata/states/labelIdentifierFieldMetadataItemSelector'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { useFamilySelectorValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilySelectorValueV2'; +import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition'; import { RecordComponentInstanceContextsWrapper } from '@/object-record/components/RecordComponentInstanceContextsWrapper'; @@ -51,7 +53,7 @@ const InternalTableStateLoaderEffect = ({ contextStoreCurrentViewIdComponentState, ); - const setCoreViews = useSetRecoilState(coreViewsState); + const setCoreViews = useSetRecoilStateV2(coreViewsState); const setRecordTableData = useSetRecordTableData({ recordTableId, @@ -142,10 +144,11 @@ const InternalTableContextProviders = ({ ]), ); - const labelIdentifierFieldMetadataItem = useRecoilValue( - labelIdentifierFieldMetadataItemSelector({ + const labelIdentifierFieldMetadataItem = useFamilySelectorValueV2( + labelIdentifierFieldMetadataItemSelector, + { objectMetadataItemId: objectMetadataItem.id, - }), + }, ); const triggerEvent = 'CLICK'; @@ -202,7 +205,7 @@ export const RecordTableDecorator: Decorator = (Story, context) => { const { recordTableObjectNameSingular: objectNameSingular } = context.parameters; - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState); const objectMetadataItem = objectMetadataItems.find( (objectMetadataItem) => diff --git a/packages/twenty-front/src/testing/decorators/WorkspaceDecorator.tsx b/packages/twenty-front/src/testing/decorators/WorkspaceDecorator.tsx index e2cdbea912..ff2cc95921 100644 --- a/packages/twenty-front/src/testing/decorators/WorkspaceDecorator.tsx +++ b/packages/twenty-front/src/testing/decorators/WorkspaceDecorator.tsx @@ -1,11 +1,11 @@ import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; +import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; import { type Decorator } from '@storybook/react-vite'; import { useEffect } from 'react'; -import { useSetRecoilState } from 'recoil'; import { mockCurrentWorkspace } from '~/testing/mock-data/users'; export const WorkspaceDecorator: Decorator = (Story) => { - const setCurrentWorkspace = useSetRecoilState(currentWorkspaceState); + const setCurrentWorkspace = useSetRecoilStateV2(currentWorkspaceState); useEffect(() => { setCurrentWorkspace(mockCurrentWorkspace); diff --git a/packages/twenty-front/src/testing/jest/JestObjectMetadataItemSetter.tsx b/packages/twenty-front/src/testing/jest/JestObjectMetadataItemSetter.tsx index 5f249ffc24..585eaafde3 100644 --- a/packages/twenty-front/src/testing/jest/JestObjectMetadataItemSetter.tsx +++ b/packages/twenty-front/src/testing/jest/JestObjectMetadataItemSetter.tsx @@ -1,8 +1,8 @@ import { type ReactNode, useEffect, useState } from 'react'; -import { useSetRecoilState } from 'recoil'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2'; import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems'; export const JestObjectMetadataItemSetter = ({ @@ -12,7 +12,7 @@ export const JestObjectMetadataItemSetter = ({ children: ReactNode; objectMetadataItems?: ObjectMetadataItem[]; }) => { - const setObjectMetadataItems = useSetRecoilState(objectMetadataItemsState); + const setObjectMetadataItems = useSetRecoilStateV2(objectMetadataItemsState); const [isLoaded, setIsLoaded] = useState(false); useEffect(() => {