From 552d6d1ff25155db91b808129b8adc2dc5e76d91 Mon Sep 17 00:00:00 2001 From: khuddite Date: Tue, 19 Nov 2024 09:57:09 -0500 Subject: [PATCH 1/7] Introduce isErrored status for handling initial page loading failure --- .../app/components/AppRouterProviders.tsx | 38 ++++---- .../src/modules/auth/hooks/useAuth.ts | 8 +- .../components/ClientConfigProvider.tsx | 27 +++++- .../components/ClientConfigProviderEffect.tsx | 93 ++++++++++++------- .../states/clientConfigApiStatusState.ts | 11 +++ .../states/isClientConfigLoadedState.ts | 6 -- .../components/GenericErrorFallback.tsx | 23 +++-- packages/twenty-ui/src/display/index.ts | 2 +- 8 files changed, 131 insertions(+), 77 deletions(-) create mode 100644 packages/twenty-front/src/modules/client-config/states/clientConfigApiStatusState.ts delete mode 100644 packages/twenty-front/src/modules/client-config/states/isClientConfigLoadedState.ts diff --git a/packages/twenty-front/src/modules/app/components/AppRouterProviders.tsx b/packages/twenty-front/src/modules/app/components/AppRouterProviders.tsx index 7f64849a27e0..932d88d9674d 100644 --- a/packages/twenty-front/src/modules/app/components/AppRouterProviders.tsx +++ b/packages/twenty-front/src/modules/app/components/AppRouterProviders.tsx @@ -28,16 +28,16 @@ export const AppRouterProviders = () => { return ( - - - - - - - - - - + + + + + + + + + + @@ -50,15 +50,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..2a68a374e070 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,32 @@ import { useRecoilValue } from 'recoil'; -import { isClientConfigLoadedState } from '@/client-config/states/isClientConfigLoadedState'; +import { clientConfigApiStatusState } from '@/client-config/states/clientConfigApiStatusState'; +import { GenericErrorFallback } from '@/error-handler/components/GenericErrorFallback'; +import styled from '@emotion/styled'; + +const StyledContainer = styled.div` + display: flex; + height: 100dvh; + width: 100%; +`; export const ClientConfigProvider: React.FC = ({ children, }) => { - const isClientConfigLoaded = useRecoilValue(isClientConfigLoadedState); + const { isLoaded, isErrored } = useRecoilValue(clientConfigApiStatusState); + + if (isLoaded && isErrored) { + return ( + + {}} + title="Unable to Reach Back-end" + shouldShowRefetch={false} + /> + + ); + } - return isClientConfigLoaded ? <>{children} : <>; + return isLoaded ? <>{children} : null; }; 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..e8223971598c 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,62 @@ 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, + })); + return; + } + + if (!isDefined(data?.clientConfig)) { + return; } + + setClientConfigApiStatus((currentStatus) => ({ + ...currentStatus, + isErrored: false, + })); + + 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 +103,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..6eda2b34bf11 --- /dev/null +++ b/packages/twenty-front/src/modules/client-config/states/clientConfigApiStatusState.ts @@ -0,0 +1,11 @@ +import { createState } from 'twenty-ui'; + +type ClientConfigApiStatus = { + isLoaded: boolean; + isErrored: boolean; +}; + +export const clientConfigApiStatusState = createState({ + key: 'clientConfigApiStatus', + defaultValue: { isLoaded: false, isErrored: false }, +}); 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/GenericErrorFallback.tsx b/packages/twenty-front/src/modules/error-handler/components/GenericErrorFallback.tsx index 8b3c647054bc..55b97e5eda89 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; + shouldShowRefetch?: boolean; +}; export const GenericErrorFallback = ({ error, resetErrorBoundary, + title = 'Something went wrong', + shouldShowRefetch = true, }: GenericErrorFallbackProps) => { const location = useLocation(); @@ -39,18 +44,20 @@ export const GenericErrorFallback = ({ - Server’s on a coffee break + {title} {error.message} - - + ); }, }, diff --git a/packages/twenty-front/src/modules/app/components/AppRouterProviders.tsx b/packages/twenty-front/src/modules/app/components/AppRouterProviders.tsx index 932d88d9674d..63e533c550ba 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 { AppThemeProviderEffect } 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 ( - - - - - - - - - - + + + + + + + + + + + @@ -52,13 +54,13 @@ export const AppRouterProviders = () => { - - - - - - - + + + + + + + ); }; 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 350f9e1bc9bc..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,43 +1,21 @@ import { useRecoilValue } from 'recoil'; import { clientConfigApiStatusState } from '@/client-config/states/clientConfigApiStatusState'; -import { GenericErrorFallback } from '@/error-handler/components/GenericErrorFallback'; -import styled from '@emotion/styled'; -import { MOBILE_VIEWPORT } from 'twenty-ui'; - -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)}; - } -`; +import { ClientConfigError } from '@/error-handler/components/ClientConfigError'; export const ClientConfigProvider: React.FC = ({ children, }) => { - const { isLoaded, isErrored } = useRecoilValue(clientConfigApiStatusState); + const { isLoaded, isErrored, error } = useRecoilValue( + clientConfigApiStatusState, + ); - if (isLoaded && isErrored) { - return ( - - {}} - title="Unable to Reach Back-end" - isInitialFetch - /> - - ); - } + // TODO: Implement a better loading strategy + if (!isLoaded) return null; - return isLoaded ? <>{children} : 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 e8223971598c..c0fcbda1f50b 100644 --- a/packages/twenty-front/src/modules/client-config/components/ClientConfigProviderEffect.tsx +++ b/packages/twenty-front/src/modules/client-config/components/ClientConfigProviderEffect.tsx @@ -52,6 +52,7 @@ export const ClientConfigProviderEffect = () => { setClientConfigApiStatus((currentStatus) => ({ ...currentStatus, isErrored: true, + error, })); return; } @@ -63,6 +64,7 @@ export const ClientConfigProviderEffect = () => { setClientConfigApiStatus((currentStatus) => ({ ...currentStatus, isErrored: false, + error: undefined, })); setAuthProviders({ diff --git a/packages/twenty-front/src/modules/client-config/states/clientConfigApiStatusState.ts b/packages/twenty-front/src/modules/client-config/states/clientConfigApiStatusState.ts index 6eda2b34bf11..08793188ff1a 100644 --- a/packages/twenty-front/src/modules/client-config/states/clientConfigApiStatusState.ts +++ b/packages/twenty-front/src/modules/client-config/states/clientConfigApiStatusState.ts @@ -3,9 +3,10 @@ import { createState } from 'twenty-ui'; type ClientConfigApiStatus = { isLoaded: boolean; isErrored: boolean; + error?: Error; }; export const clientConfigApiStatusState = createState({ key: 'clientConfigApiStatus', - defaultValue: { isLoaded: false, isErrored: false }, + defaultValue: { isLoaded: false, isErrored: false, error: undefined }, }); 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 609363140509..2d2e8efeb165 100644 --- a/packages/twenty-front/src/modules/error-handler/components/GenericErrorFallback.tsx +++ b/packages/twenty-front/src/modules/error-handler/components/GenericErrorFallback.tsx @@ -17,14 +17,14 @@ import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; type GenericErrorFallbackProps = FallbackProps & { title?: string; - isInitialFetch?: boolean; + hidePageHeader?: boolean; }; export const GenericErrorFallback = ({ error, resetErrorBoundary, - title = 'Something went wrong', - isInitialFetch = false, + title = 'Sorry, something went wrong', + hidePageHeader = false, }: GenericErrorFallbackProps) => { const location = useLocation(); @@ -38,8 +38,7 @@ export const GenericErrorFallback = ({ return ( - {/* no header for initial fetch failure */} - {!isInitialFetch && } + {!hidePageHeader && } @@ -52,15 +51,12 @@ export const GenericErrorFallback = ({ {error.message} - {/* no refetch button for initial fetch failure, hard refresh is required */} - {!isInitialFetch && ( - - - + + + + ); }, }, From c9f9250b57c529be2ff24374ea7397aa784dc840 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Malfait?= Date: Fri, 22 Nov 2024 09:39:06 +0100 Subject: [PATCH 7/7] More common syntax --- .../src/modules/ui/theme/components/BaseThemeProvider.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/twenty-front/src/modules/ui/theme/components/BaseThemeProvider.tsx b/packages/twenty-front/src/modules/ui/theme/components/BaseThemeProvider.tsx index d3ca62f518e1..99962eaf5bc0 100644 --- a/packages/twenty-front/src/modules/ui/theme/components/BaseThemeProvider.tsx +++ b/packages/twenty-front/src/modules/ui/theme/components/BaseThemeProvider.tsx @@ -14,7 +14,7 @@ type BaseThemeProviderProps = { }; export const ThemeSchemeContext = createContext<(theme: ColorScheme) => void>( - () => void 0, + () => {}, ); export const BaseThemeProvider = ({ children }: BaseThemeProviderProps) => {