diff --git a/packages/twenty-front/src/modules/activities/blocks/components/FileBlock.tsx b/packages/twenty-front/src/modules/activities/blocks/components/FileBlock.tsx
index 5c8de03869d5..685fdc0c8bb6 100644
--- a/packages/twenty-front/src/modules/activities/blocks/components/FileBlock.tsx
+++ b/packages/twenty-front/src/modules/activities/blocks/components/FileBlock.tsx
@@ -3,7 +3,6 @@ import styled from '@emotion/styled';
import { isNonEmptyString } from '@sniptt/guards';
import { ChangeEvent, useRef } from 'react';
-import { AppThemeProvider } from '@/ui/theme/components/AppThemeProvider';
import { Button } from 'twenty-ui';
import { isDefined } from '~/utils/isDefined';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
@@ -90,33 +89,26 @@ export const FileBlock = createReactBlockSpec(
if (isNonEmptyString(block.props.url)) {
return (
-
-
-
-
- {block.props.name}
-
-
-
+
+
+
+ {block.props.name}
+
+
);
}
return (
-
-
-
-
-
-
+
+
+
+
);
},
},
diff --git a/packages/twenty-front/src/modules/app/components/AppRouterProviders.tsx b/packages/twenty-front/src/modules/app/components/AppRouterProviders.tsx
index 7f64849a27e0..1fe08501ab8e 100644
--- a/packages/twenty-front/src/modules/app/components/AppRouterProviders.tsx
+++ b/packages/twenty-front/src/modules/app/components/AppRouterProviders.tsx
@@ -13,7 +13,8 @@ import { PrefetchDataProvider } from '@/prefetch/components/PrefetchDataProvider
import { DialogManager } from '@/ui/feedback/dialog-manager/components/DialogManager';
import { DialogManagerScope } from '@/ui/feedback/dialog-manager/scopes/DialogManagerScope';
import { SnackBarProvider } from '@/ui/feedback/snack-bar-manager/components/SnackBarProvider';
-import { AppThemeProvider } from '@/ui/theme/components/AppThemeProvider';
+import { UserThemeProviderEffect } from '@/ui/theme/components/AppThemeProvider';
+import { BaseThemeProvider } from '@/ui/theme/components/BaseThemeProvider';
import { PageTitle } from '@/ui/utilities/page-title/components/PageTitle';
import { UserProvider } from '@/users/components/UserProvider';
import { UserProviderEffect } from '@/users/components/UserProviderEffect';
@@ -27,17 +28,18 @@ export const AppRouterProviders = () => {
return (
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
@@ -50,15 +52,15 @@ export const AppRouterProviders = () => {
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
);
};
diff --git a/packages/twenty-front/src/modules/auth/hooks/useAuth.ts b/packages/twenty-front/src/modules/auth/hooks/useAuth.ts
index ae13d831fb7a..e8ef0aab3606 100644
--- a/packages/twenty-front/src/modules/auth/hooks/useAuth.ts
+++ b/packages/twenty-front/src/modules/auth/hooks/useAuth.ts
@@ -17,7 +17,7 @@ import { workspacesState } from '@/auth/states/workspaces';
import { authProvidersState } from '@/client-config/states/authProvidersState';
import { billingState } from '@/client-config/states/billingState';
import { captchaProviderState } from '@/client-config/states/captchaProviderState';
-import { isClientConfigLoadedState } from '@/client-config/states/isClientConfigLoadedState';
+import { clientConfigApiStatusState } from '@/client-config/states/clientConfigApiStatusState';
import { isDebugModeState } from '@/client-config/states/isDebugModeState';
import { isSignInPrefilledState } from '@/client-config/states/isSignInPrefilledState';
import { supportChatState } from '@/client-config/states/supportChatState';
@@ -229,8 +229,8 @@ export const useAuth = () => {
const captchaProvider = snapshot
.getLoadable(captchaProviderState)
.getValue();
- const isClientConfigLoaded = snapshot
- .getLoadable(isClientConfigLoadedState)
+ const clientConfigApiStatus = snapshot
+ .getLoadable(clientConfigApiStatusState)
.getValue();
const isCurrentUserLoaded = snapshot
.getLoadable(isCurrentUserLoadedState)
@@ -244,7 +244,7 @@ export const useAuth = () => {
set(supportChatState, supportChat);
set(isDebugModeState, isDebugMode);
set(captchaProviderState, captchaProvider);
- set(isClientConfigLoadedState, isClientConfigLoaded);
+ set(clientConfigApiStatusState, clientConfigApiStatus);
set(isCurrentUserLoadedState, isCurrentUserLoaded);
return undefined;
});
diff --git a/packages/twenty-front/src/modules/client-config/components/ClientConfigProvider.tsx b/packages/twenty-front/src/modules/client-config/components/ClientConfigProvider.tsx
index 671842687435..d363c191eb56 100644
--- a/packages/twenty-front/src/modules/client-config/components/ClientConfigProvider.tsx
+++ b/packages/twenty-front/src/modules/client-config/components/ClientConfigProvider.tsx
@@ -1,11 +1,21 @@
import { useRecoilValue } from 'recoil';
-import { isClientConfigLoadedState } from '@/client-config/states/isClientConfigLoadedState';
+import { clientConfigApiStatusState } from '@/client-config/states/clientConfigApiStatusState';
+import { ClientConfigError } from '@/error-handler/components/ClientConfigError';
export const ClientConfigProvider: React.FC = ({
children,
}) => {
- const isClientConfigLoaded = useRecoilValue(isClientConfigLoadedState);
+ const { isLoaded, isErrored, error } = useRecoilValue(
+ clientConfigApiStatusState,
+ );
- return isClientConfigLoaded ? <>{children}> : <>>;
+ // TODO: Implement a better loading strategy
+ if (!isLoaded) return null;
+
+ return isErrored && error instanceof Error ? (
+
+ ) : (
+ children
+ );
};
diff --git a/packages/twenty-front/src/modules/client-config/components/ClientConfigProviderEffect.tsx b/packages/twenty-front/src/modules/client-config/components/ClientConfigProviderEffect.tsx
index bf11d6713c2c..c0fcbda1f50b 100644
--- a/packages/twenty-front/src/modules/client-config/components/ClientConfigProviderEffect.tsx
+++ b/packages/twenty-front/src/modules/client-config/components/ClientConfigProviderEffect.tsx
@@ -3,8 +3,8 @@ import { authProvidersState } from '@/client-config/states/authProvidersState';
import { billingState } from '@/client-config/states/billingState';
import { captchaProviderState } from '@/client-config/states/captchaProviderState';
import { chromeExtensionIdState } from '@/client-config/states/chromeExtensionIdState';
+import { clientConfigApiStatusState } from '@/client-config/states/clientConfigApiStatusState';
import { isAnalyticsEnabledState } from '@/client-config/states/isAnalyticsEnabledState';
-import { isClientConfigLoadedState } from '@/client-config/states/isClientConfigLoadedState';
import { isDebugModeState } from '@/client-config/states/isDebugModeState';
import { isSignInPrefilledState } from '@/client-config/states/isSignInPrefilledState';
import { isSignUpDisabledState } from '@/client-config/states/isSignUpDisabledState';
@@ -27,8 +27,8 @@ export const ClientConfigProviderEffect = () => {
const setSupportChat = useSetRecoilState(supportChatState);
const setSentryConfig = useSetRecoilState(sentryConfigState);
- const [isClientConfigLoaded, setIsClientConfigLoaded] = useRecoilState(
- isClientConfigLoadedState,
+ const [clientConfigApiStatus, setClientConfigApiStatus] = useRecoilState(
+ clientConfigApiStatusState,
);
const setCaptchaProvider = useSetRecoilState(captchaProviderState);
@@ -37,42 +37,64 @@ export const ClientConfigProviderEffect = () => {
const setApiConfig = useSetRecoilState(apiConfigState);
- const { data, loading } = useGetClientConfigQuery({
- skip: isClientConfigLoaded,
+ const { data, loading, error } = useGetClientConfigQuery({
+ skip: clientConfigApiStatus.isLoaded,
});
useEffect(() => {
- if (!loading && isDefined(data?.clientConfig)) {
- setIsClientConfigLoaded(true);
- setAuthProviders({
- google: data?.clientConfig.authProviders.google,
- microsoft: data?.clientConfig.authProviders.microsoft,
- password: data?.clientConfig.authProviders.password,
- magicLink: false,
- sso: data?.clientConfig.authProviders.sso,
- });
- setIsDebugMode(data?.clientConfig.debugMode);
- setIsAnalyticsEnabled(data?.clientConfig.analyticsEnabled);
- setIsSignInPrefilled(data?.clientConfig.signInPrefilled);
- setIsSignUpDisabled(data?.clientConfig.signUpDisabled);
-
- setBilling(data?.clientConfig.billing);
- setSupportChat(data?.clientConfig.support);
-
- setSentryConfig({
- dsn: data?.clientConfig?.sentry?.dsn,
- release: data?.clientConfig?.sentry?.release,
- environment: data?.clientConfig?.sentry?.environment,
- });
-
- setCaptchaProvider({
- provider: data?.clientConfig?.captcha?.provider,
- siteKey: data?.clientConfig?.captcha?.siteKey,
- });
-
- setChromeExtensionId(data?.clientConfig?.chromeExtensionId);
- setApiConfig(data?.clientConfig?.api);
+ if (loading) return;
+ setClientConfigApiStatus((currentStatus) => ({
+ ...currentStatus,
+ isLoaded: true,
+ }));
+
+ if (error instanceof Error) {
+ setClientConfigApiStatus((currentStatus) => ({
+ ...currentStatus,
+ isErrored: true,
+ error,
+ }));
+ return;
+ }
+
+ if (!isDefined(data?.clientConfig)) {
+ return;
}
+
+ setClientConfigApiStatus((currentStatus) => ({
+ ...currentStatus,
+ isErrored: false,
+ error: undefined,
+ }));
+
+ setAuthProviders({
+ google: data?.clientConfig.authProviders.google,
+ microsoft: data?.clientConfig.authProviders.microsoft,
+ password: data?.clientConfig.authProviders.password,
+ magicLink: false,
+ sso: data?.clientConfig.authProviders.sso,
+ });
+ setIsDebugMode(data?.clientConfig.debugMode);
+ setIsAnalyticsEnabled(data?.clientConfig.analyticsEnabled);
+ setIsSignInPrefilled(data?.clientConfig.signInPrefilled);
+ setIsSignUpDisabled(data?.clientConfig.signUpDisabled);
+
+ setBilling(data?.clientConfig.billing);
+ setSupportChat(data?.clientConfig.support);
+
+ setSentryConfig({
+ dsn: data?.clientConfig?.sentry?.dsn,
+ release: data?.clientConfig?.sentry?.release,
+ environment: data?.clientConfig?.sentry?.environment,
+ });
+
+ setCaptchaProvider({
+ provider: data?.clientConfig?.captcha?.provider,
+ siteKey: data?.clientConfig?.captcha?.siteKey,
+ });
+
+ setChromeExtensionId(data?.clientConfig?.chromeExtensionId);
+ setApiConfig(data?.clientConfig?.api);
}, [
data,
setAuthProviders,
@@ -83,11 +105,12 @@ export const ClientConfigProviderEffect = () => {
setBilling,
setSentryConfig,
loading,
- setIsClientConfigLoaded,
+ setClientConfigApiStatus,
setCaptchaProvider,
setChromeExtensionId,
setApiConfig,
setIsAnalyticsEnabled,
+ error,
]);
return <>>;
diff --git a/packages/twenty-front/src/modules/client-config/states/clientConfigApiStatusState.ts b/packages/twenty-front/src/modules/client-config/states/clientConfigApiStatusState.ts
new file mode 100644
index 000000000000..08793188ff1a
--- /dev/null
+++ b/packages/twenty-front/src/modules/client-config/states/clientConfigApiStatusState.ts
@@ -0,0 +1,12 @@
+import { createState } from 'twenty-ui';
+
+type ClientConfigApiStatus = {
+ isLoaded: boolean;
+ isErrored: boolean;
+ error?: Error;
+};
+
+export const clientConfigApiStatusState = createState({
+ key: 'clientConfigApiStatus',
+ defaultValue: { isLoaded: false, isErrored: false, error: undefined },
+});
diff --git a/packages/twenty-front/src/modules/client-config/states/isClientConfigLoadedState.ts b/packages/twenty-front/src/modules/client-config/states/isClientConfigLoadedState.ts
deleted file mode 100644
index 7b6cff0b3612..000000000000
--- a/packages/twenty-front/src/modules/client-config/states/isClientConfigLoadedState.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import { createState } from 'twenty-ui';
-
-export const isClientConfigLoadedState = createState({
- key: 'isClientConfigLoadedState',
- defaultValue: false,
-});
diff --git a/packages/twenty-front/src/modules/error-handler/components/AppErrorBoundary.tsx b/packages/twenty-front/src/modules/error-handler/components/AppErrorBoundary.tsx
index fc6c822441c3..a00f61fa6863 100644
--- a/packages/twenty-front/src/modules/error-handler/components/AppErrorBoundary.tsx
+++ b/packages/twenty-front/src/modules/error-handler/components/AppErrorBoundary.tsx
@@ -12,10 +12,16 @@ export const AppErrorBoundary = ({ children }: { children: ReactNode }) => {
});
};
+ // TODO: Implement a better reset strategy, hard reload for now
+ const handleReset = () => {
+ window.location.reload();
+ };
+
return (
{children}
diff --git a/packages/twenty-front/src/modules/error-handler/components/ClientConfigError.tsx b/packages/twenty-front/src/modules/error-handler/components/ClientConfigError.tsx
new file mode 100644
index 000000000000..47141a532063
--- /dev/null
+++ b/packages/twenty-front/src/modules/error-handler/components/ClientConfigError.tsx
@@ -0,0 +1,41 @@
+import styled from '@emotion/styled';
+import { MOBILE_VIEWPORT } from 'twenty-ui';
+import { GenericErrorFallback } from './GenericErrorFallback';
+
+const StyledContainer = styled.div`
+ background: ${({ theme }) => theme.background.noisy};
+ box-sizing: border-box;
+ display: flex;
+ height: 100dvh;
+ width: 100%;
+ padding-top: ${({ theme }) => theme.spacing(3)};
+ padding-left: ${({ theme }) => theme.spacing(3)};
+ padding-bottom: 0;
+
+ @media (max-width: ${MOBILE_VIEWPORT}px) {
+ padding-left: 0;
+ padding-bottom: ${({ theme }) => theme.spacing(3)};
+ }
+`;
+
+type ClientConfigErrorProps = {
+ error: Error;
+};
+
+export const ClientConfigError = ({ error }: ClientConfigErrorProps) => {
+ // TODO: Implement a better loading strategy
+ const handleReset = () => {
+ window.location.reload();
+ };
+
+ return (
+
+
+
+ );
+};
diff --git a/packages/twenty-front/src/modules/error-handler/components/GenericErrorFallback.tsx b/packages/twenty-front/src/modules/error-handler/components/GenericErrorFallback.tsx
index 8b3c647054bc..2d2e8efeb165 100644
--- a/packages/twenty-front/src/modules/error-handler/components/GenericErrorFallback.tsx
+++ b/packages/twenty-front/src/modules/error-handler/components/GenericErrorFallback.tsx
@@ -15,11 +15,16 @@ import {
} from 'twenty-ui';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
-type GenericErrorFallbackProps = FallbackProps;
+type GenericErrorFallbackProps = FallbackProps & {
+ title?: string;
+ hidePageHeader?: boolean;
+};
export const GenericErrorFallback = ({
error,
resetErrorBoundary,
+ title = 'Sorry, something went wrong',
+ hidePageHeader = false,
}: GenericErrorFallbackProps) => {
const location = useLocation();
@@ -33,13 +38,14 @@ export const GenericErrorFallback = ({
return (
-
+ {!hidePageHeader && }
+
- Server’s on a coffee break
+ {title}
{error.message}
@@ -49,7 +55,7 @@ export const GenericErrorFallback = ({
Icon={IconRefresh}
title="Reload"
variant={'secondary'}
- onClick={() => resetErrorBoundary()}
+ onClick={resetErrorBoundary}
/>
diff --git a/packages/twenty-front/src/modules/ui/theme/components/AppThemeProvider.tsx b/packages/twenty-front/src/modules/ui/theme/components/AppThemeProvider.tsx
index abfbe9e20668..3b9e09b25f88 100644
--- a/packages/twenty-front/src/modules/ui/theme/components/AppThemeProvider.tsx
+++ b/packages/twenty-front/src/modules/ui/theme/components/AppThemeProvider.tsx
@@ -1,32 +1,17 @@
-import { useEffect } from 'react';
-import { ThemeProvider } from '@emotion/react';
-import { THEME_DARK, THEME_LIGHT, ThemeContextProvider } from 'twenty-ui';
+import { useContext, useEffect } from 'react';
+import { ThemeSchemeContext } from '@/ui/theme/components/BaseThemeProvider';
+import { useSystemColorScheme } from '@/ui/theme/hooks/useSystemColorScheme';
import { useColorScheme } from '../hooks/useColorScheme';
-import { useSystemColorScheme } from '../hooks/useSystemColorScheme';
-
-type AppThemeProviderProps = {
- children: JSX.Element;
-};
-
-export const AppThemeProvider = ({ children }: AppThemeProviderProps) => {
- const systemColorScheme = useSystemColorScheme();
+export const UserThemeProviderEffect = () => {
const { colorScheme } = useColorScheme();
-
- const computedColorScheme =
- colorScheme === 'System' ? systemColorScheme : colorScheme;
-
- const theme = computedColorScheme === 'Dark' ? THEME_DARK : THEME_LIGHT;
+ const systemColorScheme = useSystemColorScheme();
+ const setThemeScheme = useContext(ThemeSchemeContext);
useEffect(() => {
- document.documentElement.className =
- theme.name === 'dark' ? 'dark' : 'light';
- }, [theme]);
+ setThemeScheme(colorScheme === 'System' ? systemColorScheme : colorScheme);
+ }, [colorScheme, setThemeScheme, systemColorScheme]);
- return (
-
- {children}
-
- );
+ return <>>;
};
diff --git a/packages/twenty-front/src/modules/ui/theme/components/BaseThemeProvider.tsx b/packages/twenty-front/src/modules/ui/theme/components/BaseThemeProvider.tsx
new file mode 100644
index 000000000000..99962eaf5bc0
--- /dev/null
+++ b/packages/twenty-front/src/modules/ui/theme/components/BaseThemeProvider.tsx
@@ -0,0 +1,36 @@
+import { ThemeProvider } from '@emotion/react';
+import { createContext, useState } from 'react';
+import {
+ ColorScheme,
+ THEME_DARK,
+ THEME_LIGHT,
+ ThemeContextProvider,
+} from 'twenty-ui';
+
+import { useSystemColorScheme } from '../hooks/useSystemColorScheme';
+
+type BaseThemeProviderProps = {
+ children: JSX.Element | JSX.Element[];
+};
+
+export const ThemeSchemeContext = createContext<(theme: ColorScheme) => void>(
+ () => {},
+);
+
+export const BaseThemeProvider = ({ children }: BaseThemeProviderProps) => {
+ const systemColorScheme = useSystemColorScheme();
+ const [themeScheme, setThemeScheme] = useState(systemColorScheme);
+
+ document.documentElement.className =
+ themeScheme === 'Dark' ? 'dark' : 'light';
+
+ const theme = themeScheme === 'Dark' ? THEME_DARK : THEME_LIGHT;
+
+ return (
+
+
+ {children}
+
+
+ );
+};
diff --git a/packages/twenty-ui/src/display/index.ts b/packages/twenty-ui/src/display/index.ts
index 45144332b2bf..e8f248ba7a31 100644
--- a/packages/twenty-ui/src/display/index.ts
+++ b/packages/twenty-ui/src/display/index.ts
@@ -49,8 +49,8 @@ export * from './icon/types/IconComponent';
export * from './info/components/Info';
export * from './status/components/Status';
export * from './tag/components/Tag';
-export * from './text/components/SeparatorLineText';
export * from './text/components/HorizontalSeparator';
+export * from './text/components/SeparatorLineText';
export * from './tooltip/AppTooltip';
export * from './tooltip/OverflowingTextWithTooltip';
export * from './typography/components/H1Title';