From 12f63848566737276487711cafe132dd38b33842 Mon Sep 17 00:00:00 2001 From: Royce59 Date: Sat, 1 Aug 2026 03:39:07 -0700 Subject: [PATCH] =?UTF-8?q?=D0=A4=D0=B8=D0=BA=D1=81=20=D0=BE=D1=88=D0=B8?= =?UTF-8?q?=D0=B1=D0=BE=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nginx.conf | 1 + src/App.tsx | 73 ++++++++++++++----- src/components/LegalPage/LegalPage.module.css | 6 ++ src/main.tsx | 36 +++++++++ worker/index.js | 20 ++++- 5 files changed, 114 insertions(+), 22 deletions(-) diff --git a/nginx.conf b/nginx.conf index 6ed80b2..6837e93 100644 --- a/nginx.conf +++ b/nginx.conf @@ -13,6 +13,7 @@ server { } location / { + add_header Cache-Control "no-cache" always; try_files $uri $uri/ /index.html; } } diff --git a/src/App.tsx b/src/App.tsx index cd69c24..b98b291 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,7 +1,7 @@ import '@mantine/core/styles.css'; -import { Center, createTheme, Loader, MantineProvider } from '@mantine/core'; -import { lazy, Suspense } from 'react'; +import { Button, Center, createTheme, Loader, MantineProvider, Stack, Text, Title } from '@mantine/core'; +import { Component, lazy, Suspense, type ReactNode } from 'react'; import { Route, Routes } from 'react-router-dom'; import { ScrollToTop } from './components/ScrollToTop/ScrollToTop'; import './style.css'; @@ -39,27 +39,60 @@ const theme = createTheme({ }, }); +class PageErrorBoundary extends Component< + { children: ReactNode }, + { hasError: boolean } +> { + state = { hasError: false }; + + static getDerivedStateFromError() { + return { hasError: true }; + } + + render() { + if (this.state.hasError) { + return ( +
+ + + This page could not be loaded + + + Rosetta may have been updated while this tab was open. Reload the page to continue. + + + +
+ ); + } + + return this.props.children; + } +} + export default function App() { return ( - - - - - } - > - - } /> - } /> - } /> - } /> - } /> - } /> - } /> - - + + + + + + } + > + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + ); } diff --git a/src/components/LegalPage/LegalPage.module.css b/src/components/LegalPage/LegalPage.module.css index 8a8f27c..c9ab85d 100644 --- a/src/components/LegalPage/LegalPage.module.css +++ b/src/components/LegalPage/LegalPage.module.css @@ -1,18 +1,22 @@ .page { display: flex; flex-direction: column; + min-width: 0; min-height: 100vh; background: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-9)); } .shell { flex: 1; + width: 100%; + min-width: 0; padding-top: 72px; padding-bottom: 96px; } .main { width: 100%; + min-width: 0; max-width: 900px; margin-inline: auto; padding: clamp(30px, 5vw, 60px); @@ -21,6 +25,7 @@ } .intro { + min-width: 0; max-width: 760px; padding-bottom: 36px; border-bottom: 1px solid light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-5)); @@ -69,6 +74,7 @@ } .section { + min-width: 0; margin-top: 44px; padding-top: 36px; border-top: 1px solid light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-5)); diff --git a/src/main.tsx b/src/main.tsx index b6ebc29..327ca80 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -2,6 +2,42 @@ import { createRoot } from 'react-dom/client' import { BrowserRouter } from 'react-router-dom' import App from './App.tsx' +const PRELOAD_RELOAD_KEY = 'rosetta:preload-reload-at' +const PRELOAD_RELOAD_COOLDOWN = 10_000 + +if (import.meta.env.PROD) { + window.addEventListener('vite:preloadError', (event) => { + let lastReload = 0 + + try { + lastReload = Number(window.sessionStorage.getItem(PRELOAD_RELOAD_KEY) || 0) + } catch { + // Storage can be unavailable in restricted browser modes. + } + + if (Date.now() - lastReload < PRELOAD_RELOAD_COOLDOWN) { + return + } + + event.preventDefault() + + try { + window.sessionStorage.setItem(PRELOAD_RELOAD_KEY, String(Date.now())) + } catch { + // Reload recovery still works when storage is unavailable. + } + + window.location.reload() + }) + + window.setTimeout(() => { + try { + window.sessionStorage.removeItem(PRELOAD_RELOAD_KEY) + } catch { + // Nothing to clear when storage is unavailable. + } + }, PRELOAD_RELOAD_COOLDOWN) +} createRoot(document.getElementById('root')!).render( diff --git a/worker/index.js b/worker/index.js index a163804..1992a44 100644 --- a/worker/index.js +++ b/worker/index.js @@ -1,3 +1,18 @@ +function withFreshHtml(response) { + if (!response.headers.get('content-type')?.includes('text/html')) { + return response; + } + + const headers = new Headers(response.headers); + headers.set('Cache-Control', 'no-cache'); + + return new Response(response.body, { + status: response.status, + statusText: response.statusText, + headers, + }); +} + const worker = { async fetch(request, env) { const response = await env.ASSETS.fetch(request); @@ -7,11 +22,12 @@ const worker = { request.method !== 'GET' || !request.headers.get('accept')?.includes('text/html') ) { - return response; + return withFreshHtml(response); } const indexUrl = new URL('/index.html', request.url); - return env.ASSETS.fetch(new Request(indexUrl, request)); + const indexResponse = await env.ASSETS.fetch(new Request(indexUrl, request)); + return withFreshHtml(indexResponse); }, };