Improve AppError boundaries (#11107)
## What This PR aims to make sure all application exceptions are captured through react-error-boundaries Once merged we will have: - Root Level: AppErrorBoundary at the highest level (full screen) ==> this one needs to be working in any case, not relying on Theme, was not working - Route Level: AppErrorBoundary in DefaultLayout (full screen) ==> this was missing and it seems that error are not propagated outside of the router, making errors triggered in CommandMenu or NavigationDrawer missing - Page Level: AppErrorBoundary in DefaultLayout write around the Page itself (lower than CommandMenu + NavigationDrawer) - Manually triggered: example in ClientConfigProvider ## Screenshots App level (ex throw in IconsProvider) <img width="1512" alt="image" src="https://github.com/user-attachments/assets/18a14815-a203-4edf-b931-43068c3436ec" /> Route level (ex throw in CommandMenu) <img width="1512" alt="image" src="https://github.com/user-attachments/assets/ca066627-14c7-438e-a432-f0999a1f3b84" /> Page level (ex throw in RecordTable) <img width="1512" alt="image" src="https://github.com/user-attachments/assets/ffeaa935-02af-4762-8859-7a0ccf8b77e1" /> Manually Triggered (clientConfig, ex when backend is not up) <img width="1512" alt="image" src="https://github.com/user-attachments/assets/062d6d84-097a-4ed9-b6ce-763b8c27c659" />
This commit is contained in:
@ -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 (
|
||||
<RecoilRoot>
|
||||
<RecoilURLSyncJSON location={{ part: 'queryParams' }}>
|
||||
<AppErrorBoundary>
|
||||
<AppErrorBoundary
|
||||
resetOnLocationChange={false}
|
||||
FallbackComponent={AppRootErrorFallback}
|
||||
>
|
||||
<I18nProvider i18n={i18n}>
|
||||
<RecoilDebugObserverEffect />
|
||||
<ApolloDevLogEffect />
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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<React.PropsWithChildren> = ({
|
||||
children,
|
||||
@ -14,7 +14,13 @@ export const ClientConfigProvider: React.FC<React.PropsWithChildren> = ({
|
||||
if (!isLoaded) return null;
|
||||
|
||||
return isErrored && error instanceof Error ? (
|
||||
<ClientConfigError error={error} />
|
||||
<AppFullScreenErrorFallback
|
||||
error={error}
|
||||
resetErrorBoundary={() => {
|
||||
window.location.reload();
|
||||
}}
|
||||
title="Unable to Reach Back-end"
|
||||
/>
|
||||
) : (
|
||||
children
|
||||
);
|
||||
|
||||
@ -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<FallbackProps>;
|
||||
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 (
|
||||
<ErrorBoundary
|
||||
FallbackComponent={GenericErrorFallback}
|
||||
FallbackComponent={({ error, resetErrorBoundary }) => (
|
||||
<>
|
||||
{resetOnLocationChange && (
|
||||
<AppErrorBoundaryEffect resetErrorBoundary={resetErrorBoundary} />
|
||||
)}
|
||||
<FallbackComponent
|
||||
error={error}
|
||||
resetErrorBoundary={resetErrorBoundary}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
onError={handleError}
|
||||
onReset={handleReset}
|
||||
>
|
||||
|
||||
@ -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 (
|
||||
<StyledContainer>
|
||||
<PageBody>
|
||||
<AppErrorDisplay
|
||||
error={error}
|
||||
resetErrorBoundary={resetErrorBoundary}
|
||||
title={title}
|
||||
/>
|
||||
</PageBody>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
@ -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 (
|
||||
<PageContainer>
|
||||
<PageHeader />
|
||||
|
||||
<PageBody>
|
||||
<AppErrorDisplay
|
||||
error={error}
|
||||
resetErrorBoundary={resetErrorBoundary}
|
||||
title={title}
|
||||
/>
|
||||
</PageBody>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
@ -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 (
|
||||
<StyledContainer>
|
||||
<StyledPanel>
|
||||
<StyledEmptyContainer>
|
||||
<StyledImageContainer>
|
||||
<StyledBackgroundImage
|
||||
src="/images/placeholders/background/error_index_bg.png"
|
||||
alt="Background"
|
||||
/>
|
||||
<StyledInnerImage
|
||||
src="/images/placeholders/moving-image/error_index.png"
|
||||
alt="Inner"
|
||||
/>
|
||||
</StyledImageContainer>
|
||||
<StyledEmptyTextContainer>
|
||||
<StyledEmptyTitle>{title}</StyledEmptyTitle>
|
||||
<StyledEmptySubTitle>{error.message}</StyledEmptySubTitle>
|
||||
</StyledEmptyTextContainer>
|
||||
<StyledButton onClick={resetErrorBoundary}>
|
||||
<StyledIcon size={16} />
|
||||
Reload
|
||||
</StyledButton>
|
||||
</StyledEmptyContainer>
|
||||
</StyledPanel>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
@ -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 (
|
||||
<StyledContainer>
|
||||
<GenericErrorFallback
|
||||
error={error}
|
||||
resetErrorBoundary={handleReset}
|
||||
title="Unable to Reach Back-end"
|
||||
hidePageHeader
|
||||
/>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
@ -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 (
|
||||
<PageContainer>
|
||||
{!hidePageHeader && <PageHeader />}
|
||||
|
||||
<PageBody>
|
||||
<AnimatedPlaceholderEmptyContainer>
|
||||
<AnimatedPlaceholder type="errorIndex" />
|
||||
<AnimatedPlaceholderEmptyTextContainer>
|
||||
<AnimatedPlaceholderEmptyTitle>
|
||||
{title}
|
||||
</AnimatedPlaceholderEmptyTitle>
|
||||
<AnimatedPlaceholderEmptySubTitle>
|
||||
{error.message}
|
||||
</AnimatedPlaceholderEmptySubTitle>
|
||||
</AnimatedPlaceholderEmptyTextContainer>
|
||||
<Button
|
||||
Icon={IconRefresh}
|
||||
title="Reload"
|
||||
variant={'secondary'}
|
||||
onClick={resetErrorBoundary}
|
||||
/>
|
||||
</AnimatedPlaceholderEmptyContainer>
|
||||
</PageBody>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,22 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { FallbackProps } from 'react-error-boundary';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
|
||||
type AppErrorBoundaryEffectProps = Pick<FallbackProps, 'resetErrorBoundary'>;
|
||||
|
||||
export const AppErrorBoundaryEffect = ({
|
||||
resetErrorBoundary,
|
||||
}: AppErrorBoundaryEffectProps) => {
|
||||
const location = useLocation();
|
||||
|
||||
const [previousLocation] = useState(location);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isDeeplyEqual(previousLocation, location)) {
|
||||
resetErrorBoundary();
|
||||
}
|
||||
}, [previousLocation, location, resetErrorBoundary]);
|
||||
|
||||
return <></>;
|
||||
};
|
||||
@ -0,0 +1,34 @@
|
||||
import { AppErrorDisplayProps } from '@/error-handler/types/AppErrorDisplayProps';
|
||||
import {
|
||||
AnimatedPlaceholder,
|
||||
AnimatedPlaceholderEmptyContainer,
|
||||
AnimatedPlaceholderEmptySubTitle,
|
||||
AnimatedPlaceholderEmptyTextContainer,
|
||||
AnimatedPlaceholderEmptyTitle,
|
||||
Button,
|
||||
IconRefresh,
|
||||
} from 'twenty-ui';
|
||||
|
||||
export const AppErrorDisplay = ({
|
||||
error,
|
||||
resetErrorBoundary,
|
||||
title = 'Sorry, something went wrong',
|
||||
}: AppErrorDisplayProps) => {
|
||||
return (
|
||||
<AnimatedPlaceholderEmptyContainer>
|
||||
<AnimatedPlaceholder type="errorIndex" />
|
||||
<AnimatedPlaceholderEmptyTextContainer>
|
||||
<AnimatedPlaceholderEmptyTitle>{title}</AnimatedPlaceholderEmptyTitle>
|
||||
<AnimatedPlaceholderEmptySubTitle>
|
||||
{error.message}
|
||||
</AnimatedPlaceholderEmptySubTitle>
|
||||
</AnimatedPlaceholderEmptyTextContainer>
|
||||
<Button
|
||||
Icon={IconRefresh}
|
||||
title="Reload"
|
||||
variant={'secondary'}
|
||||
onClick={resetErrorBoundary}
|
||||
/>
|
||||
</AnimatedPlaceholderEmptyContainer>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,5 @@
|
||||
import { FallbackProps } from 'react-error-boundary';
|
||||
|
||||
export type AppErrorDisplayProps = FallbackProps & {
|
||||
title?: string;
|
||||
};
|
||||
@ -1,6 +1,8 @@
|
||||
import { AuthModal } from '@/auth/components/AuthModal';
|
||||
import { CommandMenuRouter } from '@/command-menu/components/CommandMenuRouter';
|
||||
import { AppErrorBoundary } from '@/error-handler/components/AppErrorBoundary';
|
||||
import { AppFullScreenErrorFallback } from '@/error-handler/components/AppFullScreenErrorFallback';
|
||||
import { AppPageErrorFallback } from '@/error-handler/components/AppPageErrorFallback';
|
||||
import { KeyboardShortcutMenu } from '@/keyboard-shortcut-menu/components/KeyboardShortcutMenu';
|
||||
import { AppNavigationDrawer } from '@/navigation/components/AppNavigationDrawer';
|
||||
import { MobileNavigationBar } from '@/navigation/components/MobileNavigationBar';
|
||||
@ -82,51 +84,52 @@ export const DefaultLayout = () => {
|
||||
`}
|
||||
/>
|
||||
<StyledLayout>
|
||||
{!showAuthModal && (
|
||||
<>
|
||||
<CommandMenuRouter />
|
||||
<KeyboardShortcutMenu />
|
||||
</>
|
||||
)}
|
||||
|
||||
<StyledPageContainer
|
||||
animate={{
|
||||
marginLeft:
|
||||
isSettingsPage && !isMobile && !useShowFullScreen
|
||||
? (windowsWidth -
|
||||
(OBJECT_SETTINGS_WIDTH +
|
||||
NAV_DRAWER_WIDTHS.menu.desktop.expanded +
|
||||
64)) /
|
||||
2
|
||||
: 0,
|
||||
}}
|
||||
transition={{ duration: theme.animation.duration.normal }}
|
||||
>
|
||||
{showAuthModal ? (
|
||||
<StyledAppNavigationDrawerMock />
|
||||
) : useShowFullScreen ? null : (
|
||||
<StyledAppNavigationDrawer />
|
||||
)}
|
||||
{showAuthModal ? (
|
||||
<>
|
||||
<SignInBackgroundMockPage />
|
||||
<AnimatePresence mode="wait">
|
||||
<LayoutGroup>
|
||||
<AuthModal>
|
||||
<Outlet />
|
||||
</AuthModal>
|
||||
</LayoutGroup>
|
||||
</AnimatePresence>
|
||||
</>
|
||||
) : (
|
||||
<StyledMainContainer>
|
||||
<AppErrorBoundary>
|
||||
<Outlet />
|
||||
</AppErrorBoundary>
|
||||
</StyledMainContainer>
|
||||
)}
|
||||
</StyledPageContainer>
|
||||
{isMobile && <MobileNavigationBar />}
|
||||
<AppErrorBoundary FallbackComponent={AppFullScreenErrorFallback}>
|
||||
<StyledPageContainer
|
||||
animate={{
|
||||
marginLeft:
|
||||
isSettingsPage && !isMobile && !useShowFullScreen
|
||||
? (windowsWidth -
|
||||
(OBJECT_SETTINGS_WIDTH +
|
||||
NAV_DRAWER_WIDTHS.menu.desktop.expanded +
|
||||
64)) /
|
||||
2
|
||||
: 0,
|
||||
}}
|
||||
transition={{ duration: theme.animation.duration.normal }}
|
||||
>
|
||||
{!showAuthModal && (
|
||||
<>
|
||||
<CommandMenuRouter />
|
||||
<KeyboardShortcutMenu />
|
||||
</>
|
||||
)}
|
||||
{showAuthModal ? (
|
||||
<StyledAppNavigationDrawerMock />
|
||||
) : useShowFullScreen ? null : (
|
||||
<StyledAppNavigationDrawer />
|
||||
)}
|
||||
{showAuthModal ? (
|
||||
<>
|
||||
<SignInBackgroundMockPage />
|
||||
<AnimatePresence mode="wait">
|
||||
<LayoutGroup>
|
||||
<AuthModal>
|
||||
<Outlet />
|
||||
</AuthModal>
|
||||
</LayoutGroup>
|
||||
</AnimatePresence>
|
||||
</>
|
||||
) : (
|
||||
<StyledMainContainer>
|
||||
<AppErrorBoundary FallbackComponent={AppPageErrorFallback}>
|
||||
<Outlet />
|
||||
</AppErrorBoundary>
|
||||
</StyledMainContainer>
|
||||
)}
|
||||
</StyledPageContainer>
|
||||
{isMobile && <MobileNavigationBar />}
|
||||
</AppErrorBoundary>
|
||||
</StyledLayout>
|
||||
</>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user