170 lines
4.8 KiB
TypeScript
170 lines
4.8 KiB
TypeScript
import type { LinksFunction } from "@remix-run/node";
|
|
import {
|
|
Links,
|
|
Meta,
|
|
Outlet,
|
|
Scripts,
|
|
ScrollRestoration,
|
|
isRouteErrorResponse,
|
|
useRouteError
|
|
} from "@remix-run/react";
|
|
import React, { useEffect, useState, type PropsWithChildren } from "react";
|
|
|
|
import stylesheet from "bootstrap/dist/css/bootstrap.min.css?url";
|
|
|
|
export const links: LinksFunction = () => [
|
|
{ rel: "stylesheet", href: stylesheet },
|
|
];
|
|
|
|
import { Col, Container, Row } from "react-bootstrap";
|
|
|
|
function Document({
|
|
children,
|
|
title = "K-FRIDGE",
|
|
}: PropsWithChildren<{ title?: string}>) {
|
|
return (
|
|
<html lang="en" data-bs-theme="light">
|
|
<head>
|
|
<meta charSet="utf-8" />
|
|
<meta
|
|
name="viewport"
|
|
content="width=device-width, initial-scale=1"
|
|
/>
|
|
<Meta />
|
|
<Links />
|
|
</head>
|
|
<body>
|
|
{children}
|
|
<ScrollRestoration />
|
|
<Scripts />
|
|
</body>
|
|
</html>
|
|
);
|
|
}
|
|
|
|
export default function App() {
|
|
return (
|
|
<Document>
|
|
<Outlet></Outlet>
|
|
</Document>
|
|
);
|
|
}
|
|
|
|
export function ErrorBoundary() {
|
|
const error = useRouteError();
|
|
|
|
const [isOnline, setIsOnline] = useState<boolean>(
|
|
typeof navigator !== "undefined" ? navigator.onLine : true
|
|
);
|
|
|
|
const tryRefresh = async () => {
|
|
if (typeof window === "undefined") return;
|
|
try {
|
|
const res = await fetch(window.location.href, { method: "GET", cache: "no-store" });
|
|
if (res && res.ok) {
|
|
window.location.reload();
|
|
}
|
|
} catch (e) {
|
|
// network still down; ignore, we'll retry on the next interval or when online
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (typeof window === "undefined") return;
|
|
|
|
const handleOnline = () => {
|
|
setIsOnline(true);
|
|
tryRefresh();
|
|
};
|
|
const handleOffline = () => setIsOnline(false);
|
|
|
|
window.addEventListener("online", handleOnline);
|
|
window.addEventListener("offline", handleOffline);
|
|
|
|
const hourly = window.setInterval(() => {
|
|
tryRefresh();
|
|
}, 15 * 60 * 1000); // Every 15 minutes
|
|
|
|
return () => {
|
|
window.removeEventListener("online", handleOnline);
|
|
window.removeEventListener("offline", handleOffline);
|
|
window.clearInterval(hourly);
|
|
};
|
|
}, []);
|
|
|
|
if (isRouteErrorResponse(error)) {
|
|
return (
|
|
<Document
|
|
title={`${error.status} ${error.statusText}`}
|
|
>
|
|
<Container>
|
|
<Row className="align-items-md-stretch">
|
|
<Col md={12}>
|
|
<div className="h-100 p-5 text-bg-dark rounded-3">
|
|
<h1>{error.status}</h1>
|
|
{error.statusText}
|
|
<div className="mt-3">
|
|
<button
|
|
className="btn btn-primary me-2"
|
|
onClick={() => {
|
|
if (typeof window !== "undefined") window.location.reload();
|
|
}}
|
|
>
|
|
Reload page
|
|
</button>
|
|
<button
|
|
className="btn btn-outline-secondary"
|
|
onClick={() => {
|
|
tryRefresh();
|
|
}}
|
|
>
|
|
Try refresh now
|
|
</button>
|
|
<div className="mt-2">Status: {isOnline ? "Online" : "Offline"}. Will retry automatically every 15 minutes.</div>
|
|
</div>
|
|
</div>
|
|
</Col>
|
|
</Row>
|
|
</Container>
|
|
</Document>
|
|
);
|
|
}
|
|
|
|
const errorMessage =
|
|
error instanceof Error
|
|
? error.message
|
|
: "Unknown error";
|
|
return (
|
|
<Document title="Uh-oh!">
|
|
<Container>
|
|
<Row className="align-items-md-stretch">
|
|
<Col md={12}>
|
|
<div className="h-100 p-5 text-bg-dark rounded-3">
|
|
<h1>App Error</h1>
|
|
{errorMessage}
|
|
<div className="mt-3">
|
|
<button
|
|
className="btn btn-primary me-2"
|
|
onClick={() => {
|
|
if (typeof window !== "undefined") window.location.reload();
|
|
}}
|
|
>
|
|
Reload page
|
|
</button>
|
|
<button
|
|
className="btn btn-outline-secondary"
|
|
onClick={() => {
|
|
tryRefresh();
|
|
}}
|
|
>
|
|
Try refresh now
|
|
</button>
|
|
<div className="mt-2">Status: {isOnline ? "Online" : "Offline"}. Will retry automatically every 15 minutes.</div>
|
|
</div>
|
|
</div>
|
|
</Col>
|
|
</Row>
|
|
</Container>
|
|
</Document>
|
|
);
|
|
} |