Security & reliability audit follow-up. The app was already well-hardened
(strict CSP synced across index.html/_headers/netlify.toml, careful
textContent/escapeHtml in the Leaflet DOM, broad sanitizeUrl usage,
consistent author-filtering on trust-sensitive queries). This closes the
remaining gaps:
- sanitizeUrl is now HTTPS-only: bare http:// links are upgraded to
https:// (not silently dropped); every other scheme (javascript:,
data:, file:, relative) is rejected. Removes a downgrade/mixed-content/
IP-leak vector and matches the native HTTPS-only WebView. Added a
sanitizeCssString() allowlist helper for future CSS interpolation.
- Route-level ErrorBoundary (RouteErrorBoundary) wraps the routed pages,
keyed by pathname: a crash in one page now shows a recoverable
"this page slammed → go back" message while the header/nav/rest of the
app keep working, instead of the single top-level boundary blanking the
ENTIRE app. Resets automatically on navigation.
- ErrorBoundary fallback no longer leaks stack traces to users (full
detail still logged to console for us); redesigned on-brand ("Slammed",
neon, reassures that account + data are safe). Updated its test.
Author-filtering spot-check across admin/newsletter/featured/crew/stash
paths found no gaps — all trust-sensitive queries correctly constrain
authors.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
52 lines
1.9 KiB
TypeScript
52 lines
1.9 KiB
TypeScript
import { useLocation } from 'react-router-dom';
|
|
import { ErrorBoundary } from '@/components/ErrorBoundary';
|
|
|
|
/**
|
|
* Per-route error boundary.
|
|
*
|
|
* The app has a single top-level ErrorBoundary (in main.tsx) as a last resort,
|
|
* but on its own that means ANY component throwing blanks the ENTIRE app. This
|
|
* wrapper adds a second, inner boundary around the routed page content so a
|
|
* crash in one page shows a recoverable message while the app keeps running.
|
|
*
|
|
* It is keyed by the current pathname: navigating to a different route remounts
|
|
* the boundary and clears the error automatically, so a broken page never
|
|
* "sticks" once the user moves away.
|
|
*/
|
|
export function RouteErrorBoundary({ children }: { children: React.ReactNode }) {
|
|
const location = useLocation();
|
|
|
|
return (
|
|
<ErrorBoundary
|
|
key={location.pathname}
|
|
fallback={
|
|
<div className="min-h-[60vh] flex items-center justify-center p-6">
|
|
<div className="max-w-sm w-full text-center space-y-5">
|
|
<div className="mx-auto flex h-14 w-14 items-center justify-center rounded-2xl bg-[#ff0040]/15 border border-[#ff0040]/30 text-3xl">
|
|
🛹
|
|
</div>
|
|
<div className="space-y-1.5">
|
|
<h2 className="text-xl font-black uppercase text-[#ff0040]">
|
|
This page slammed
|
|
</h2>
|
|
<p className="text-sm text-gray-400">
|
|
Something on this screen tripped up. Your account and data are safe.
|
|
Head back and try again.
|
|
</p>
|
|
</div>
|
|
<button
|
|
onClick={() => window.history.back()}
|
|
className="w-full px-4 py-3 rounded-full font-black uppercase bg-[#ff0040] text-white hover:bg-[#ff0040]/90 transition-colors"
|
|
>
|
|
Go back
|
|
</button>
|
|
</div>
|
|
</div>
|
|
}
|
|
>
|
|
{children}
|
|
</ErrorBoundary>
|
|
);
|
|
}
|
|
|
|
export default RouteErrorBoundary;
|