mirror of
https://github.com/Unleash/unleash.git
synced 2025-07-26 13:48:33 +02:00
feat: frontend app error boundary (#2144)
* feat: frontend app error boundary * fix: freeze added dependency * update yarn lock
This commit is contained in:
parent
5535a1769d
commit
879e1358ef
@ -61,6 +61,7 @@
|
|||||||
"date-fns": "2.29.3",
|
"date-fns": "2.29.3",
|
||||||
"debounce": "1.2.1",
|
"debounce": "1.2.1",
|
||||||
"deep-diff": "1.0.2",
|
"deep-diff": "1.0.2",
|
||||||
|
"dequal": "2.0.3",
|
||||||
"eslint": "8.23.0",
|
"eslint": "8.23.0",
|
||||||
"eslint-config-react-app": "7.0.1",
|
"eslint-config-react-app": "7.0.1",
|
||||||
"fast-json-patch": "3.1.1",
|
"fast-json-patch": "3.1.1",
|
||||||
@ -76,6 +77,7 @@
|
|||||||
"react": "17.0.2",
|
"react": "17.0.2",
|
||||||
"react-chartjs-2": "4.3.1",
|
"react-chartjs-2": "4.3.1",
|
||||||
"react-dom": "17.0.2",
|
"react-dom": "17.0.2",
|
||||||
|
"react-error-boundary": "3.1.4",
|
||||||
"react-hooks-global-state": "2.0.0",
|
"react-hooks-global-state": "2.0.0",
|
||||||
"react-router-dom": "6.3.0",
|
"react-router-dom": "6.3.0",
|
||||||
"react-table": "7.8.0",
|
"react-table": "7.8.0",
|
||||||
@ -128,8 +130,5 @@
|
|||||||
"ignorePatterns": [
|
"ignorePatterns": [
|
||||||
"cypress"
|
"cypress"
|
||||||
]
|
]
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"dequal": "2.0.3"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
|
import { Suspense } from 'react';
|
||||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
import { Navigate, Route, Routes } from 'react-router-dom';
|
||||||
|
import { ErrorBoundary } from 'react-error-boundary';
|
||||||
|
import { Error } from 'component/layout/Error/Error';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { FeedbackNPS } from 'component/feedback/FeedbackNPS/FeedbackNPS';
|
import { FeedbackNPS } from 'component/feedback/FeedbackNPS/FeedbackNPS';
|
||||||
import { LayoutPicker } from 'component/layout/LayoutPicker/LayoutPicker';
|
import { LayoutPicker } from 'component/layout/LayoutPicker/LayoutPicker';
|
||||||
@ -14,7 +17,6 @@ import { SplashPageRedirect } from 'component/splash/SplashPageRedirect/SplashPa
|
|||||||
import { useStyles } from './App.styles';
|
import { useStyles } from './App.styles';
|
||||||
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
||||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
import { Suspense } from 'react';
|
|
||||||
|
|
||||||
export const App = () => {
|
export const App = () => {
|
||||||
const { classes: styles } = useStyles();
|
const { classes: styles } = useStyles();
|
||||||
@ -29,40 +31,50 @@ export const App = () => {
|
|||||||
: routes;
|
: routes;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SWRProvider>
|
<ErrorBoundary FallbackComponent={Error}>
|
||||||
<Suspense fallback={<Loader />}>
|
<SWRProvider>
|
||||||
<ConditionallyRender
|
<Suspense fallback={<Loader />}>
|
||||||
condition={!hasFetchedAuth}
|
<ConditionallyRender
|
||||||
show={<Loader />}
|
condition={!hasFetchedAuth}
|
||||||
elseShow={
|
show={<Loader />}
|
||||||
<div className={styles.container}>
|
elseShow={
|
||||||
<ToastRenderer />
|
<div className={styles.container}>
|
||||||
<LayoutPicker>
|
<ToastRenderer />
|
||||||
<Routes>
|
<LayoutPicker>
|
||||||
{availableRoutes.map(route => (
|
<Routes>
|
||||||
|
{availableRoutes.map(route => (
|
||||||
|
<Route
|
||||||
|
key={route.path}
|
||||||
|
path={route.path}
|
||||||
|
element={
|
||||||
|
<ProtectedRoute
|
||||||
|
route={route}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
<Route
|
<Route
|
||||||
key={route.path}
|
path="/"
|
||||||
path={route.path}
|
|
||||||
element={
|
element={
|
||||||
<ProtectedRoute route={route} />
|
<Navigate
|
||||||
|
to="/features"
|
||||||
|
replace
|
||||||
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
))}
|
<Route
|
||||||
<Route
|
path="*"
|
||||||
path="/"
|
element={<NotFound />}
|
||||||
element={
|
/>
|
||||||
<Navigate to="/features" replace />
|
</Routes>
|
||||||
}
|
<FeedbackNPS openUrl="http://feedback.unleash.run" />
|
||||||
/>
|
<SplashPageRedirect />
|
||||||
<Route path="*" element={<NotFound />} />
|
</LayoutPicker>
|
||||||
</Routes>
|
</div>
|
||||||
<FeedbackNPS openUrl="http://feedback.unleash.run" />
|
}
|
||||||
<SplashPageRedirect />
|
/>
|
||||||
</LayoutPicker>
|
</Suspense>
|
||||||
</div>
|
</SWRProvider>
|
||||||
}
|
</ErrorBoundary>
|
||||||
/>
|
|
||||||
</Suspense>
|
|
||||||
</SWRProvider>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
44
frontend/src/component/layout/Error/Error.tsx
Normal file
44
frontend/src/component/layout/Error/Error.tsx
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { VFC } from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { Box, Typography } from '@mui/material';
|
||||||
|
import { Dialogue } from 'component/common/Dialogue/Dialogue';
|
||||||
|
import { GO_BACK } from 'constants/navigate';
|
||||||
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
|
|
||||||
|
interface IErrorProps {
|
||||||
|
error: Error;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Error: VFC<IErrorProps> = ({ error }) => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
return (
|
||||||
|
<Box sx={{ backgroundColor: 'neutral.light', height: '100%', p: 4 }}>
|
||||||
|
<Dialogue
|
||||||
|
open={true}
|
||||||
|
title="Something went wrong"
|
||||||
|
primaryButtonText="Go back"
|
||||||
|
onClick={() => {
|
||||||
|
navigate('/');
|
||||||
|
window?.location?.reload();
|
||||||
|
}}
|
||||||
|
secondaryButtonText="Reload this page"
|
||||||
|
onClose={() => {
|
||||||
|
window?.location?.reload();
|
||||||
|
}}
|
||||||
|
maxWidth="xl"
|
||||||
|
>
|
||||||
|
<Box component="pre" sx={{ color: 'error.main' }}>
|
||||||
|
{error.message}
|
||||||
|
</Box>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={Boolean(error.stack)}
|
||||||
|
show={
|
||||||
|
<Box component="pre" sx={{ color: 'error.main' }}>
|
||||||
|
{error.stack}
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Dialogue>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
@ -22,7 +22,7 @@ export const useInviteTokens = (options: SWRConfiguration = {}) => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
data: data
|
data: data
|
||||||
? { tokens: data.tokens.filter(token => token.enabled) }
|
? { tokens: data.tokens?.filter(token => token.enabled) }
|
||||||
: undefined,
|
: undefined,
|
||||||
error,
|
error,
|
||||||
loading,
|
loading,
|
||||||
|
@ -5920,7 +5920,7 @@ react-dom@17.0.2:
|
|||||||
object-assign "^4.1.1"
|
object-assign "^4.1.1"
|
||||||
scheduler "^0.20.2"
|
scheduler "^0.20.2"
|
||||||
|
|
||||||
react-error-boundary@^3.1.0:
|
react-error-boundary@3.1.4, react-error-boundary@^3.1.0:
|
||||||
version "3.1.4"
|
version "3.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-3.1.4.tgz#255db92b23197108757a888b01e5b729919abde0"
|
resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-3.1.4.tgz#255db92b23197108757a888b01e5b729919abde0"
|
||||||
integrity sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==
|
integrity sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==
|
||||||
|
Loading…
Reference in New Issue
Block a user