Skip to content

Commit 0f5af05

Browse files
committed
Refactor error screen
1 parent 77d9937 commit 0f5af05

File tree

2 files changed

+96
-112
lines changed

2 files changed

+96
-112
lines changed

site/src/components/Margins/Margins.tsx

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { makeStyles } from "@material-ui/core/styles"
22
import { FC } from "react"
3+
import { combineClasses } from "utils/combineClasses"
34
import {
45
containerWidth,
56
containerWidthMedium,
@@ -24,14 +25,15 @@ const useStyles = makeStyles(() => ({
2425
},
2526
}))
2627

27-
interface MarginsProps {
28-
size?: Size
29-
}
30-
31-
export const Margins: FC<React.PropsWithChildren<MarginsProps>> = ({
32-
children,
28+
export const Margins: FC<JSX.IntrinsicElements["div"] & { size: Size }> = ({
3329
size = "regular",
30+
...divProps
3431
}) => {
3532
const styles = useStyles({ maxWidth: widthBySize[size] })
36-
return <div className={styles.margins}>{children}</div>
33+
return (
34+
<div
35+
{...divProps}
36+
className={combineClasses([styles.margins, divProps.className])}
37+
/>
38+
)
3739
}
Lines changed: 87 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -1,125 +1,107 @@
1-
import Box from "@material-ui/core/Box"
1+
import Button from "@material-ui/core/Button"
22
import Link from "@material-ui/core/Link"
33
import { makeStyles } from "@material-ui/core/styles"
4-
import ErrorOutlineIcon from "@material-ui/icons/ErrorOutline"
5-
import { useEffect, useReducer, FC } from "react"
6-
import { mapStackTrace } from "sourcemapped-stacktrace"
4+
import RefreshOutlined from "@material-ui/icons/RefreshOutlined"
5+
import { CoderIcon } from "components/Icons/CoderIcon"
6+
import { FullScreenLoader } from "components/Loader/FullScreenLoader"
7+
import { Stack } from "components/Stack/Stack"
8+
import { FC, useEffect, useState } from "react"
9+
import { Helmet } from "react-helmet-async"
710
import { Margins } from "../Margins/Margins"
8-
import { Section } from "../Section/Section"
9-
import { Typography } from "../Typography/Typography"
10-
import {
11-
createFormattedStackTrace,
12-
reducer,
13-
RuntimeErrorReport,
14-
stackTraceAvailable,
15-
stackTraceUnavailable,
16-
} from "./RuntimeErrorReport"
1711

18-
export const Language = {
19-
title: "Coder encountered an error",
20-
body: "Please copy the crash log using the button below and",
21-
link: "send it to us.",
22-
}
23-
24-
export interface RuntimeErrorStateProps {
25-
error: Error
26-
}
27-
28-
/**
29-
* A title for our error boundary UI
30-
*/
31-
const ErrorStateTitle = () => {
32-
const styles = useStyles()
33-
return (
34-
<Box className={styles.title} display="flex" alignItems="center">
35-
<ErrorOutlineIcon />
36-
<span>{Language.title}</span>
37-
</Box>
38-
)
39-
}
40-
41-
/**
42-
* A description for our error boundary UI
43-
*/
44-
const ErrorStateDescription = ({ emailBody }: { emailBody?: string }) => {
45-
const styles = useStyles()
46-
return (
47-
<Typography variant="body2" color="textSecondary">
48-
{Language.body}&nbsp;
49-
<Link
50-
href={`mailto:support@coder.com?subject=Error Report from Coder&body=${
51-
emailBody && emailBody.replace(/\r\n|\r|\n/g, "%0D%0A") // preserving line breaks
52-
}`}
53-
className={styles.link}
54-
>
55-
{Language.link}
56-
</Link>
57-
</Typography>
58-
)
59-
}
12+
const fetchDynamicallyImportedModuleError =
13+
"Failed to fetch dynamically imported module" as const
6014

61-
/**
62-
* An error UI that is displayed when our error boundary (ErrorBoundary.tsx) is triggered
63-
*/
64-
export const RuntimeErrorState: FC<RuntimeErrorStateProps> = ({ error }) => {
15+
export const RuntimeErrorState: FC<{ error: Error }> = ({ error }) => {
6516
const styles = useStyles()
66-
const [reportState, dispatch] = useReducer(reducer, {
67-
error,
68-
mappedStack: null,
69-
})
17+
const [shouldDisplayMessage, setShouldDisplayMessage] = useState(false)
7018

19+
// We use an effect to show a loading state if the page is trying to reload
7120
useEffect(() => {
72-
try {
73-
mapStackTrace(error.stack, (mappedStack) =>
74-
dispatch(stackTraceAvailable(mappedStack)),
75-
)
76-
} catch {
77-
dispatch(stackTraceUnavailable)
21+
const isImportError = error.message.includes(
22+
fetchDynamicallyImportedModuleError,
23+
)
24+
const isRetried = window.location.search.includes("retries=1")
25+
26+
if (isImportError && !isRetried) {
27+
const url = new URL(location.href)
28+
// Add a retry to avoid loops
29+
url.searchParams.set("retries", "1")
30+
location.assign(url.search)
31+
return
7832
}
79-
}, [error])
33+
34+
setShouldDisplayMessage(true)
35+
}, [error.message])
8036

8137
return (
82-
<Box display="flex" flexDirection="column">
83-
<Margins>
84-
<Section
85-
className={styles.reportContainer}
86-
title={<ErrorStateTitle />}
87-
description={
88-
<ErrorStateDescription
89-
emailBody={createFormattedStackTrace(
90-
reportState.error,
91-
reportState.mappedStack,
92-
).join("\r\n")}
93-
/>
94-
}
95-
>
96-
<RuntimeErrorReport
97-
error={reportState.error}
98-
mappedStack={reportState.mappedStack}
99-
/>
100-
</Section>
101-
</Margins>
102-
</Box>
38+
<>
39+
<Helmet>
40+
<title>Something went wrong...</title>
41+
</Helmet>
42+
{shouldDisplayMessage ? (
43+
<Margins size="small" className={styles.root}>
44+
<div>
45+
<CoderIcon className={styles.logo} />
46+
<h1 className={styles.title}>Something went wrong...</h1>
47+
<p className={styles.text}>
48+
Please try reloading the page, if that doesn&lsquo;t work, you can
49+
ask for help in the{" "}
50+
<Link href="https://discord.gg/coder">
51+
Coder Discord community
52+
</Link>{" "}
53+
or{" "}
54+
<Link href="https://github.com/coder/coder/issues/new">
55+
open an issue
56+
</Link>
57+
.
58+
</p>
59+
<Stack direction="row" justifyContent="center">
60+
<Button
61+
startIcon={<RefreshOutlined />}
62+
onClick={() => {
63+
window.location.reload()
64+
}}
65+
>
66+
Reload page
67+
</Button>
68+
<Button component="a" href="/" variant="outlined">
69+
Go to dashboard
70+
</Button>
71+
</Stack>
72+
</div>
73+
</Margins>
74+
) : (
75+
<FullScreenLoader />
76+
)}
77+
</>
10378
)
10479
}
10580

10681
const useStyles = makeStyles((theme) => ({
107-
title: {
108-
"& span": {
109-
paddingLeft: theme.spacing(1),
110-
},
82+
root: {
83+
paddingTop: theme.spacing(4),
84+
paddingBottom: theme.spacing(4),
85+
textAlign: "center",
86+
display: "flex",
87+
alignItems: "center",
88+
justifyContent: "center",
89+
minHeight: "100vh",
90+
},
11191

112-
"& .MuiSvgIcon-root": {
113-
color: theme.palette.error.main,
114-
},
92+
logo: {
93+
fontSize: theme.spacing(8),
11594
},
116-
link: {
117-
textDecoration: "none",
118-
color: theme.palette.primary.main,
95+
96+
title: {
97+
fontSize: theme.spacing(4),
98+
fontWeight: 400,
11999
},
120-
reportContainer: {
121-
display: "flex",
122-
justifyContent: "center",
123-
marginTop: theme.spacing(5),
100+
101+
text: {
102+
fontSize: 16,
103+
color: theme.palette.text.secondary,
104+
lineHeight: "160%",
105+
marginBottom: theme.spacing(4),
124106
},
125107
}))

0 commit comments

Comments
 (0)