fix(client-config): set isLoaded to false on API status update (#12371)

Attempt at #12289 (edit Félix: removed fix keyword since I don't think
it fixes it)

---------

Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
Antoine Moreaux
2025-05-30 14:44:31 +02:00
committed by GitHub
parent 35a4b07bc2
commit b7473371b3
25 changed files with 224 additions and 170 deletions

View File

@ -12,3 +12,4 @@ VITE_DISABLE_ESLINT_CHECKER=true
# VITE_HOST=localhost.com
# SSL_KEY_PATH="./certs/your-cert.key"
# SSL_CERT_PATH="./certs/your-cert.crt"
# IS_DEBUG_MODE=false

View File

@ -1,7 +1,7 @@
import { InMemoryCache, NormalizedCacheObject } from '@apollo/client';
import { useMemo, useRef } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
import { useRecoilState, useSetRecoilState } from 'recoil';
import { currentUserState } from '@/auth/states/currentUserState';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
@ -9,7 +9,6 @@ import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { previousUrlState } from '@/auth/states/previousUrlState';
import { tokenPairState } from '@/auth/states/tokenPairState';
import { workspacesState } from '@/auth/states/workspaces';
import { isDebugModeState } from '@/client-config/states/isDebugModeState';
import { REACT_APP_SERVER_BASE_URL } from '~/config';
import { useUpdateEffect } from '~/hooks/useUpdateEffect';
import { isMatchingLocation } from '~/utils/isMatchingLocation';
@ -22,18 +21,16 @@ import { ApolloFactory, Options } from '../services/apollo.factory';
export const useApolloFactory = (options: Partial<Options<any>> = {}) => {
// eslint-disable-next-line @nx/workspace-no-state-useref
const apolloRef = useRef<ApolloFactory<NormalizedCacheObject> | null>(null);
const [isDebugMode] = useRecoilState(isDebugModeState);
const navigate = useNavigate();
const setTokenPair = useSetRecoilState(tokenPairState);
const [currentWorkspace, setCurrentWorkspace] = useRecoilState(
currentWorkspaceState,
);
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const setCurrentUser = useSetRecoilState(currentUserState);
const setCurrentWorkspaceMember = useSetRecoilState(
const [currentWorkspaceMember, setCurrentWorkspaceMember] = useRecoilState(
currentWorkspaceMemberState,
);
const setCurrentUser = useSetRecoilState(currentUserState);
const setCurrentUserWorkspace = useSetRecoilState(currentUserWorkspaceState);
const setWorkspaces = useSetRecoilState(workspacesState);
@ -50,18 +47,15 @@ export const useApolloFactory = (options: Partial<Options<any>> = {}) => {
},
},
}),
headers: {
...(currentWorkspace?.metadataVersion && {
'X-Schema-Version': `${currentWorkspace.metadataVersion}`,
}),
},
defaultOptions: {
watchQuery: {
fetchPolicy: 'cache-and-network',
},
},
connectToDevTools: isDebugMode,
connectToDevTools: process.env.IS_DEBUG_MODE === 'true',
currentWorkspaceMember: currentWorkspaceMember,
currentWorkspace: currentWorkspace,
onTokenPairChange: (tokenPair) => {
setTokenPair(tokenPair);
},
@ -83,7 +77,7 @@ export const useApolloFactory = (options: Partial<Options<any>> = {}) => {
}
},
extraLinks: [],
isDebugMode,
isDebugMode: process.env.IS_DEBUG_MODE === 'true',
// Override options
...options,
});
@ -96,8 +90,6 @@ export const useApolloFactory = (options: Partial<Options<any>> = {}) => {
setCurrentWorkspaceMember,
setCurrentWorkspace,
setWorkspaces,
isDebugMode,
currentWorkspace?.metadataVersion,
setPreviousUrl,
]);
@ -107,5 +99,11 @@ export const useApolloFactory = (options: Partial<Options<any>> = {}) => {
}
}, [currentWorkspaceMember]);
useUpdateEffect(() => {
if (isDefined(apolloRef.current)) {
apolloRef.current.updateCurrentWorkspace(currentWorkspace);
}
}, [currentWorkspace]);
return apolloClient;
};

View File

@ -1,6 +1,7 @@
import { ApolloError, gql, InMemoryCache } from '@apollo/client';
import fetchMock, { enableFetchMocks } from 'jest-fetch-mock';
import { WorkspaceActivationStatus } from '~/generated/graphql';
import { ApolloFactory, Options } from '../apollo.factory';
enableFetchMocks();
@ -31,9 +32,32 @@ const mockWorkspaceMember = {
colorScheme: 'Light' as const,
};
const mockWorkspace = {
id: 'workspace-id',
metadataVersion: 1,
allowImpersonation: false,
activationStatus: WorkspaceActivationStatus.ACTIVE,
billingSubscriptions: [],
currentBillingSubscription: null,
workspaceMembersCount: 0,
isPublicInviteLinkEnabled: false,
isGoogleAuthEnabled: false,
isMicrosoftAuthEnabled: false,
isPasswordAuthEnabled: false,
isCustomDomainEnabled: false,
hasValidEnterpriseKey: false,
subdomain: 'test',
customDomain: 'test.com',
workspaceUrls: {
subdomainUrl: 'test.com',
customUrl: 'test.com',
},
};
const createMockOptions = (): Options<any> => ({
uri: 'http://localhost:3000',
currentWorkspaceMember: mockWorkspaceMember,
currentWorkspace: mockWorkspace,
cache: new InMemoryCache(),
isDebugMode: true,
onError: mockOnError,

View File

@ -13,6 +13,7 @@ import { createUploadLink } from 'apollo-upload-client';
import { renewToken } from '@/auth/services/AuthService';
import { CurrentWorkspaceMember } from '@/auth/states/currentWorkspaceMemberState';
import { CurrentWorkspace } from '@/auth/states/currentWorkspaceState';
import { AuthTokenPair } from '~/generated/graphql';
import { logDebug } from '~/utils/logDebug';
@ -34,6 +35,7 @@ export interface Options<TCacheShape> extends ApolloClientOptions<TCacheShape> {
onTokenPairChange?: (tokenPair: AuthTokenPair) => void;
onUnauthenticatedError?: () => void;
currentWorkspaceMember: CurrentWorkspaceMember | null;
currentWorkspace: CurrentWorkspace | null;
extraLinks?: ApolloLink[];
isDebugMode?: boolean;
}
@ -41,6 +43,7 @@ export interface Options<TCacheShape> extends ApolloClientOptions<TCacheShape> {
export class ApolloFactory<TCacheShape> implements ApolloManager<TCacheShape> {
private client: ApolloClient<TCacheShape>;
private currentWorkspaceMember: CurrentWorkspaceMember | null = null;
private currentWorkspace: CurrentWorkspace | null = null;
constructor(opts: Options<TCacheShape>) {
const {
@ -50,12 +53,14 @@ export class ApolloFactory<TCacheShape> implements ApolloManager<TCacheShape> {
onTokenPairChange,
onUnauthenticatedError,
currentWorkspaceMember,
currentWorkspace,
extraLinks,
isDebugMode,
...options
} = opts;
this.currentWorkspaceMember = currentWorkspaceMember;
this.currentWorkspace = currentWorkspace;
const buildApolloLink = (): ApolloLink => {
const httpLink = createUploadLink({
@ -84,6 +89,9 @@ export class ApolloFactory<TCacheShape> implements ApolloManager<TCacheShape> {
...(this.currentWorkspaceMember?.locale
? { 'x-locale': this.currentWorkspaceMember.locale }
: { 'x-locale': i18n.locale }),
...(this.currentWorkspace?.metadataVersion && {
'X-Schema-Version': `${this.currentWorkspace.metadataVersion}`,
}),
},
};
});
@ -188,6 +196,10 @@ export class ApolloFactory<TCacheShape> implements ApolloManager<TCacheShape> {
this.currentWorkspaceMember = workspaceMember;
}
updateCurrentWorkspace(workspace: CurrentWorkspace | null) {
this.currentWorkspace = workspace;
}
getClient() {
return this.client;
}

View File

@ -0,0 +1,7 @@
import { getTokenPair } from '@/apollo/utils/getTokenPair';
import { isDefined } from 'twenty-shared/utils';
export const hasTokenPair = () => {
const tokenPair = getTokenPair();
return isDefined(tokenPair) && isDefined(tokenPair.accessToken?.token);
};

View File

@ -33,15 +33,15 @@ export const AppRouterProviders = () => {
const pageTitle = getPageTitleFromPath(pathname);
return (
<CaptchaProvider>
<ApolloProvider>
<BaseThemeProvider>
<ClientConfigProviderEffect />
<ClientConfigProvider>
<ChromeExtensionSidecarEffect />
<ChromeExtensionSidecarProvider>
<UserProviderEffect />
<WorkspaceProviderEffect />
<ClientConfigProvider>
<CaptchaProvider>
<ChromeExtensionSidecarEffect />
<ChromeExtensionSidecarProvider>
<UserProvider>
<AuthProvider>
<ApolloMetadataClientProvider>
@ -71,9 +71,9 @@ export const AppRouterProviders = () => {
</AuthProvider>
</UserProvider>
</ChromeExtensionSidecarProvider>
</CaptchaProvider>
</ClientConfigProvider>
</BaseThemeProvider>
</ApolloProvider>
</CaptchaProvider>
);
};

View File

@ -17,7 +17,7 @@ export const VerifyLoginTokenEffect = () => {
const navigate = useNavigateApp();
const { verifyLoginToken } = useVerifyLogin();
const { isLoaded: clientConfigLoaded } = useRecoilValue(
const { isSaved: clientConfigLoaded } = useRecoilValue(
clientConfigApiStatusState,
);

View File

@ -1,6 +1,5 @@
import { useAuth } from '@/auth/hooks/useAuth';
import { billingState } from '@/client-config/states/billingState';
import { isDebugModeState } from '@/client-config/states/isDebugModeState';
import { isDeveloperDefaultSignInPrefilledState } from '@/client-config/states/isDeveloperDefaultSignInPrefilledState';
import { supportChatState } from '@/client-config/states/supportChatState';
import { workspaceAuthProvidersState } from '@/workspace/states/workspaceAuthProvidersState';
@ -118,7 +117,6 @@ describe('useAuth', () => {
isDeveloperDefaultSignInPrefilledState,
);
const supportChat = useRecoilValue(supportChatState);
const isDebugMode = useRecoilValue(isDebugModeState);
const isMultiWorkspaceEnabled = useRecoilValue(
isMultiWorkspaceEnabledState,
);
@ -131,7 +129,6 @@ describe('useAuth', () => {
billing,
isDeveloperDefaultSignInPrefilled,
supportChat,
isDebugMode,
isMultiWorkspaceEnabled,
},
};
@ -160,7 +157,6 @@ describe('useAuth', () => {
supportDriver: 'none',
supportFrontChatId: null,
});
expect(state.isDebugMode).toBe(false);
});
it('should handle credential sign-up', async () => {

View File

@ -11,11 +11,9 @@ import {
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { isCurrentUserLoadedState } from '@/auth/states/isCurrentUserLoadingState';
import { workspacesState } from '@/auth/states/workspaces';
import { billingState } from '@/client-config/states/billingState';
import { clientConfigApiStatusState } from '@/client-config/states/clientConfigApiStatusState';
import { isDebugModeState } from '@/client-config/states/isDebugModeState';
import { supportChatState } from '@/client-config/states/supportChatState';
import { ColorScheme } from '@/workspace-member/types/WorkspaceMember';
import { REACT_APP_SERVER_BASE_URL } from '~/config';
@ -42,25 +40,27 @@ import { currentUserState } from '../states/currentUserState';
import { tokenPairState } from '../states/tokenPairState';
import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState';
import { isCurrentUserLoadedState } from '@/auth/states/isCurrentUserLoadedState';
import {
SignInUpStep,
signInUpStepState,
} from '@/auth/states/signInUpStepState';
import { workspacePublicDataState } from '@/auth/states/workspacePublicDataState';
import { BillingCheckoutSession } from '@/auth/types/billingCheckoutSession.type';
import { apiConfigState } from '@/client-config/states/apiConfigState';
import { captchaState } from '@/client-config/states/captchaState';
import { isEmailVerificationRequiredState } from '@/client-config/states/isEmailVerificationRequiredState';
import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
import { sentryConfigState } from '@/client-config/states/sentryConfigState';
import { useIsCurrentLocationOnAWorkspace } from '@/domain-manager/hooks/useIsCurrentLocationOnAWorkspace';
import { useLastAuthenticatedWorkspaceDomain } from '@/domain-manager/hooks/useLastAuthenticatedWorkspaceDomain';
import { useOrigin } from '@/domain-manager/hooks/useOrigin';
import { useRedirect } from '@/domain-manager/hooks/useRedirect';
import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain';
import { domainConfigurationState } from '@/domain-manager/states/domainConfigurationState';
import { useRefreshObjectMetadataItems } from '@/object-metadata/hooks/useRefreshObjectMetadataItem';
import { workspaceAuthProvidersState } from '@/workspace/states/workspaceAuthProvidersState';
import { i18n } from '@lingui/core';
import { useSearchParams } from 'react-router-dom';
import { useNavigate, useSearchParams } from 'react-router-dom';
import { APP_LOCALES } from 'twenty-shared/translations';
import { isDefined } from 'twenty-shared/utils';
import { iconsState } from 'twenty-ui/display';
@ -85,8 +85,6 @@ export const useAuth = () => {
isEmailVerificationRequiredState,
);
const { refreshObjectMetadataItems } = useRefreshObjectMetadataItems();
const setSignInUpStep = useSetRecoilState(signInUpStepState);
const setCurrentWorkspace = useSetRecoilState(currentWorkspaceState);
const setWorkspaces = useSetRecoilState(workspacesState);
@ -119,10 +117,13 @@ export const useAuth = () => {
const [, setSearchParams] = useSearchParams();
const navigate = useNavigate();
const clearSession = useRecoilCallback(
({ snapshot }) =>
async () => {
const emptySnapshot = snapshot_UNSTABLE();
const iconsValue = snapshot.getLoadable(iconsState).getValue();
const authProvidersValue = snapshot
.getLoadable(workspaceAuthProvidersState)
@ -132,7 +133,6 @@ export const useAuth = () => {
.getLoadable(isDeveloperDefaultSignInPrefilledState)
.getValue();
const supportChat = snapshot.getLoadable(supportChatState).getValue();
const isDebugMode = snapshot.getLoadable(isDebugModeState).getValue();
const captcha = snapshot.getLoadable(captchaState).getValue();
const clientConfigApiStatus = snapshot
.getLoadable(clientConfigApiStatusState)
@ -146,6 +146,12 @@ export const useAuth = () => {
const domainConfiguration = snapshot
.getLoadable(domainConfigurationState)
.getValue();
const apiConfig = snapshot.getLoadable(apiConfigState).getValue();
const sentryConfig = snapshot.getLoadable(sentryConfigState).getValue();
const workspacePublicData = snapshot
.getLoadable(workspacePublicDataState)
.getValue();
const initialSnapshot = emptySnapshot.map(({ set }) => {
set(iconsState, iconsValue);
set(workspaceAuthProvidersState, authProvidersValue);
@ -155,22 +161,27 @@ export const useAuth = () => {
isDeveloperDefaultSignInPrefilled,
);
set(supportChatState, supportChat);
set(isDebugModeState, isDebugMode);
set(captchaState, captcha);
set(apiConfigState, apiConfig);
set(sentryConfigState, sentryConfig);
set(workspacePublicDataState, workspacePublicData);
set(clientConfigApiStatusState, clientConfigApiStatus);
set(isCurrentUserLoadedState, isCurrentUserLoaded);
set(isMultiWorkspaceEnabledState, isMultiWorkspaceEnabled);
set(domainConfigurationState, domainConfiguration);
return undefined;
});
goToRecoilSnapshot(initialSnapshot);
await client.clearStore();
sessionStorage.clear();
localStorage.clear();
await client.clearStore();
// We need to explicitly clear the state to trigger the cookie deletion which include the parent domain
setLastAuthenticateWorkspaceDomain(null);
navigate(AppPath.SignInUp);
},
[client, goToRecoilSnapshot, setLastAuthenticateWorkspaceDomain],
[navigate, client, goToRecoilSnapshot, setLastAuthenticateWorkspaceDomain],
);
const handleGetLoginTokenFromCredentials = useCallback(
@ -368,16 +379,9 @@ export const useAuth = () => {
),
);
await refreshObjectMetadataItems();
await loadCurrentUser();
},
[
getAuthTokensFromLoginToken,
setTokenPair,
refreshObjectMetadataItems,
loadCurrentUser,
origin,
],
[getAuthTokensFromLoginToken, setTokenPair, loadCurrentUser, origin],
);
const handleCredentialsSignIn = useCallback(

View File

@ -1,21 +1,30 @@
import React from 'react';
import React, { useMemo } from 'react';
import { CaptchaProviderScriptLoaderEffect } from '@/captcha/components/CaptchaProviderScriptLoaderEffect';
import { isCaptchaRequiredForPath } from '@/captcha/utils/isCaptchaRequiredForPath';
import { useLocation } from 'react-router-dom';
export const CaptchaProvider = ({ children }: React.PropsWithChildren) => {
export const CaptchaProvider = React.memo(
({ children }: React.PropsWithChildren) => {
const location = useLocation();
if (!isCaptchaRequiredForPath(location.pathname)) {
return <>{children}</>;
}
const isCaptchaRequired = useMemo(
() => isCaptchaRequiredForPath(location.pathname),
[location.pathname],
);
return (
<>
{isCaptchaRequired && (
<>
<div id="captcha-widget" data-size="invisible"></div>
<CaptchaProviderScriptLoaderEffect />
</>
)}
{children}
</>
);
};
},
);
CaptchaProvider.displayName = 'CaptchaProvider';

View File

@ -9,7 +9,6 @@ import { clientConfigApiStatusState } from '@/client-config/states/clientConfigA
import { isAnalyticsEnabledState } from '@/client-config/states/isAnalyticsEnabledState';
import { isAttachmentPreviewEnabledState } from '@/client-config/states/isAttachmentPreviewEnabledState';
import { isConfigVariablesInDbEnabledState } from '@/client-config/states/isConfigVariablesInDbEnabledState';
import { isDebugModeState } from '@/client-config/states/isDebugModeState';
import { isDeveloperDefaultSignInPrefilledState } from '@/client-config/states/isDeveloperDefaultSignInPrefilledState';
import { isEmailVerificationRequiredState } from '@/client-config/states/isEmailVerificationRequiredState';
import { isGoogleCalendarEnabledState } from '@/client-config/states/isGoogleCalendarEnabledState';
@ -26,7 +25,6 @@ import { useRecoilState, useSetRecoilState } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
export const ClientConfigProviderEffect = () => {
const setIsDebugMode = useSetRecoilState(isDebugModeState);
const setIsAnalyticsEnabled = useSetRecoilState(isAnalyticsEnabledState);
const setDomainConfiguration = useSetRecoilState(domainConfigurationState);
const setAuthProviders = useSetRecoilState(authProvidersState);
@ -90,10 +88,17 @@ export const ClientConfigProviderEffect = () => {
const { data, loading, error, fetchClientConfig } = useClientConfig();
useEffect(() => {
if (!clientConfigApiStatus.isLoaded) {
if (
!clientConfigApiStatus.isLoadedOnce &&
!clientConfigApiStatus.isLoading
) {
fetchClientConfig();
}
}, [clientConfigApiStatus.isLoaded, fetchClientConfig]);
}, [
clientConfigApiStatus.isLoadedOnce,
clientConfigApiStatus.isLoading,
fetchClientConfig,
]);
useEffect(() => {
if (loading) return;
@ -124,7 +129,6 @@ export const ClientConfigProviderEffect = () => {
magicLink: false,
sso: data?.clientConfig.authProviders.sso,
});
setIsDebugMode(data?.clientConfig.debugMode);
setIsAnalyticsEnabled(data?.clientConfig.analyticsEnabled);
setIsDeveloperDefaultSignInPrefilled(data?.clientConfig.signInPrefilled);
setIsMultiWorkspaceEnabled(data?.clientConfig.isMultiWorkspaceEnabled);
@ -167,24 +171,23 @@ export const ClientConfigProviderEffect = () => {
);
setClientConfigApiStatus((currentStatus) => ({
...currentStatus,
isLoaded: true,
isSaved: true,
}));
}, [
data,
setIsDebugMode,
loading,
error,
setIsDeveloperDefaultSignInPrefilled,
setIsMultiWorkspaceEnabled,
setIsEmailVerificationRequired,
setSupportChat,
setBilling,
setSentryConfig,
loading,
setClientConfigApiStatus,
setCaptcha,
setChromeExtensionId,
setApiConfig,
setIsAnalyticsEnabled,
error,
setDomainConfiguration,
setAuthProviders,
setCanManageFeatureFlags,

View File

@ -1,4 +1,4 @@
import { useRecoilCallback, useRecoilValue } from 'recoil';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { ClientConfig } from '~/generated/graphql';
import { clientConfigApiStatusState } from '../states/clientConfigApiStatusState';
import { getClientConfig } from '../utils/getClientConfig';
@ -13,40 +13,37 @@ type UseClientConfigResult = {
export const useClientConfig = (): UseClientConfigResult => {
const clientConfigApiStatus = useRecoilValue(clientConfigApiStatusState);
const setClientConfigApiStatus = useSetRecoilState(
clientConfigApiStatusState,
);
const fetchClientConfig = useRecoilCallback(
({ set }) =>
async () => {
set(clientConfigApiStatusState, (prev) => ({
const fetchClientConfig = async () => {
setClientConfigApiStatus((prev) => ({
...prev,
isLoading: true,
isErrored: false,
error: undefined,
}));
try {
const clientConfig = await getClientConfig();
set(clientConfigApiStatusState, (prev) => ({
setClientConfigApiStatus((prev) => ({
...prev,
isLoading: false,
isLoaded: true,
isLoadedOnce: true,
isErrored: false,
error: undefined,
data: { clientConfig },
}));
} catch (err) {
const error =
err instanceof Error
? err
: new Error('Failed to fetch client config');
set(clientConfigApiStatusState, (prev) => ({
err instanceof Error ? err : new Error('Failed to fetch client config');
setClientConfigApiStatus((prev) => ({
...prev,
isLoading: false,
isErrored: true,
error,
}));
}
},
[],
);
};
return {
data: clientConfigApiStatus.data,

View File

@ -2,9 +2,10 @@ import { createState } from 'twenty-ui/utilities';
import { ClientConfig } from '~/generated/graphql';
type ClientConfigApiStatus = {
isLoaded: boolean;
isLoadedOnce: boolean;
isLoading: boolean;
isErrored: boolean;
isSaved: boolean;
error?: Error;
data?: { clientConfig: ClientConfig };
};
@ -12,9 +13,10 @@ type ClientConfigApiStatus = {
export const clientConfigApiStatusState = createState<ClientConfigApiStatus>({
key: 'clientConfigApiStatus',
defaultValue: {
isLoaded: false,
isLoadedOnce: false,
isLoading: false,
isErrored: false,
isSaved: false,
error: undefined,
data: undefined,
},

View File

@ -1,5 +0,0 @@
import { createState } from 'twenty-ui/utilities';
export const isDebugModeState = createState<boolean>({
key: 'isDebugModeState',
defaultValue: false,
});

View File

@ -2,14 +2,12 @@ import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainCo
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
import { getViewType } from '@/context-store/utils/getViewType';
import { useSetLastVisitedObjectMetadataId } from '@/navigation/hooks/useSetLastVisitedObjectMetadataId';
import { useSetLastVisitedViewForObjectMetadataNamePlural } from '@/navigation/hooks/useSetLastVisitedViewForObjectMetadataNamePlural';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { prefetchViewFromViewIdFamilySelector } from '@/prefetch/states/selector/prefetchViewFromViewIdFamilySelector';
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
import { View } from '@/views/types/View';
import { ViewType } from '@/views/types/ViewType';
import { useEffect } from 'react';
import { useRecoilValue } from 'recoil';
@ -124,31 +122,3 @@ export const MainContextStoreProviderEffect = ({
return null;
};
const getViewType = ({
isSettingsPage,
isRecordShowPage,
isRecordIndexPage,
view,
}: {
isSettingsPage: boolean;
isRecordShowPage: boolean;
isRecordIndexPage: boolean;
view?: View;
}) => {
if (isSettingsPage) {
return null;
}
if (isRecordIndexPage) {
return view?.type === ViewType.Kanban
? ContextStoreViewType.Kanban
: ContextStoreViewType.Table;
}
if (isRecordShowPage) {
return ContextStoreViewType.ShowPage;
}
return null;
};

View File

@ -0,0 +1,31 @@
import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
import { View } from '@/views/types/View';
import { ViewType } from '@/views/types/ViewType';
export const getViewType = ({
isSettingsPage,
isRecordShowPage,
isRecordIndexPage,
view,
}: {
isSettingsPage: boolean;
isRecordShowPage: boolean;
isRecordIndexPage: boolean;
view?: View;
}) => {
if (isSettingsPage) {
return null;
}
if (isRecordIndexPage) {
return view?.type === ViewType.Kanban
? ContextStoreViewType.Kanban
: ContextStoreViewType.Table;
}
if (isRecordShowPage) {
return ContextStoreViewType.ShowPage;
}
return null;
};

View File

@ -1,11 +1,8 @@
import { loadDevMessages, loadErrorMessages } from '@apollo/client/dev';
import { useEffect } from 'react';
import { useRecoilValue } from 'recoil';
import { isDebugModeState } from '@/client-config/states/isDebugModeState';
export const ApolloDevLogEffect = () => {
const isDebugMode = useRecoilValue(isDebugModeState);
const isDebugMode = process.env.IS_DEBUG_MODE === 'true';
useEffect(() => {
if (isDebugMode) {

View File

@ -1,5 +1,4 @@
import { isDebugModeState } from '@/client-config/states/isDebugModeState';
import { useRecoilTransactionObserver_UNSTABLE, useRecoilValue } from 'recoil';
import { useRecoilTransactionObserver_UNSTABLE } from 'recoil';
import { logDebug } from '~/utils/logDebug';
@ -15,7 +14,7 @@ const formatTitle = (stateName: string) => {
};
export const RecoilDebugObserverEffect = () => {
const isDebugMode = useRecoilValue(isDebugModeState);
const isDebugMode = process.env.IS_DEBUG_MODE === 'true';
useRecoilTransactionObserver_UNSTABLE(({ snapshot }) => {
if (!isDebugMode) {

View File

@ -28,7 +28,7 @@ export const useGetPublicWorkspaceDataByDomain = () => {
origin,
},
skip:
!clientConfigApiStatus.isLoaded ||
!clientConfigApiStatus.isSaved ||
(isMultiWorkspaceEnabled && isDefaultDomain) ||
isDefined(workspacePublicData),
onCompleted: (data) => {
@ -38,9 +38,17 @@ export const useGetPublicWorkspaceDataByDomain = () => {
setWorkspacePublicDataState(data.getPublicWorkspaceDataByDomain);
},
onError: (error) => {
// Only redirect to default domain if it's a workspace not found error
const isWorkspaceNotFoundError = error.graphQLErrors?.some(
(graphQLError) => graphQLError.extensions?.code === 'NOT_FOUND',
);
if (isWorkspaceNotFoundError) {
redirectToDefaultDomain();
} else {
// eslint-disable-next-line no-console
console.error(error);
redirectToDefaultDomain();
}
},
});

View File

@ -5,8 +5,8 @@ import { currentUserState } from '@/auth/states/currentUserState';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { useLoadMockedObjectMetadataItems } from '@/object-metadata/hooks/useLoadMockedObjectMetadataItems';
import { useRefreshObjectMetadataItems } from '@/object-metadata/hooks/useRefreshObjectMetadataItem';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
import { isWorkspaceActiveOrSuspended } from 'twenty-shared/workspace';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
export const ObjectMetadataItemsLoadEffect = () => {
const currentUser = useRecoilValue(currentUserState);

View File

@ -1,7 +1,7 @@
import React from 'react';
import { useRecoilValue } from 'recoil';
import { isCurrentUserLoadedState } from '@/auth/states/isCurrentUserLoadingState';
import { isCurrentUserLoadedState } from '@/auth/states/isCurrentUserLoadedState';
import { dateTimeFormatState } from '@/localization/states/dateTimeFormatState';
import { AppPath } from '@/types/AppPath';
import { UserContext } from '@/users/contexts/UserContext';

View File

@ -1,13 +1,13 @@
import { useEffect, useState } from 'react';
import { useRecoilCallback, useRecoilState, useSetRecoilState } from 'recoil';
import { useIsLogged } from '@/auth/hooks/useIsLogged';
import { currentUserState } from '@/auth/states/currentUserState';
import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState';
import { currentWorkspaceDeletedMembersState } from '@/auth/states/currentWorkspaceDeletedMembersStates';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { currentWorkspaceMembersState } from '@/auth/states/currentWorkspaceMembersStates';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { isCurrentUserLoadedState } from '@/auth/states/isCurrentUserLoadingState';
import { isCurrentUserLoadedState } from '@/auth/states/isCurrentUserLoadedState';
import { workspacesState } from '@/auth/states/workspaces';
import { DateFormat } from '@/localization/constants/DateFormat';
import { TimeFormat } from '@/localization/constants/TimeFormat';
@ -21,6 +21,7 @@ import { AppPath } from '@/types/AppPath';
import { getDateFnsLocale } from '@/ui/field/display/utils/getDateFnsLocale.util';
import { ColorScheme } from '@/workspace-member/types/WorkspaceMember';
import { enUS } from 'date-fns/locale';
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
import { APP_LOCALES, SOURCE_LOCALE } from 'twenty-shared/translations';
import { isDefined } from 'twenty-shared/utils';
@ -31,7 +32,6 @@ import { dynamicActivate } from '~/utils/i18n/dynamicActivate';
import { isMatchingLocation } from '~/utils/isMatchingLocation';
export const UserProviderEffect = () => {
const [isLoading, setIsLoading] = useState(true);
const location = useLocation();
const [isCurrentUserLoaded, setIsCurrentUserLoaded] = useRecoilState(
@ -42,6 +42,8 @@ export const UserProviderEffect = () => {
const setCurrentUserWorkspace = useSetRecoilState(currentUserWorkspaceState);
const setWorkspaces = useSetRecoilState(workspacesState);
const setDateTimeFormat = useSetRecoilState(dateTimeFormatState);
const isLoggedIn = useIsLogged();
const updateLocaleCatalog = useRecoilCallback(
({ snapshot, set }) =>
async (newLocale: keyof typeof APP_LOCALES) => {
@ -68,8 +70,9 @@ export const UserProviderEffect = () => {
currentWorkspaceDeletedMembersState,
);
const { loading: queryLoading, data: queryData } = useGetCurrentUserQuery({
const { data: queryData, loading: queryLoading } = useGetCurrentUserQuery({
skip:
!isLoggedIn ||
isCurrentUserLoaded ||
isMatchingLocation(location, AppPath.Verify) ||
isMatchingLocation(location, AppPath.VerifyEmail),
@ -77,7 +80,6 @@ export const UserProviderEffect = () => {
useEffect(() => {
if (!queryLoading) {
setIsLoading(false);
setIsCurrentUserLoaded(true);
}
@ -159,15 +161,14 @@ export const UserProviderEffect = () => {
setWorkspaces(workspaces);
}
}, [
queryLoading,
queryData?.currentUser,
setCurrentUser,
setCurrentUserWorkspace,
setCurrentWorkspaceMembers,
isLoading,
queryLoading,
setCurrentWorkspace,
setCurrentWorkspaceMember,
setWorkspaces,
queryData?.currentUser,
setIsCurrentUserLoaded,
setDateTimeFormat,
setCurrentWorkspaceMembersWithDeleted,

View File

@ -1,8 +1,7 @@
import styled from '@emotion/styled';
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
import { useRecoilState, useSetRecoilState } from 'recoil';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { isDebugModeState } from '@/client-config/states/isDebugModeState';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { getDateFnsLocale } from '@/ui/field/display/utils/getDateFnsLocale.util';
@ -29,7 +28,6 @@ export const LocalePicker = () => {
currentWorkspaceMemberState,
);
const setDateLocale = useSetRecoilState(dateLocaleState);
const isDebugMode = useRecoilValue(isDebugModeState);
const { updateOneRecord } = useUpdateOneRecord({
objectNameSingular: CoreObjectNameSingular.WorkspaceMember,
@ -204,7 +202,7 @@ export const LocalePicker = () => {
},
];
if (isDebugMode) {
if (process.env.NODE_ENV === 'development') {
unsortedLocaleOptions.push({
label: t`Pseudo-English`,
value: APP_LOCALES['pseudo-en'],

View File

@ -24,6 +24,7 @@ export default defineConfig(({ command, mode }) => {
SSL_CERT_PATH,
SSL_KEY_PATH,
REACT_APP_PORT,
IS_DEBUG_MODE,
} = env;
const port = isNonEmptyString(REACT_APP_PORT)
@ -191,6 +192,7 @@ export default defineConfig(({ command, mode }) => {
},
'process.env': {
REACT_APP_SERVER_BASE_URL,
IS_DEBUG_MODE,
},
},
css: {