Compare commits

...

17 Commits

Author SHA1 Message Date
Charles Bochet 93a35a50f1 Fix 2026-02-22 16:01:46 +01:00
Charles Bochet 98e47b6d1c Fix 2026-02-22 15:31:18 +01:00
Charles Bochet 8c98c2d752 Merge main into jotai-11
- Resolve conflict in useOpenRecordInCommandMenu.ts (keep jotaiStore + add CommandMenuPages)
- Resolve conflict in useFrontComponentExecutionContext.ts (keep useRecoilValueV2, add new imports from main)
- Resolve conflict in MetadataProviderEffect.tsx (adapt split setCoreViews logic to Jotai V2 store.set/get)
- Accept deletion of SettingsAIRouterSettings.tsx from main

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-22 15:18:29 +01:00
Charles Bochet 4c635534ea Fix 2026-02-22 15:00:29 +01:00
Charles Bochet d7715c459f Fix 2026-02-22 14:55:02 +01:00
Charles Bochet 98c395f013 More 2026-02-22 14:39:44 +01:00
Charles Bochet 5f3821aaca Revert "Fix ViewBarFilterDropdownAdvancedFilterButton crash on initial load"
This reverts commit 0df4821e41.
2026-02-22 12:59:06 +01:00
Charles Bochet 0df4821e41 Fix ViewBarFilterDropdownAdvancedFilterButton crash on initial load
Extract content into an inner component so hooks are only called when
objectMetadataId is defined, preventing the render-time throw
("Object metadata id is missing from current view") that fires when the
filter dropdown renders before core views have been fetched.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-22 12:57:13 +01:00
Charles Bochet f7c00991e2 Address PR #18119 review feedback
- Migrate objectPermissionsFamilySelector to createFamilySelectorV2 (all
  deps are Jotai V2; removes stale Recoil bridge)
- Update useShouldActionBeRegisteredParams to use jotaiStore.get instead
  of useRecoilCallback for objectPermissionsFamilySelector
- Migrate recordStoreIdentifiersFamilySelector to createFamilySelectorV2
  using recordStoreFamilyStateV2 (kept in sync); update consumer
  useCommandMenuContextChips to use useFamilySelectorValueV2
- Fix useBrowsingContext to use store from useStore() instead of global
  jotaiStore for objectMetadataItemsState
- Add TODO comment to visibleRecordFieldsComponentSelector acknowledging
  temporary jotaiStore bridge (currentRecordFieldsComponentState still Recoil)
- Simplify createJotaiCookieStorage setItem by computing valueToStore
  first and removing the isNullish variable
- Revert unrelated SDK improvements commit (app-typecheck.ts and other
  SDK files not linked to this Jotai migration PR)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-22 12:47:56 +01:00
Charles Bochet bdaaf8dcb5 Merge branch 'main' into jotai-11 2026-02-20 13:58:06 +01:00
Charles Bochet 4494aef51f Fix 2026-02-20 13:54:54 +01:00
Charles Bochet cb7deae98f feat(twenty-front): migrate object-metadata states and selectors to Jotai V2
Migrate 3 states (objectMetadataItemsState, lastFieldMetadataItemUpdateState,
shouldAppBeLoadingState) and 12 selectors/family selectors to Jotai V2.

Extends the V2 abstraction layer:
- Add SelectorV2 and FamilySelectorV2 support to SelectorGetterV2 type
- Extend buildGetHelper to handle SelectorV2 and FamilySelectorV2

Update ~90 consumer files across the codebase to use V2 hooks
(useRecoilValueV2, useFamilySelectorValueV2, useSetRecoilStateV2)
and jotaiStore.get/set for callbacks. Bridge remaining Recoil selectors
that depend on migrated states via jotaiStore.get.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-20 13:33:27 +01:00
Charles Bochet 3aa63a1c26 Keep migrating to jotai 2026-02-20 13:05:44 +01:00
Charles Bochet e769b28dd5 Migrate client-config states to Jotai V2
- Migrate 22 client-config states from createState to createStateV2
- Update ~55 consumer files to use V2 hooks (useRecoilValueV2, useRecoilStateV2, useSetRecoilStateV2)
- Convert useRecoilCallback to useCallback + jotaiStore.get for captchaState in useRequestFreshCaptchaToken
- Simplify clearSession in useAuth by removing snapshot read/restore for client-config states (now Jotai, unaffected by goToRecoilSnapshot)
- Update test files to use jotaiStore.set for migrated states

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-20 12:38:54 +01:00
Charles Bochet 15d34da5ff Fix 2026-02-20 12:26:04 +01:00
Charles Bochet 674f4353cd Continue jotai migration 2026-02-20 11:58:06 +01:00
Charles Bochet c1a7726c33 Various SDK improvements 2026-02-20 11:21:43 +01:00
503 changed files with 3813 additions and 3500 deletions
@@ -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);
@@ -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)) &&
@@ -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 ??
@@ -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(
@@ -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();
@@ -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<Attachment | null>(null);
const isAttachmentPreviewEnabled = useRecoilValue(
const isAttachmentPreviewEnabled = useRecoilValueV2(
isAttachmentPreviewEnabledState,
);
@@ -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 }) => (
<RecoilRoot>
<MockedProvider cache={cache}>
<JestObjectMetadataItemSetter>
<SnackBarComponentInstanceContext.Provider
value={{ instanceId: 'snack-bar-manager' }}
>
{children}
</SnackBarComponentInstanceContext.Provider>
</JestObjectMetadataItemSetter>
</MockedProvider>
</RecoilRoot>
<JotaiProvider store={jotaiStore}>
<RecoilRoot>
<MockedProvider cache={cache}>
<JestObjectMetadataItemSetter>
<SnackBarComponentInstanceContext.Provider
value={{ instanceId: 'snack-bar-manager' }}
>
{children}
</SnackBarComponentInstanceContext.Provider>
</JestObjectMetadataItemSetter>
</MockedProvider>
</RecoilRoot>
</JotaiProvider>
);
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);
});
@@ -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({
@@ -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<NoteTarget[] | TaskTarget[]>,
) => {
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState);
const activity = useRecoilValue(
recordStoreFamilyState(activityRecordId ?? ''),
@@ -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<ObjectMetadataItem[]>(
const objectMetadataItems = useRecoilValueV2<ObjectMetadataItem[]>(
objectMetadataItemsState,
);
const isNoteTargetMigrated = useIsFeatureEnabled(
@@ -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 &&
@@ -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<ObjectMetadataItem[]>(objectMetadataItemsState)
.getValue();
const objectMetadataItems = jotaiStore.get(
objectMetadataItemsState.atom,
);
const pickedObjectMetadataItem = objectMetadataItems.find(
(objectMetadataItem) =>
@@ -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,
);
@@ -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,
);
@@ -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({
@@ -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();
@@ -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;
@@ -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<Options<any>> = {}) => {
// eslint-disable-next-line twenty/no-state-useref
const apolloRef = useRef<ApolloFactory<NormalizedCacheObject> | 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();
@@ -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) ??
@@ -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)) {
@@ -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 };
};
@@ -1,5 +1,5 @@
import { createState } from '@/ui/utilities/state/utils/createState';
export const isAppEffectRedirectEnabledState = createState<boolean>({
import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2';
export const isAppEffectRedirectEnabledState = createStateV2<boolean>({
key: 'isAppEffectRedirectEnabledState',
defaultValue: true,
});
@@ -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<string | undefined>({
export const verifyEmailRedirectPathState = createStateV2<string | undefined>({
key: 'verifyEmailRedirectPathState',
defaultValue: undefined,
});
@@ -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,
);
@@ -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);
@@ -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(() => {
@@ -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,
);
@@ -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 {
@@ -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,
@@ -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,7 +23,9 @@ 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 { useSignUpInNewWorkspace } from '@/auth/sign-in-up/hooks/useSignUpInNewWorkspace';
@@ -47,11 +44,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 +65,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 +85,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 +102,7 @@ export const useAuth = () => {
useVerifyEmailAndGetWorkspaceAgnosticTokenMutation();
const [getAuthTokensFromOtp] = useGetAuthTokensFromOtpMutation();
const workspacePublicData = useRecoilValue(workspacePublicDataState);
const workspacePublicData = useRecoilValueV2(workspacePublicDataState);
const { setLastAuthenticateWorkspaceDomain } =
useLastAuthenticatedWorkspaceDomain();
@@ -123,7 +119,7 @@ export const useAuth = () => {
const { loadMockedObjectMetadataItems } = useLoadMockedObjectMetadataItems();
const clearSession = useRecoilCallback(
({ snapshot, set }) =>
({ snapshot }) =>
async () => {
const sseClient = getSnapshotValue(snapshot, sseClientState);
@@ -131,53 +127,26 @@ 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 isCurrentUserLoadedValue = jotaiStore.get(
isCurrentUserLoadedState.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 +155,18 @@ export const useAuth = () => {
goToRecoilSnapshot(initialSnapshot);
set(lastAuthenticatedMethodState, lastAuthenticatedMethod);
jotaiStore.set(workspaceAuthProvidersState.atom, authProvidersValue);
jotaiStore.set(workspacePublicDataState.atom, workspacePublicDataValue);
jotaiStore.set(isCurrentUserLoadedState.atom, isCurrentUserLoadedValue);
jotaiStore.set(domainConfigurationState.atom, domainConfigurationValue);
jotaiStore.set(
isCaptchaScriptLoadedState.atom,
isCaptchaScriptLoadedValue,
);
jotaiStore.set(
lastAuthenticatedMethodState.atom,
lastAuthenticatedMethod,
);
await client.clearStore();
setLastAuthenticateWorkspaceDomain(null);
@@ -199,7 +179,6 @@ export const useAuth = () => {
setLastAuthenticateWorkspaceDomain,
loadMockedObjectMetadataItems,
navigate,
isCaptchaScriptLoaded,
],
);
@@ -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;
};
@@ -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();
@@ -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();
@@ -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();
@@ -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();
@@ -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();
@@ -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);
@@ -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();
@@ -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<Form>();
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 {
@@ -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();
@@ -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();
@@ -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();
@@ -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>(
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) {
@@ -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 };
};
@@ -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;
}) => (
<MemoryRouter initialEntries={[initialEntry]}>
<RecoilRoot
initializeState={(snapshot) => {
snapshot.set(
isDeveloperDefaultSignInPrefilledState,
isDeveloperDefaultSignInPrefilled,
);
}}
>
{children}
</RecoilRoot>
</MemoryRouter>
);
}) => {
jotaiStore.set(
isDeveloperDefaultSignInPrefilledState.atom,
isDeveloperDefaultSignInPrefilled,
);
return (
<MemoryRouter initialEntries={[initialEntry]}>
<JotaiProvider store={jotaiStore}>
<RecoilRoot>{children}</RecoilRoot>
</JotaiProvider>
</MemoryRouter>
);
};
describe('useSignInUpForm', () => {
beforeEach(() => {
jest.clearAllMocks();
resetJotaiStore();
});
it('should initialize the form with default values', async () => {
@@ -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();
@@ -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;
@@ -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<Form>) => {
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,
);
@@ -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<ReturnType<typeof makeValidationSchema>>;
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();
@@ -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 {
@@ -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,
);
@@ -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 } =
@@ -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<AvailableWorkspaces>({
export const availableWorkspacesState = createStateV2<AvailableWorkspaces>({
key: 'availableWorkspacesState',
defaultValue: {
availableWorkspacesForSignIn: [],
@@ -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<BillingCheckoutSession>({
key: 'billingCheckoutSessionState',
defaultValue: BILLING_CHECKOUT_SESSION_DEFAULT_VALUE,
});
export const billingCheckoutSessionState =
createStateV2<BillingCheckoutSession>({
key: 'billingCheckoutSessionState',
defaultValue: BILLING_CHECKOUT_SESSION_DEFAULT_VALUE,
});
@@ -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<CurrentUser | null>({
export const currentUserState = createStateV2<CurrentUser | null>({
key: 'currentUserState',
defaultValue: null,
});
@@ -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<CurrentUserWorkspace | null>({
createStateV2<CurrentUserWorkspace | null>({
key: 'currentUserWorkspaceState',
defaultValue: null,
});
@@ -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',
@@ -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<CurrentWorkspaceMember | null>({
createStateV2<CurrentWorkspaceMember | null>({
key: 'currentWorkspaceMemberState',
defaultValue: null,
});
@@ -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',
@@ -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<Application, 'id'> | null;
};
export const currentWorkspaceState = createState<CurrentWorkspace | null>({
export const currentWorkspaceState = createStateV2<CurrentWorkspace | null>({
key: 'currentWorkspaceState',
defaultValue: null,
});
@@ -1,5 +1,5 @@
import { createState } from '@/ui/utilities/state/utils/createState';
export const isCurrentUserLoadedState = createState<boolean>({
import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2';
export const isCurrentUserLoadedState = createStateV2<boolean>({
key: 'isCurrentUserLoadedState',
defaultValue: false,
});
@@ -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<boolean>({
import { tokenPairState } from '@/auth/states/tokenPairState';
import { createSelectorV2 } from '@/ui/utilities/state/jotai/utils/createSelectorV2';
export const isImpersonatingState = createSelectorV2<boolean>({
key: 'isImpersonatingState',
get: ({ get }) => {
const tokenPair = get(tokenPairState);
@@ -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<AuthenticatedMethod | null>({
key: LAST_AUTHENTICATED_METHOD_STORAGE_KEY,
default: null,
effects: [localStorageEffect()],
});
export const lastAuthenticatedMethodState =
createStateV2<AuthenticatedMethod | null>({
key: 'lastAuthenticatedMethodState',
defaultValue: null,
useLocalStorage: true,
});
@@ -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<AuthToken['token'] | null>({
export const loginTokenState = createStateV2<AuthToken['token'] | null>({
key: 'loginTokenState',
defaultValue: null,
});
@@ -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;
@@ -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<SignInUpStep>({
export const signInUpStepState = createStateV2<SignInUpStep>({
key: 'signInUpStepState',
defaultValue: SignInUpStep.Init,
});
@@ -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<AuthTokenPair | null>({
export const tokenPairState = createStateV2<AuthTokenPair | null>({
key: 'tokenPairState',
defaultValue: null,
effects: [
cookieStorageEffect(
'tokenPair',
{},
{
validateInitFn: (payload: AuthTokenPair) =>
Boolean(payload['accessOrWorkspaceAgnosticToken']),
},
),
],
useCookieStorage: {
cookieKey: 'tokenPair',
validateInitFn: (payload: AuthTokenPair) =>
Boolean(payload['accessOrWorkspaceAgnosticToken']),
},
});
@@ -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<PublicWorkspaceDataOutput | null>({
createStateV2<PublicWorkspaceDataOutput | null>({
key: 'workspacePublicDataState',
defaultValue: null,
});
@@ -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;
@@ -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;
@@ -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,
);
@@ -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);
@@ -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);
@@ -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);
@@ -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);
@@ -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);
@@ -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;
@@ -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];
@@ -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,
@@ -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();
@@ -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 }) => (
<JotaiProvider store={jotaiStore}>
<RecoilRoot>{children}</RecoilRoot>
</JotaiProvider>
);
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 }) => (
<RecoilRoot
initializeState={({ set }) => {
set(captchaTokenState, 'test-token');
}}
>
{children}
</RecoilRoot>
),
},
);
jotaiStore.set(captchaTokenState.atom, 'test-token');
const { result } = renderHook(() => useReadCaptchaToken(), {
wrapper: Wrapper,
});
await act(async () => {
const token = await result.current.readCaptchaToken();
@@ -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 }) => (
<JotaiProvider store={jotaiStore}>
<RecoilRoot>{children}</RecoilRoot>
</JotaiProvider>
);
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 }) => (
<RecoilRoot
initializeState={({ set }) => {
set(captchaState, {
provider: CaptchaDriverType.GOOGLE_RECAPTCHA,
siteKey: 'google-site-key',
});
set(isRequestingCaptchaTokenState, false);
}}
>
{children}
</RecoilRoot>
),
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 }) => (
<RecoilRoot
initializeState={({ set }) => {
set(captchaState, {
provider: CaptchaDriverType.TURNSTILE,
siteKey: 'turnstile-site-key',
});
set(isRequestingCaptchaTokenState, false);
}}
>
{children}
</RecoilRoot>
),
wrapper: createWrapper,
});
await act(async () => {
@@ -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 };
};
@@ -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 };
};
@@ -1,5 +1,5 @@
import { createState } from '@/ui/utilities/state/utils/createState';
export const captchaTokenState = createState<string | undefined>({
import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2';
export const captchaTokenState = createStateV2<string | undefined>({
key: 'captchaTokenState',
defaultValue: undefined,
});
@@ -1,5 +1,5 @@
import { createState } from '@/ui/utilities/state/utils/createState';
export const isCaptchaScriptLoadedState = createState<boolean>({
import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2';
export const isCaptchaScriptLoadedState = createStateV2<boolean>({
key: 'isCaptchaScriptLoadedState',
defaultValue: false,
});
@@ -1,5 +1,5 @@
import { createState } from '@/ui/utilities/state/utils/createState';
export const isRequestingCaptchaTokenState = createState<boolean>({
import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2';
export const isRequestingCaptchaTokenState = createStateV2<boolean>({
key: 'isRequestingCaptchaTokenState',
defaultValue: false,
});
@@ -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,
);
@@ -1,5 +1,5 @@
import { createState } from '@/ui/utilities/state/utils/createState';
export const isLoadingTokensFromExtensionState = createState<boolean | null>({
import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2';
export const isLoadingTokensFromExtensionState = createStateV2<boolean | null>({
key: 'isLoadingTokensFromExtensionState',
defaultValue: null,
});
@@ -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<React.PropsWithChildren> = ({
children,
}) => {
const { isErrored, error } = useRecoilValue(clientConfigApiStatusState);
const { isErrored, error } = useRecoilValueV2(clientConfigApiStatusState);
const { t } = useLingui();
return isErrored && error instanceof Error ? (
@@ -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,
);
@@ -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);
@@ -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 {
@@ -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<boolean>({
export const allowRequestsToTwentyIconsState = createStateV2<boolean>({
key: 'allowRequestsToTwentyIcons',
defaultValue: true,
});
@@ -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<ApiConfig | null>({
export const apiConfigState = createStateV2<ApiConfig | null>({
key: 'apiConfigState',
defaultValue: null,
});
@@ -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<Billing | null>({
export const billingState = createStateV2<Billing | null>({
key: 'billingState',
defaultValue: null,
});
@@ -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<string | null>({
export const calendarBookingPageIdState = createStateV2<string | null>({
key: 'calendarBookingPageIdState',
defaultValue: null,
});
@@ -1,5 +1,5 @@
import { createState } from '@/ui/utilities/state/utils/createState';
export const canManageFeatureFlagsState = createState<boolean>({
import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2';
export const canManageFeatureFlagsState = createStateV2<boolean>({
key: 'canManageFeatureFlagsState',
defaultValue: false,
});
@@ -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<Captcha | null>({
export const captchaState = createStateV2<Captcha | null>({
key: 'captchaState',
defaultValue: null,
});
@@ -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<ClientConfigApiStatus>({
export const clientConfigApiStatusState = createStateV2<ClientConfigApiStatus>({
key: 'clientConfigApiStatus',
defaultValue: {
isLoadedOnce: false,

Some files were not shown because too many files have changed in this diff Show More