1import { css } from '@emotion/react'; 2import { Button, theme, typography } from '@expo/styleguide'; 3import { spacing } from '@expo/styleguide-base'; 4import * as Sentry from '@sentry/browser'; 5import { useEffect, useState } from 'react'; 6 7import { getRedirectPath } from '~/common/error-utilities'; 8import Head from '~/components/Head'; 9import { NotFoundImage, RedirectImage, ServerErrorImage } from '~/ui/components/ErrorPage'; 10import { Layout } from '~/ui/components/Layout'; 11import { H1, P } from '~/ui/components/Text'; 12 13const REDIRECT_SUFFIX = '?redirected'; 14 15const renderRedirect = () => ( 16 // note(simek): "redirect-link" ID is needed for test-links script 17 <> 18 <Head title="Redirecting" /> 19 <RedirectImage /> 20 <H1 css={styles.header}>Redirecting</H1> 21 <P css={styles.description} id="redirect-link"> 22 Just a moment… 23 </P> 24 </> 25); 26 27const renderNotFoundAfterRedirect = () => ( 28 <> 29 <Head title="Not Found" /> 30 <ServerErrorImage /> 31 <H1 css={styles.header}>404: Not Found</H1> 32 <P css={styles.description} id="__redirect_failed"> 33 We took an educated guess and tried to direct you to the right page, but it seems that did not 34 work out! Maybe it doesn't exist anymore! 35 </P> 36 <Button theme="secondary" href="/"> 37 Return Home 38 </Button> 39 </> 40); 41 42const renderNotFound = () => ( 43 <> 44 <Head title="Not Found" /> 45 <NotFoundImage /> 46 <H1 css={styles.header}>404: Not Found</H1> 47 <P css={styles.description} id="__not_found"> 48 We couldn't find the page you were looking for. Check the URL to make sure it's correct and 49 try again. 50 </P> 51 <Button theme="secondary" href="/"> 52 Return Home 53 </Button> 54 </> 55); 56 57const Error = () => { 58 const [notFound, setNotFound] = useState<boolean>(false); 59 const [redirectFailed, setRedirectFailed] = useState<boolean>(false); 60 const [redirectPath, setRedirectPath] = useState<string | undefined>(undefined); 61 62 useEffect(() => { 63 if (typeof window === 'undefined') { 64 return; 65 } 66 67 const { pathname, search } = window.location; 68 69 if (search === REDIRECT_SUFFIX) { 70 Sentry.captureMessage(`Redirect failed`); 71 setRedirectFailed(true); 72 return; 73 } 74 75 const newRedirectPath = getRedirectPath(pathname); 76 77 if (newRedirectPath !== pathname) { 78 setRedirectPath(newRedirectPath); 79 return; 80 } 81 82 // We are confident now that we can render a not found error 83 setNotFound(true); 84 Sentry.captureMessage(`Page not found (404)`, { 85 extra: { 86 '404': pathname, 87 }, 88 }); 89 }, []); 90 91 useEffect(() => { 92 if (redirectPath && typeof window !== 'undefined') { 93 setTimeout(() => (window.location.href = `${redirectPath}${REDIRECT_SUFFIX}`), 1200); 94 } 95 }, [redirectPath]); 96 97 const getContent = () => { 98 if (redirectPath) { 99 return renderRedirect(); 100 } else if (redirectFailed) { 101 return renderNotFoundAfterRedirect(); 102 } else if (notFound) { 103 return renderNotFound(); 104 } 105 return undefined; 106 }; 107 108 return ( 109 <Layout cssLayout={styles.layout} cssContent={styles.container}> 110 {getContent()} 111 </Layout> 112 ); 113}; 114 115export default Error; 116 117const styles = { 118 layout: css({ 119 backgroundColor: theme.background.subtle, 120 }), 121 container: css({ 122 display: 'flex', 123 alignItems: 'center', 124 justifyContent: 'center', 125 flexDirection: 'column', 126 }), 127 header: css({ 128 ...typography.fontSizes[31], 129 marginTop: spacing[8], 130 }), 131 description: css({ 132 textAlign: 'center', 133 maxWidth: 450, 134 marginTop: spacing[6], 135 marginBottom: spacing[8], 136 color: theme.text.secondary, 137 }), 138}; 139