Compare commits

...

3 Commits

Author SHA1 Message Date
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
265 changed files with 1403 additions and 992 deletions
@@ -5,6 +5,7 @@ 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';
@@ -49,7 +50,9 @@ export const usePageChangeEffectNavigateLocation = () => {
const objectMetadataItem = objectMetadataItems?.find(
(objectMetadataItem) => objectMetadataItem.namePlural === objectNamePlural,
);
const verifyEmailRedirectPath = useRecoilValue(verifyEmailRedirectPathState);
const verifyEmailRedirectPath = useRecoilValueV2(
verifyEmailRedirectPathState,
);
if (
(!isLoggedIn || (isLoggedIn && !isOnAWorkspace)) &&
@@ -17,6 +17,7 @@ import { hasAnySoftDeleteFilterOnViewComponentSelector } from '@/object-record/r
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { useContext, useMemo } from 'react';
import { useRecoilCallback, useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
@@ -136,7 +137,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();
@@ -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,26 +113,27 @@ 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]);
const { result } = renderHook(
() => {
const setCurrentWorkspaceMember = useSetRecoilState(
currentWorkspaceMemberState,
);
const setObjectMetadataItems = useSetRecoilState(
objectMetadataItemsState,
);
@@ -145,7 +148,6 @@ describe('useActivityTargetObjectRecords', () => {
return {
activityTargetObjectRecords,
setCurrentWorkspaceMember,
setObjectMetadataItems,
setRecordFromStore,
};
@@ -154,7 +156,6 @@ describe('useActivityTargetObjectRecords', () => {
);
act(() => {
result.current.setCurrentWorkspaceMember(mockWorkspaceMembers[0]);
result.current.setObjectMetadataItems(generatedMockObjectMetadataItems);
result.current.setRecordFromStore(task);
});
@@ -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,7 +93,7 @@ export const EventRow = ({
event,
mainObjectMetadataItem,
}: EventRowProps) => {
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const currentWorkspaceMember = useRecoilValueV2(currentWorkspaceMemberState);
const allowRequestsToTwentyIcons = useRecoilValue(
allowRequestsToTwentyIconsState,
@@ -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();
@@ -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';
@@ -93,7 +93,7 @@ export const PageChangeEffect = () => {
const { executeTasksOnAnyLocationChange } =
useExecuteTasksOnAnyLocationChange();
const isAppEffectRedirectEnabled = useRecoilValue(
const isAppEffectRedirectEnabled = useRecoilValueV2(
isAppEffectRedirectEnabledState,
);
@@ -366,7 +366,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 { useRecoilValue } from 'recoil';
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,
);
@@ -4,6 +4,7 @@ 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';
@@ -158,7 +159,7 @@ describe('useAuth', () => {
const { result } = renderHook(
() => {
const client = useApolloClient();
const workspaceAuthProviders = useRecoilValue(
const workspaceAuthProviders = useRecoilValueV2(
workspaceAuthProvidersState,
);
const billing = useRecoilValue(billingState);
@@ -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,
@@ -5,7 +5,6 @@ import {
useGotoRecoilSnapshot,
useRecoilCallback,
useRecoilValue,
useSetRecoilState,
} from 'recoil';
import { AppPath } from 'twenty-shared/types';
@@ -29,6 +28,9 @@ import {
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';
@@ -71,15 +73,15 @@ 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 isCaptchaScriptLoaded = useRecoilValueV2(isCaptchaScriptLoadedState);
const isMultiWorkspaceEnabled = useRecoilValue(isMultiWorkspaceEnabledState);
const isEmailVerificationRequired = useRecoilValue(
isEmailVerificationRequiredState,
@@ -89,7 +91,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 +108,7 @@ export const useAuth = () => {
useVerifyEmailAndGetWorkspaceAgnosticTokenMutation();
const [getAuthTokensFromOtp] = useGetAuthTokensFromOtpMutation();
const workspacePublicData = useRecoilValue(workspacePublicDataState);
const workspacePublicData = useRecoilValueV2(workspacePublicDataState);
const { setLastAuthenticateWorkspaceDomain } =
useLastAuthenticatedWorkspaceDomain();
@@ -123,7 +125,7 @@ export const useAuth = () => {
const { loadMockedObjectMetadataItems } = useLoadMockedObjectMetadataItems();
const clearSession = useRecoilCallback(
({ snapshot, set }) =>
({ snapshot }) =>
async () => {
const sseClient = getSnapshotValue(snapshot, sseClientState);
@@ -131,9 +133,9 @@ export const useAuth = () => {
const emptySnapshot = snapshot_UNSTABLE();
const authProvidersValue = snapshot
.getLoadable(workspaceAuthProvidersState)
.getValue();
const authProvidersValue = jotaiStore.get(
workspaceAuthProvidersState.atom,
);
const billing = snapshot.getLoadable(billingState).getValue();
const isDeveloperDefaultSignInPrefilled = snapshot
.getLoadable(isDeveloperDefaultSignInPrefilledState)
@@ -143,26 +145,28 @@ export const useAuth = () => {
const clientConfigApiStatus = snapshot
.getLoadable(clientConfigApiStatusState)
.getValue();
const isCurrentUserLoaded = snapshot
.getLoadable(isCurrentUserLoadedState)
.getValue();
const isCurrentUserLoadedValue = jotaiStore.get(
isCurrentUserLoadedState.atom,
);
const isMultiWorkspaceEnabled = snapshot
.getLoadable(isMultiWorkspaceEnabledState)
.getValue();
const domainConfiguration = snapshot
.getLoadable(domainConfigurationState)
.getValue();
const domainConfigurationValue = jotaiStore.get(
domainConfigurationState.atom,
);
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 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,
@@ -172,12 +176,8 @@ export const useAuth = () => {
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);
return undefined;
});
@@ -186,7 +186,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 +210,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,12 @@ 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 { 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 +41,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 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,10 @@ 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 { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
const searchParams = new URLSearchParams(window.location.search);
@@ -21,9 +23,9 @@ enum LoadingStatus {
}
export const SignInUpWorkspaceScopeFormEffect = () => {
const workspaceAuthProviders = useRecoilValue(workspaceAuthProvidersState);
const workspaceAuthProviders = useRecoilValueV2(workspaceAuthProvidersState);
const isRequestingCaptchaToken = useRecoilValue(
const isRequestingCaptchaToken = useRecoilValueV2(
isRequestingCaptchaTokenState,
);
@@ -38,7 +40,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 };
};
@@ -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,
);
@@ -13,6 +13,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,7 +35,7 @@ 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
@@ -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,7 +1,10 @@
import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { selectorFamily } from 'recoil';
// Temporary bridge: reads from Jotai store for migrated state.
// Will be fully migrated to Jotai in PR 6.
export const objectPermissionsFamilySelector = selectorFamily<
{
canRead: boolean;
@@ -13,7 +16,9 @@ export const objectPermissionsFamilySelector = selectorFamily<
get:
({ objectNameSingular }) =>
({ get }) => {
const currentUserWorkspace = get(currentUserWorkspaceState);
const currentUserWorkspace = jotaiStore.get(
currentUserWorkspaceState.atom,
);
const objectMetadataItems = get(objectMetadataItemsState);
const objectMetadataItem = objectMetadataItems.find(
@@ -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];
@@ -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 { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2';
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
import { useRecoilValue } from 'recoil';
import { assertIsDefinedOrThrow } from 'twenty-shared/utils';
import { CaptchaDriverType } from '~/generated-metadata/graphql';
export const CaptchaProviderScriptLoaderEffect = () => {
const captcha = useRecoilValue(captchaState);
const setIsCaptchaScriptLoaded = useSetRecoilState(
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,27 @@
import { act, renderHook, waitFor } from '@testing-library/react';
import { RecoilRoot } from 'recoil';
import { type ReactNode } from 'react';
import { type MutableSnapshot, 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 =
(initializeRecoilState?: (mutableSnapshot: MutableSnapshot) => void) =>
({ children }: { children: ReactNode }) => (
<JotaiProvider store={jotaiStore}>
<RecoilRoot initializeState={initializeRecoilState}>
{children}
</RecoilRoot>
</JotaiProvider>
);
describe('useRequestFreshCaptchaToken', () => {
const mockGrecaptchaExecute = jest.fn();
const mockTurnstileRender = jest.fn();
@@ -47,7 +60,7 @@ describe('useRequestFreshCaptchaToken', () => {
(isCaptchaRequiredForPath as jest.Mock).mockReturnValue(false);
const { result } = renderHook(() => useRequestFreshCaptchaToken(), {
wrapper: RecoilRoot,
wrapper: createWrapper(),
});
await act(async () => {
@@ -61,7 +74,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 +87,15 @@ describe('useRequestFreshCaptchaToken', () => {
});
it('should request a token from Google reCAPTCHA when provider is GOOGLE_RECAPTCHA', async () => {
jotaiStore.set(isRequestingCaptchaTokenState.atom, false);
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(({ set }) => {
set(captchaState, {
provider: CaptchaDriverType.GOOGLE_RECAPTCHA,
siteKey: 'google-site-key',
} as Captcha);
}),
});
await act(async () => {
@@ -104,20 +112,15 @@ describe('useRequestFreshCaptchaToken', () => {
});
it('should request a token from Turnstile when provider is TURNSTILE', async () => {
jotaiStore.set(isRequestingCaptchaTokenState.atom, false);
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(({ set }) => {
set(captchaState, {
provider: CaptchaDriverType.TURNSTILE,
siteKey: 'turnstile-site-key',
} as Captcha);
}),
});
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,15 +1,15 @@
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 { useRecoilCallback } from 'recoil';
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,
);
@@ -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,
});
@@ -2,15 +2,16 @@ 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 { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
export const useCaptcha = () => {
const captcha = useRecoilValue(captchaState);
const captchaToken = useRecoilValue(captchaTokenState);
const captchaToken = useRecoilValueV2(captchaTokenState);
const clientConfigApiStatus = useRecoilValue(clientConfigApiStatusState);
const isCaptchaScriptLoaded = useRecoilValue(isCaptchaScriptLoadedState);
const isCaptchaScriptLoaded = useRecoilValueV2(isCaptchaScriptLoadedState);
const isClientConfigLoaded = clientConfigApiStatus.isLoadedOnce;
const isSiteKeyDefined = isDefined(captcha?.siteKey);
@@ -45,7 +45,7 @@ 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);
@@ -8,6 +8,7 @@ import { expect, userEvent, within } from 'storybook/test';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2';
import { ComponentWithRouterDecorator } from '~/testing/decorators/ComponentWithRouterDecorator';
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
@@ -72,11 +73,11 @@ const meta: Meta<typeof CommandMenu> = {
component: CommandMenuRouter,
decorators: [
(Story) => {
const setCurrentWorkspace = useSetRecoilState(currentWorkspaceState);
const setCurrentUserWorkspace = useSetRecoilState(
const setCurrentWorkspace = useSetRecoilStateV2(currentWorkspaceState);
const setCurrentUserWorkspace = useSetRecoilStateV2(
currentUserWorkspaceState,
);
const setCurrentWorkspaceMember = useSetRecoilState(
const setCurrentWorkspaceMember = useSetRecoilStateV2(
currentWorkspaceMemberState,
);
const setIsCommandMenuOpened = useSetRecoilState(
@@ -138,7 +139,7 @@ export const LimitedPermissions: Story = {
},
decorators: [
(Story) => {
const setCurrentUserWorkspace = useSetRecoilState(
const setCurrentUserWorkspace = useSetRecoilStateV2(
currentUserWorkspaceState,
);
setCurrentUserWorkspace(
@@ -1,27 +1,25 @@
import { useCallback } from 'react';
import { billingCheckoutSessionState } from '@/auth/states/billingCheckoutSessionState';
import { BILLING_CHECKOUT_SESSION_DEFAULT_VALUE } from '@/billing/constants/BillingCheckoutSessionDefaultValue';
import { useRecoilCallback } from 'recoil';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
export const useBuildSearchParamsFromUrlSyncedStates = () => {
const buildSearchParamsFromUrlSyncedStates = useRecoilCallback(
({ snapshot }) =>
async () => {
const billingCheckoutSession = snapshot
.getLoadable(billingCheckoutSessionState)
.getValue();
const buildSearchParamsFromUrlSyncedStates = useCallback(async () => {
const billingCheckoutSession = jotaiStore.get(
billingCheckoutSessionState.atom,
);
const output = {
...(billingCheckoutSession !== BILLING_CHECKOUT_SESSION_DEFAULT_VALUE
? {
billingCheckoutSession: JSON.stringify(billingCheckoutSession),
}
: {}),
};
const output = {
...(billingCheckoutSession !== BILLING_CHECKOUT_SESSION_DEFAULT_VALUE
? {
billingCheckoutSession: JSON.stringify(billingCheckoutSession),
}
: {}),
};
return output;
},
[],
);
return output;
}, []);
return {
buildSearchParamsFromUrlSyncedStates,
@@ -4,9 +4,11 @@ import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWork
import { useIsCurrentLocationOnDefaultDomain } from '@/domain-manager/hooks/useIsCurrentLocationOnDefaultDomain';
import { useOrigin } from '@/domain-manager/hooks/useOrigin';
import { useRedirectToDefaultDomain } from '@/domain-manager/hooks/useRedirectToDefaultDomain';
import { workspaceAuthProvidersState } from '@/workspace/states/workspaceAuthProvidersState';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2';
import { workspaceAuthBypassProvidersState } from '@/workspace/states/workspaceAuthBypassProvidersState';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { workspaceAuthProvidersState } from '@/workspace/states/workspaceAuthProvidersState';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { useGetPublicWorkspaceDataByDomainQuery } from '~/generated-metadata/graphql';
@@ -14,15 +16,15 @@ export const useGetPublicWorkspaceDataByDomain = () => {
const { isDefaultDomain } = useIsCurrentLocationOnDefaultDomain();
const isMultiWorkspaceEnabled = useRecoilValue(isMultiWorkspaceEnabledState);
const { origin } = useOrigin();
const setWorkspaceAuthProviders = useSetRecoilState(
const setWorkspaceAuthProviders = useSetRecoilStateV2(
workspaceAuthProvidersState,
);
const setWorkspaceAuthBypassProviders = useSetRecoilState(
const setWorkspaceAuthBypassProviders = useSetRecoilStateV2(
workspaceAuthBypassProvidersState,
);
const workspacePublicData = useRecoilValue(workspacePublicDataState);
const workspacePublicData = useRecoilValueV2(workspacePublicDataState);
const { redirectToDefaultDomain } = useRedirectToDefaultDomain();
const setWorkspacePublicDataState = useSetRecoilState(
const setWorkspacePublicDataState = useSetRecoilStateV2(
workspacePublicDataState,
);
const clientConfigApiStatus = useRecoilValue(clientConfigApiStatusState);
@@ -1,6 +1,7 @@
import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
import { useReadDefaultDomainFromConfiguration } from '@/domain-manager/hooks/useReadDefaultDomainFromConfiguration';
import { domainConfigurationState } from '@/domain-manager/states/domainConfigurationState';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
@@ -8,7 +9,7 @@ export const useIsCurrentLocationOnAWorkspace = () => {
const { defaultDomain } = useReadDefaultDomainFromConfiguration();
const isMultiWorkspaceEnabled = useRecoilValue(isMultiWorkspaceEnabledState);
const domainConfiguration = useRecoilValue(domainConfigurationState);
const domainConfiguration = useRecoilValueV2(domainConfigurationState);
if (
isMultiWorkspaceEnabled &&
@@ -1,10 +1,11 @@
import { domainConfigurationState } from '@/domain-manager/states/domainConfigurationState';
import { lastAuthenticatedWorkspaceDomainState } from '@/domain-manager/states/lastAuthenticatedWorkspaceDomainState';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2';
export const useLastAuthenticatedWorkspaceDomain = () => {
const domainConfiguration = useRecoilValue(domainConfigurationState);
const setLastAuthenticatedWorkspaceDomain = useSetRecoilState(
const domainConfiguration = useRecoilValueV2(domainConfigurationState);
const setLastAuthenticatedWorkspaceDomain = useSetRecoilStateV2(
lastAuthenticatedWorkspaceDomainState,
);
const setLastAuthenticateWorkspaceDomainWithCookieAttributes = (
@@ -1,9 +1,10 @@
import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
import { domainConfigurationState } from '@/domain-manager/states/domainConfigurationState';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { useRecoilValue } from 'recoil';
export const useReadDefaultDomainFromConfiguration = () => {
const domainConfiguration = useRecoilValue(domainConfigurationState);
const domainConfiguration = useRecoilValueV2(domainConfigurationState);
const isMultiWorkspaceEnabled = useRecoilValue(isMultiWorkspaceEnabledState);
const defaultDomain = isMultiWorkspaceEnabled
@@ -1,7 +1,7 @@
import { type ClientConfig } from '@/client-config/types/ClientConfig';
import { createState } from '@/ui/utilities/state/utils/createState';
import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2';
export const domainConfigurationState = createState<
export const domainConfigurationState = createStateV2<
Pick<ClientConfig, 'frontDomain' | 'defaultSubdomain'>
>({
key: 'domainConfiguration',
@@ -1,7 +1,6 @@
import { cookieStorageEffect } from '~/utils/recoil/cookieStorageEffect';
import { createState } from '@/ui/utilities/state/utils/createState';
import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2';
export const lastAuthenticatedWorkspaceDomainState = createState<
export const lastAuthenticatedWorkspaceDomainState = createStateV2<
| {
workspaceUrl: string;
workspaceId: string;
@@ -13,9 +12,10 @@ export const lastAuthenticatedWorkspaceDomainState = createState<
>({
key: 'lastAuthenticateWorkspaceDomain',
defaultValue: null,
effects: [
cookieStorageEffect('lastAuthenticateWorkspaceDomain', {
expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365), // 1 year
}),
],
useCookieStorage: {
cookieKey: 'lastAuthenticateWorkspaceDomain',
attributes: {
expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365),
},
},
});
@@ -1,10 +1,10 @@
import { useEffect, useState } from 'react';
import { useRecoilValue } from 'recoil';
import { currentUserState } from '@/auth/states/currentUserState';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { sentryConfigState } from '@/client-config/states/sentryConfigState';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { useEffect, useState } from 'react';
import { useRecoilValue } from 'recoil';
import { isNonEmptyString } from '@sniptt/guards';
import { isDefined } from 'twenty-shared/utils';
import { REACT_APP_SERVER_BASE_URL } from '~/config';
@@ -12,9 +12,9 @@ import { REACT_APP_SERVER_BASE_URL } from '~/config';
export const SentryInitEffect = () => {
const sentryConfig = useRecoilValue(sentryConfigState);
const currentUser = useRecoilValue(currentUserState);
const currentWorkspace = useRecoilValue(currentWorkspaceState);
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const currentUser = useRecoilValueV2(currentUserState);
const currentWorkspace = useRecoilValueV2(currentWorkspaceState);
const currentWorkspaceMember = useRecoilValueV2(currentWorkspaceMemberState);
const [isSentryInitialized, setIsSentryInitialized] = useState(false);
const [isSentryInitializing, setIsSentryInitializing] = useState(false);
@@ -13,14 +13,14 @@ import { NavigationDrawerSectionTitle } from '@/ui/navigation/navigation-drawer/
import { useNavigationSection } from '@/ui/navigation/navigation-drawer/hooks/useNavigationSection';
import { isNavigationSectionOpenFamilyState } from '@/ui/navigation/navigation-drawer/states/isNavigationSectionOpenFamilyState';
import { useFamilyRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilyRecoilValueV2';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { useLingui } from '@lingui/react/macro';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { IconFolderPlus } from 'twenty-ui/display';
import { LightIconButton } from 'twenty-ui/input';
export const CurrentWorkspaceMemberFavoritesFolders = () => {
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const currentWorkspaceMember = useRecoilValueV2(currentWorkspaceMemberState);
const { sortedFavorites: favorites } = useFavorites();
const { favoritesByFolder } = useFavoritesByFolder();
@@ -5,6 +5,7 @@ import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMembe
import { useCreateFavorite } from '@/favorites/hooks/useCreateFavorite';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems';
import {
@@ -46,13 +47,10 @@ const Wrapper = getJestMetadataAndApolloMocksWrapper({
describe('useCreateFavorite', () => {
it('should create favorite successfully', async () => {
jotaiStore.set(currentWorkspaceMemberState.atom, mockWorkspaceMember);
const { result } = renderHook(
() => {
const setCurrentWorkspaceMember = useSetRecoilState(
currentWorkspaceMemberState,
);
setCurrentWorkspaceMember(mockWorkspaceMember);
const setMetadataItems = useSetRecoilState(objectMetadataItemsState);
setMetadataItems(generatedMockObjectMetadataItems);
@@ -4,6 +4,7 @@ import { useSetRecoilState } from 'recoil';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { useDeleteFavorite } from '@/favorites/hooks/useDeleteFavorite';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems';
import {
@@ -23,13 +24,10 @@ const Wrapper = getJestMetadataAndApolloMocksWrapper({
describe('useDeleteFavorite', () => {
it('should delete favorite successfully', async () => {
jotaiStore.set(currentWorkspaceMemberState.atom, mockWorkspaceMember);
const { result } = renderHook(
() => {
const setCurrentWorkspaceMember = useSetRecoilState(
currentWorkspaceMemberState,
);
setCurrentWorkspaceMember(mockWorkspaceMember);
const setMetadataItems = useSetRecoilState(objectMetadataItemsState);
setMetadataItems(generatedMockObjectMetadataItems);
@@ -6,6 +6,7 @@ import { useFavorites } from '@/favorites/hooks/useFavorites';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { prefetchFavoritesState } from '@/prefetch/states/prefetchFavoritesState';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems';
import {
@@ -20,16 +21,13 @@ const Wrapper = getJestMetadataAndApolloMocksWrapper({
describe('useFavorites', () => {
it('should fetch and sort favorites successfully', () => {
jotaiStore.set(currentWorkspaceMemberState.atom, mockWorkspaceMember);
const { result } = renderHook(
() => {
const setPrefetchFavorites = useSetRecoilState(prefetchFavoritesState);
setPrefetchFavorites(initialFavorites);
const setCurrentWorkspaceMember = useSetRecoilState(
currentWorkspaceMemberState,
);
setCurrentWorkspaceMember(mockWorkspaceMember);
const setMetadataItems = useSetRecoilState(objectMetadataItemsState);
setMetadataItems(generatedMockObjectMetadataItems);
@@ -11,6 +11,7 @@ import { createFolderDroppableId } from '@/favorites/utils/createFolderDroppable
import { createFolderHeaderDroppableId } from '@/favorites/utils/createFolderHeaderDroppableId';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { prefetchFavoritesState } from '@/prefetch/states/prefetchFavoritesState';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems';
import {
@@ -33,17 +34,14 @@ describe('useHandleFavoriteDragAndDrop', () => {
};
const setupHook = () => {
jotaiStore.set(currentWorkspaceMemberState.atom, mockWorkspaceMember);
return renderHook(
() => {
const setPrefetchFavorites = useSetRecoilState(prefetchFavoritesState);
setPrefetchFavorites(initialFavorites as Favorite[]);
const setCurrentWorkspaceMember = useSetRecoilState(
currentWorkspaceMemberState,
);
const setMetadataItems = useSetRecoilState(objectMetadataItemsState);
setCurrentWorkspaceMember(mockWorkspaceMember);
setMetadataItems(generatedMockObjectMetadataItems);
return {
@@ -1,6 +1,7 @@
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { type Favorite } from '@/favorites/types/Favorite';
import { prefetchFavoritesState } from '@/prefetch/states/prefetchFavoritesState';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { useRecoilValue } from 'recoil';
type PrefetchedFavoritesData = {
@@ -10,7 +11,7 @@ type PrefetchedFavoritesData = {
};
export const usePrefetchedFavoritesData = (): PrefetchedFavoritesData => {
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const currentWorkspaceMember = useRecoilValueV2(currentWorkspaceMemberState);
const currentWorkspaceMemberId = currentWorkspaceMember?.id;
const prefetchFavorites = useRecoilValue(prefetchFavoritesState);
@@ -1,6 +1,7 @@
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { type FavoriteFolder } from '@/favorites/types/FavoriteFolder';
import { prefetchFavoriteFoldersState } from '@/prefetch/states/prefetchFavoriteFoldersState';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { useRecoilValue } from 'recoil';
type PrefetchedFavoritesFoldersData = {
@@ -10,7 +11,9 @@ type PrefetchedFavoritesFoldersData = {
export const usePrefetchedFavoritesFoldersData =
(): PrefetchedFavoritesFoldersData => {
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const currentWorkspaceMember = useRecoilValueV2(
currentWorkspaceMemberState,
);
const currentWorkspaceMemberId = currentWorkspaceMember?.id;
const prefetchFavoriteFolders = useRecoilValue(
@@ -1,4 +1,3 @@
import { useRecoilValue } from 'recoil';
import {
type FrontComponentExecutionContext,
type FrontComponentHostCommunicationApi,
@@ -6,13 +5,14 @@ import {
import { type AppPath } from 'twenty-shared/types';
import { currentUserState } from '@/auth/states/currentUserState';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { useNavigateApp } from '~/hooks/useNavigateApp';
export const useFrontComponentExecutionContext = (): {
executionContext: FrontComponentExecutionContext;
frontComponentHostCommunicationApi: FrontComponentHostCommunicationApi;
} => {
const currentUser = useRecoilValue(currentUserState);
const currentUser = useRecoilValueV2(currentUserState);
const navigateApp = useNavigateApp();
const navigate: FrontComponentHostCommunicationApi['navigate'] = async (
@@ -3,13 +3,13 @@ import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMembe
import { isImpersonatingState } from '@/auth/states/isImpersonatingState';
import { InformationBanner } from '@/information-banner/components/InformationBanner';
import { t } from '@lingui/core/macro';
import { useRecoilValue } from 'recoil';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { isDefined } from 'twenty-shared/utils';
import { IconLogout } from 'twenty-ui/display';
export const InformationBannerIsImpersonating = () => {
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const isImpersonating = useRecoilValue(isImpersonatingState);
const currentWorkspaceMember = useRecoilValueV2(currentWorkspaceMemberState);
const isImpersonating = useRecoilValueV2(isImpersonatingState);
const { signOut } = useAuth();
@@ -3,10 +3,10 @@ import { currentUserState } from '@/auth/states/currentUserState';
import { type InformationBannerKeys } from '@/information-banner/types/InformationBannerKeys';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
import { useRecoilValue } from 'recoil';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
export const useAccountToReconnect = (key: InformationBannerKeys) => {
const currentUser = useRecoilValue(currentUserState);
const currentUser = useRecoilValueV2(currentUserState);
const userVars = currentUser?.userVars;
@@ -1,6 +1,7 @@
import { act, renderHook } from '@testing-library/react';
import { type ReactNode } from 'react';
import { RecoilRoot, type MutableSnapshot } from 'recoil';
import { type MutableSnapshot, RecoilRoot } from 'recoil';
import { Provider as JotaiProvider } from 'jotai';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { DateFormat } from '@/localization/constants/DateFormat';
@@ -8,6 +9,7 @@ import { NumberFormat } from '@/localization/constants/NumberFormat';
import { TimeFormat } from '@/localization/constants/TimeFormat';
import { useFormatPreferences } from '@/localization/hooks/useFormatPreferences';
import { workspaceMemberFormatPreferencesState } from '@/localization/states/workspaceMemberFormatPreferencesState';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { detectCalendarStartDay } from '@/localization/utils/detection/detectCalendarStartDay';
import { detectDateFormat } from '@/localization/utils/detection/detectDateFormat';
import { detectNumberFormat } from '@/localization/utils/detection/detectNumberFormat';
@@ -73,19 +75,30 @@ const mockInitialPreferences = {
calendarStartDay: CalendarStartDay.MONDAY,
};
const Wrapper = ({ children }: { children: ReactNode }) => {
const initializeState = ({ set }: MutableSnapshot) => {
set(currentWorkspaceMemberState, mockCurrentWorkspaceMember);
set(workspaceMemberFormatPreferencesState, mockInitialPreferences);
};
const createWrapper =
(initializeRecoilState?: (mutableSnapshot: MutableSnapshot) => void) =>
({ children }: { children: ReactNode }) => {
const defaultInitState = ({ set }: MutableSnapshot) => {
set(workspaceMemberFormatPreferencesState, mockInitialPreferences);
initializeRecoilState?.({ set } as MutableSnapshot);
};
return <RecoilRoot initializeState={initializeState}>{children}</RecoilRoot>;
};
return (
<JotaiProvider store={jotaiStore}>
<RecoilRoot initializeState={defaultInitState}>{children}</RecoilRoot>
</JotaiProvider>
);
};
describe('useFormatPreferences', () => {
beforeEach(() => {
jest.clearAllMocks();
jotaiStore.set(
currentWorkspaceMemberState.atom,
mockCurrentWorkspaceMember,
);
mockUseUpdateOneRecord.mockReturnValue({
updateOneRecord: mockUpdateOneRecord,
});
@@ -106,7 +119,7 @@ describe('useFormatPreferences', () => {
it('should return format preferences and update functions', () => {
const { result } = renderHook(() => useFormatPreferences(), {
wrapper: Wrapper,
wrapper: createWrapper(),
});
expect(result.current).toHaveProperty('formatPreferences');
@@ -123,7 +136,7 @@ describe('useFormatPreferences', () => {
it('should return current format preferences', () => {
const { result } = renderHook(() => useFormatPreferences(), {
wrapper: Wrapper,
wrapper: createWrapper(),
});
expect(result.current.formatPreferences).toEqual(mockInitialPreferences);
@@ -131,7 +144,7 @@ describe('useFormatPreferences', () => {
it('should update single format preference successfully', async () => {
const { result } = renderHook(() => useFormatPreferences(), {
wrapper: Wrapper,
wrapper: createWrapper(),
});
const newTimeZone = 'Europe/London';
@@ -152,7 +165,7 @@ describe('useFormatPreferences', () => {
it('should handle SYSTEM values by detecting actual format', async () => {
const { result } = renderHook(() => useFormatPreferences(), {
wrapper: Wrapper,
wrapper: createWrapper(),
});
mockDetectTimeZone.mockReturnValue('America/Chicago');
@@ -174,7 +187,7 @@ describe('useFormatPreferences', () => {
it('should update multiple format preferences successfully', async () => {
const { result } = renderHook(() => useFormatPreferences(), {
wrapper: Wrapper,
wrapper: createWrapper(),
});
const updates = {
@@ -198,17 +211,10 @@ describe('useFormatPreferences', () => {
});
it('should not update preferences when user is not logged in', async () => {
const { result } = renderHook(() => useFormatPreferences(), {
wrapper: ({ children }: { children: ReactNode }) => {
const initializeState = ({ set }: MutableSnapshot) => {
set(currentWorkspaceMemberState, null);
set(workspaceMemberFormatPreferencesState, mockInitialPreferences);
};
jotaiStore.set(currentWorkspaceMemberState.atom, null);
return (
<RecoilRoot initializeState={initializeState}>{children}</RecoilRoot>
);
},
const { result } = renderHook(() => useFormatPreferences(), {
wrapper: createWrapper(),
});
await act(async () => {
@@ -220,7 +226,7 @@ describe('useFormatPreferences', () => {
it('should handle update errors gracefully', async () => {
const { result } = renderHook(() => useFormatPreferences(), {
wrapper: Wrapper,
wrapper: createWrapper(),
});
const error = new Error('Update failed');
@@ -238,7 +244,7 @@ describe('useFormatPreferences', () => {
it('should initialize format preferences from workspace member', () => {
const { result } = renderHook(() => useFormatPreferences(), {
wrapper: Wrapper,
wrapper: createWrapper(),
});
act(() => {
@@ -250,7 +256,7 @@ describe('useFormatPreferences', () => {
it('should not initialize format preferences when workspace member is null', () => {
const { result } = renderHook(() => useFormatPreferences(), {
wrapper: Wrapper,
wrapper: createWrapper(),
});
act(() => {
@@ -1,5 +1,5 @@
import { useCallback } from 'react';
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
import { useRecoilState } from 'recoil';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { DateFormat } from '@/localization/constants/DateFormat';
@@ -18,6 +18,8 @@ import { getFormatPreferencesFromWorkspaceMember } from '@/localization/utils/fo
import { getWorkspaceMemberUpdateFromFormatPreferences } from '@/localization/utils/format-preferences/getWorkspaceMemberUpdateFromFormatPreferences';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2';
import { CalendarStartDay } from 'twenty-shared/constants';
import { logError } from '~/utils/logError';
@@ -28,8 +30,8 @@ export const useFormatPreferences = () => {
workspaceMemberFormatPreferences,
setWorkspaceMemberFormatPreferences,
] = useRecoilState(workspaceMemberFormatPreferencesState);
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const setCurrentWorkspaceMember = useSetRecoilState(
const currentWorkspaceMember = useRecoilValueV2(currentWorkspaceMemberState);
const setCurrentWorkspaceMember = useSetRecoilStateV2(
currentWorkspaceMemberState,
);
@@ -1,10 +1,8 @@
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { useLingui } from '@lingui/react/macro';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { IconFolderPlus } from 'twenty-ui/display';
import { LightIconButton } from 'twenty-ui/input';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { CurrentWorkspaceMemberOrphanNavigationMenuItems } from '@/navigation-menu-item/components/CurrentWorkspaceMemberOrphanNavigationMenuItems';
import { NavigationMenuItemFolders } from '@/navigation-menu-item/components/NavigationMenuItemFolders';
import { NavigationMenuItemSkeletonLoader } from '@/navigation-menu-item/components/NavigationMenuItemSkeletonLoader';
@@ -19,9 +17,10 @@ import { NavigationDrawerSectionTitle } from '@/ui/navigation/navigation-drawer/
import { useNavigationSection } from '@/ui/navigation/navigation-drawer/hooks/useNavigationSection';
import { isNavigationSectionOpenFamilyState } from '@/ui/navigation/navigation-drawer/states/isNavigationSectionOpenFamilyState';
import { useFamilyRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilyRecoilValueV2';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
export const CurrentWorkspaceMemberNavigationMenuItemFolders = () => {
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const currentWorkspaceMember = useRecoilValueV2(currentWorkspaceMemberState);
const { navigationMenuItemsSorted } = useSortedNavigationMenuItems();
const { userNavigationMenuItemsByFolder } = useNavigationMenuItemsByFolder();

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