diff --git a/packages/twenty-front/src/modules/app/components/App.tsx b/packages/twenty-front/src/modules/app/components/App.tsx index 77064d34a..a753f5b5b 100644 --- a/packages/twenty-front/src/modules/app/components/App.tsx +++ b/packages/twenty-front/src/modules/app/components/App.tsx @@ -2,6 +2,7 @@ import { AppRouter } from '@/app/components/AppRouter'; import { ApolloDevLogEffect } from '@/debug/components/ApolloDevLogEffect'; import { RecoilDebugObserverEffect } from '@/debug/components/RecoilDebugObserver'; import { AppErrorBoundary } from '@/error-handler/components/AppErrorBoundary'; +import { AppRootErrorFallback } from '@/error-handler/components/AppRootErrorFallback'; import { ExceptionHandlerProvider } from '@/error-handler/components/ExceptionHandlerProvider'; import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope'; import { i18n } from '@lingui/core'; @@ -18,7 +19,10 @@ export const App = () => { return ( - + diff --git a/packages/twenty-front/src/modules/app/components/AppRouterProviders.tsx b/packages/twenty-front/src/modules/app/components/AppRouterProviders.tsx index 633af6248..16eb64bde 100644 --- a/packages/twenty-front/src/modules/app/components/AppRouterProviders.tsx +++ b/packages/twenty-front/src/modules/app/components/AppRouterProviders.tsx @@ -1,8 +1,8 @@ -import { CaptchaProvider } from '@/captcha/components/CaptchaProvider'; import { ApolloProvider } from '@/apollo/components/ApolloProvider'; import { GotoHotkeysEffectsProvider } from '@/app/effect-components/GotoHotkeysEffectsProvider'; import { PageChangeEffect } from '@/app/effect-components/PageChangeEffect'; import { AuthProvider } from '@/auth/components/AuthProvider'; +import { CaptchaProvider } from '@/captcha/components/CaptchaProvider'; import { ChromeExtensionSidecarEffect } from '@/chrome-extension-sidecar/components/ChromeExtensionSidecarEffect'; import { ChromeExtensionSidecarProvider } from '@/chrome-extension-sidecar/components/ChromeExtensionSidecarProvider'; import { ClientConfigProvider } from '@/client-config/components/ClientConfigProvider'; 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 d363c191e..f88b9652f 100644 --- a/packages/twenty-front/src/modules/client-config/components/ClientConfigProvider.tsx +++ b/packages/twenty-front/src/modules/client-config/components/ClientConfigProvider.tsx @@ -1,7 +1,7 @@ import { useRecoilValue } from 'recoil'; import { clientConfigApiStatusState } from '@/client-config/states/clientConfigApiStatusState'; -import { ClientConfigError } from '@/error-handler/components/ClientConfigError'; +import { AppFullScreenErrorFallback } from '@/error-handler/components/AppFullScreenErrorFallback'; export const ClientConfigProvider: React.FC = ({ children, @@ -14,7 +14,13 @@ export const ClientConfigProvider: React.FC = ({ if (!isLoaded) return null; return isErrored && error instanceof Error ? ( - + { + window.location.reload(); + }} + title="Unable to Reach Back-end" + /> ) : ( children ); 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 a00f61fa6..8317cfbbb 100644 --- a/packages/twenty-front/src/modules/error-handler/components/AppErrorBoundary.tsx +++ b/packages/twenty-front/src/modules/error-handler/components/AppErrorBoundary.tsx @@ -1,25 +1,43 @@ +import { AppErrorBoundaryEffect } from '@/error-handler/components/internal/AppErrorBoundaryEffect'; import * as Sentry from '@sentry/react'; import { ErrorInfo, ReactNode } from 'react'; -import { ErrorBoundary } from 'react-error-boundary'; +import { ErrorBoundary, FallbackProps } from 'react-error-boundary'; -import { GenericErrorFallback } from '@/error-handler/components/GenericErrorFallback'; +type AppErrorBoundaryProps = { + children: ReactNode; + FallbackComponent: React.ComponentType; + resetOnLocationChange?: boolean; +}; -export const AppErrorBoundary = ({ children }: { children: ReactNode }) => { - const handleError = (_error: Error, _info: ErrorInfo) => { - Sentry.captureException(_error, (scope) => { - scope.setExtras({ _info }); +export const AppErrorBoundary = ({ + children, + FallbackComponent, + resetOnLocationChange = true, +}: AppErrorBoundaryProps) => { + const handleError = (error: Error, info: ErrorInfo) => { + Sentry.captureException(error, (scope) => { + scope.setExtras({ info }); return scope; }); }; - // TODO: Implement a better reset strategy, hard reload for now const handleReset = () => { window.location.reload(); }; return ( ( + <> + {resetOnLocationChange && ( + + )} + + + )} onError={handleError} onReset={handleReset} > diff --git a/packages/twenty-front/src/modules/error-handler/components/AppFullScreenErrorFallback.tsx b/packages/twenty-front/src/modules/error-handler/components/AppFullScreenErrorFallback.tsx new file mode 100644 index 000000000..089027f49 --- /dev/null +++ b/packages/twenty-front/src/modules/error-handler/components/AppFullScreenErrorFallback.tsx @@ -0,0 +1,34 @@ +import { AppErrorDisplay } from '@/error-handler/components/internal/AppErrorDisplay'; +import { AppErrorDisplayProps } from '@/error-handler/types/AppErrorDisplayProps'; +import { PageBody } from '@/ui/layout/page/components/PageBody'; +import styled from '@emotion/styled'; + +type AppFullScreenErrorFallbackProps = AppErrorDisplayProps; + +const StyledContainer = styled.div` + background: ${({ theme }) => theme.background.noisy}; + box-sizing: border-box; + display: flex; + height: 100vh; + width: 100vw; + padding-top: ${({ theme }) => theme.spacing(3)}; + padding-left: ${({ theme }) => theme.spacing(3)}; +`; + +export const AppFullScreenErrorFallback = ({ + error, + resetErrorBoundary, + title = 'Sorry, something went wrong', +}: AppFullScreenErrorFallbackProps) => { + return ( + + + + + + ); +}; diff --git a/packages/twenty-front/src/modules/error-handler/components/AppPageErrorFallback.tsx b/packages/twenty-front/src/modules/error-handler/components/AppPageErrorFallback.tsx new file mode 100644 index 000000000..ee0031000 --- /dev/null +++ b/packages/twenty-front/src/modules/error-handler/components/AppPageErrorFallback.tsx @@ -0,0 +1,27 @@ +import { AppErrorDisplay } from '@/error-handler/components/internal/AppErrorDisplay'; +import { AppErrorDisplayProps } from '@/error-handler/types/AppErrorDisplayProps'; +import { PageBody } from '@/ui/layout/page/components/PageBody'; +import { PageContainer } from '@/ui/layout/page/components/PageContainer'; +import { PageHeader } from '@/ui/layout/page/components/PageHeader'; + +type AppPageErrorFallbackProps = AppErrorDisplayProps; + +export const AppPageErrorFallback = ({ + error, + resetErrorBoundary, + title = 'Sorry, something went wrong', +}: AppPageErrorFallbackProps) => { + return ( + + + + + + + + ); +}; diff --git a/packages/twenty-front/src/modules/error-handler/components/AppRootErrorFallback.tsx b/packages/twenty-front/src/modules/error-handler/components/AppRootErrorFallback.tsx new file mode 100644 index 000000000..3479fdb3e --- /dev/null +++ b/packages/twenty-front/src/modules/error-handler/components/AppRootErrorFallback.tsx @@ -0,0 +1,131 @@ +import { AppErrorDisplayProps } from '@/error-handler/types/AppErrorDisplayProps'; +import styled from '@emotion/styled'; +import LightNoise from '@ui/theme/assets/light-noise.png'; +import { motion } from 'framer-motion'; +import { GRAY_SCALE, IconReload } from 'twenty-ui'; + +type AppRootErrorFallbackProps = AppErrorDisplayProps; + +const StyledContainer = styled.div` + background: url(${LightNoise.toString()}); + box-sizing: border-box; + display: flex; + height: 100vh; + width: 100vw; + padding: 12px; +`; + +const StyledPanel = styled.div` + background: ${GRAY_SCALE.gray0}; + border: 1px solid ${GRAY_SCALE.gray20}; + border-radius: 8px; + height: 100%; + overflow-x: auto; + overflow-y: hidden; + width: 100%; +`; + +const StyledEmptyContainer = styled(motion.div)` + align-items: center; + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + gap: 24px; + justify-content: center; + text-align: center; +`; + +const StyledImageContainer = styled.div` + display: flex; + align-items: center; + justify-content: center; + position: relative; +`; + +const StyledBackgroundImage = styled.img` + max-height: 160px; + max-width: 160px; +`; + +const StyledInnerImage = styled.img` + max-height: 130px; + position: absolute; + max-width: 130px; +`; + +const StyledEmptyTextContainer = styled.div` + align-items: center; + display: flex; + flex-direction: column; + gap: 8px; + justify-content: center; + text-align: center; + width: 100%; +`; + +const StyledEmptyTitle = styled.div` + color: ${GRAY_SCALE.gray60}; + font-size: 1.23rem; + font-weight: 600; +`; + +const StyledEmptySubTitle = styled.div` + color: ${GRAY_SCALE.gray50}; + font-size: 0.92rem; + font-weight: 400; + line-height: 1.5; + max-height: 2.8em; + overflow: hidden; + width: 50%; +`; + +const StyledButton = styled.button` + align-items: center; + background: ${GRAY_SCALE.gray0}; + border: 1px solid ${GRAY_SCALE.gray20}; + color: ${GRAY_SCALE.gray60}; + border-radius: 8px; + cursor: pointer; + display: flex; + padding: 8px 16px; + padding: 8px; +`; + +const StyledIcon = styled(IconReload)` + color: ${GRAY_SCALE.gray60}; + margin-right: 8px; +`; + +export const AppRootErrorFallback = ({ + error, + resetErrorBoundary, + title = 'Sorry, something went wrong', +}: AppRootErrorFallbackProps) => { + return ( + + + + + + + + + {title} + {error.message} + + + + Reload + + + + + ); +}; diff --git a/packages/twenty-front/src/modules/error-handler/components/ClientConfigError.tsx b/packages/twenty-front/src/modules/error-handler/components/ClientConfigError.tsx deleted file mode 100644 index 47141a532..000000000 --- a/packages/twenty-front/src/modules/error-handler/components/ClientConfigError.tsx +++ /dev/null @@ -1,41 +0,0 @@ -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 deleted file mode 100644 index 2d2e8efeb..000000000 --- a/packages/twenty-front/src/modules/error-handler/components/GenericErrorFallback.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { PageBody } from '@/ui/layout/page/components/PageBody'; -import { PageContainer } from '@/ui/layout/page/components/PageContainer'; -import { PageHeader } from '@/ui/layout/page/components/PageHeader'; -import { useEffect, useState } from 'react'; -import { FallbackProps } from 'react-error-boundary'; -import { useLocation } from 'react-router-dom'; -import { - AnimatedPlaceholder, - AnimatedPlaceholderEmptyContainer, - AnimatedPlaceholderEmptySubTitle, - AnimatedPlaceholderEmptyTextContainer, - AnimatedPlaceholderEmptyTitle, - Button, - IconRefresh, -} from 'twenty-ui'; -import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; - -type GenericErrorFallbackProps = FallbackProps & { - title?: string; - hidePageHeader?: boolean; -}; - -export const GenericErrorFallback = ({ - error, - resetErrorBoundary, - title = 'Sorry, something went wrong', - hidePageHeader = false, -}: GenericErrorFallbackProps) => { - const location = useLocation(); - - const [previousLocation] = useState(location); - - useEffect(() => { - if (!isDeeplyEqual(previousLocation, location)) { - resetErrorBoundary(); - } - }, [previousLocation, location, resetErrorBoundary]); - - return ( - - {!hidePageHeader && } - - - - - - - {title} - - - {error.message} - - -