Skip to content

Commit 1e6d3da

Browse files
committed
fixup! ♿️(frontend) redirect unmanaged 5xx to dedicated /500 page
1 parent 34dc46e commit 1e6d3da

7 files changed

Lines changed: 91 additions & 31 deletions

File tree

src/frontend/apps/e2e/__tests__/app-impress/doc-routing.spec.ts

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,18 +40,44 @@ test.describe('Doc Routing', () => {
4040
await expect(page).toHaveURL(/\/docs\/$/);
4141
});
4242

43-
test('checks 500 page', async ({ page }) => {
44-
await page.waitForTimeout(300);
43+
test('checks 500 refresh retries original document request', async ({
44+
page,
45+
browserName,
46+
}) => {
47+
const [docTitle] = await createDoc(page, 'doc-routing-500', browserName, 1);
48+
await verifyDocName(page, docTitle);
49+
50+
const docId = page.url().split('/docs/')[1]?.split('/')[0];
51+
let documentRequestCount = 0;
52+
53+
await page.route(/\**\/documents\/\**/, async (route) => {
54+
const request = route.request();
55+
if (
56+
request.method().includes('GET') &&
57+
docId &&
58+
request.url().includes(`/documents/${docId}/`)
59+
) {
60+
documentRequestCount += 1;
61+
await route.fulfill({
62+
status: 500,
63+
json: { detail: 'Internal Server Error' },
64+
});
65+
} else {
66+
await route.continue();
67+
}
68+
});
69+
70+
await page.reload();
4571

46-
await page.goto('/500');
72+
await expect(page).toHaveURL(/\/500\/?\?from=/, { timeout: 15000 });
4773

4874
const refreshButton = page.getByRole('button', { name: 'Refresh page' });
4975
await expect(refreshButton).toBeVisible();
76+
await refreshButton.click();
5077

51-
const homeLink = page.getByRole('link', { name: 'Home', exact: true });
52-
await expect(homeLink).toBeVisible();
53-
await homeLink.click();
54-
await expect(page).toHaveURL(/\/$/);
78+
await expect
79+
.poll(() => documentRequestCount, { timeout: 10000 })
80+
.toBeGreaterThan(1);
5581
});
5682

5783
test('checks 404 on docs/[id] page', async ({ page }) => {

src/frontend/apps/impress/src/components/ErrorPage.tsx

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,34 @@ const StyledButton = styled(Button)`
1313
interface ErrorPageProps {
1414
image: StaticImageData;
1515
description: string;
16+
refreshTarget?: string;
1617
}
1718

18-
export const ErrorPage = ({ image, description }: ErrorPageProps) => {
19+
const getSafeRefreshUrl = (target?: string): string | undefined => {
20+
if (!target) {
21+
return undefined;
22+
}
23+
24+
try {
25+
const url = new URL(target, window.location.origin);
26+
if (url.origin !== window.location.origin) {
27+
return undefined;
28+
}
29+
return url.pathname;
30+
} catch {
31+
return undefined;
32+
}
33+
};
34+
35+
export const ErrorPage = ({
36+
image,
37+
description,
38+
refreshTarget,
39+
}: ErrorPageProps) => {
1940
const { t } = useTranslation();
2041

2142
const errorTitle = t('An unexpected error occurred.');
43+
const safeTarget = getSafeRefreshUrl(refreshTarget);
2244

2345
return (
2446
<>
@@ -77,20 +99,22 @@ export const ErrorPage = ({ image, description }: ErrorPageProps) => {
7799
</StyledButton>
78100
</StyledLink>
79101

80-
<StyledButton
81-
color="neutral"
82-
variant="bordered"
83-
icon={
84-
<Icon
85-
iconName="refresh"
86-
variant="symbols-outlined"
87-
$withThemeInherited
88-
/>
89-
}
90-
onClick={() => window.location.reload()}
91-
>
92-
{t('Refresh page')}
93-
</StyledButton>
102+
{safeTarget && (
103+
<StyledButton
104+
color="neutral"
105+
variant="bordered"
106+
icon={
107+
<Icon
108+
iconName="refresh"
109+
variant="symbols-outlined"
110+
$withThemeInherited
111+
/>
112+
}
113+
onClick={() => window.location.assign(safeTarget)}
114+
>
115+
{t('Refresh page')}
116+
</StyledButton>
117+
)}
94118
</Box>
95119
</Box>
96120
</>

src/frontend/apps/impress/src/features/docs/doc-versioning/components/DocVersionEditor.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export const DocVersionEditor = ({
3232
versionId,
3333
});
3434

35-
const { replace } = useRouter();
35+
const { replace, asPath } = useRouter();
3636
const [initialContent, setInitialContent] = useState<Y.XmlFragment>();
3737

3838
// Reset initialContent when versionId changes to avoid conflicts between versions
@@ -56,9 +56,10 @@ export const DocVersionEditor = ({
5656
if (error.status === 404) {
5757
void replace('/404');
5858
} else {
59-
void replace('/500');
59+
const fromPath = encodeURIComponent(asPath);
60+
void replace(`/500?from=${fromPath}`);
6061
}
61-
}, [isError, error, replace]);
62+
}, [isError, error, replace, asPath]);
6263

6364
if (isError) {
6465
return null;

src/frontend/apps/impress/src/features/docs/doc-versioning/components/VersionList.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,14 @@ const VersionListState = ({
3030
versions,
3131
}: VersionListStateProps) => {
3232
const { formatDateSpecial } = useDate();
33-
const { replace } = useRouter();
33+
const { replace, asPath } = useRouter();
3434

3535
useEffect(() => {
3636
if (error) {
37-
void replace('/500');
37+
const fromPath = encodeURIComponent(asPath);
38+
void replace(`/500?from=${fromPath}`);
3839
}
39-
}, [error, replace]);
40+
}, [error, replace, asPath]);
4041

4142
if (isLoading) {
4243
return (

src/frontend/apps/impress/src/pages/500.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useRouter } from 'next/router';
12
import { ReactElement } from 'react';
23
import { useTranslation } from 'react-i18next';
34

@@ -8,13 +9,18 @@ import { NextPageWithLayout } from '@/types/next';
89

910
const Page: NextPageWithLayout = () => {
1011
const { t } = useTranslation();
12+
const { query } = useRouter();
13+
const from = Array.isArray(query.from) ? query.from[0] : query.from;
14+
const refreshTarget =
15+
from?.startsWith('/') && !from.startsWith('//') ? from : undefined;
1116

1217
return (
1318
<ErrorPage
1419
image={error_img}
1520
description={t(
1621
'An unexpected error occurred. Go grab a coffee or try to refresh the page.',
1722
)}
23+
refreshTarget={refreshTarget}
1824
/>
1925
);
2026
};

src/frontend/apps/impress/src/pages/_error.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const Error = () => {
1515
<ErrorPage
1616
image={error_img}
1717
description={t('An unexpected error occurred.')}
18+
refreshTarget="/"
1819
/>
1920
);
2021
};

src/frontend/apps/impress/src/pages/docs/[id]/index.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ const DocPage = ({ id }: DocProps) => {
105105
const { setCurrentDoc } = useDocStore();
106106
const { addTask } = useBroadcastStore();
107107
const queryClient = useQueryClient();
108-
const { replace } = useRouter();
108+
const { replace, asPath } = useRouter();
109109
useCollaboration(doc?.id, doc?.content);
110110
const { t } = useTranslation();
111111
const { authenticated } = useAuth();
@@ -210,9 +210,10 @@ const DocPage = ({ id }: DocProps) => {
210210
}
211211

212212
if (error.status !== 403) {
213-
void replace('/500');
213+
const fromPath = encodeURIComponent(asPath);
214+
void replace(`/500?from=${fromPath}`);
214215
}
215-
}, [isError, error?.status, replace, authenticated, queryClient]);
216+
}, [isError, error?.status, replace, authenticated, queryClient, asPath]);
216217

217218
if (isError && error?.status) {
218219
if (error.status === 403) {

0 commit comments

Comments
 (0)