import { useState } from "react"; import { Button } from "~/components/ui/button"; import { describePreviewError } from "./errorCodeMessages"; interface Props { url: string; /** Chromium net error code, e.g. -105. */ code: number; /** Stringified Chromium error, e.g. "ERR_NAME_NOT_RESOLVED". */ description: string; onReload: () => void;} /** Theme-aware tailwind port of Chromium's "This site can't be reached" page. */export function PreviewUnreachable({ url, code, description, onReload }: Props) { const [showDetails, setShowDetails] = useState(false); const host = safeHost(url) ?? url; const friendly = describePreviewError(description); const errorLabel = description.length > 0 ? description : `ERR_${Math.abs(code) || "FAILED"}`; return ( <div className="relative flex h-full min-h-0 w-full overflow-y-auto bg-background"> <div className="mx-auto flex w-full max-w-xl flex-1 flex-col px-8 py-12 sm:py-16"> <ErrorIcon className="mb-6 size-12 text-muted-foreground/70" /> <h1 className="mb-3 text-2xl font-semibold leading-tight text-foreground"> This site can’t be reached </h1> <p className="text-sm leading-relaxed text-muted-foreground"> <span className="font-semibold text-foreground">{host}</span>: {friendly}. </p> {showDetails ? ( <div className="mt-6 rounded-lg border border-border bg-muted/40 p-4 text-sm"> <p className="mb-2 font-medium text-foreground">Try:</p> <ul className="list-disc space-y-1 pl-5 text-muted-foreground"> <li>Checking your connection</li> <li>Confirming the dev server is running</li> <li>Checking the proxy and the firewall</li> </ul> </div> ) : null} <div className="mt-8 text-xs uppercase tracking-wide text-muted-foreground/70"> {errorLabel} </div> <div className="mt-auto flex items-center gap-2 pt-8"> <Button type="button" variant="outline" size="sm" onClick={() => setShowDetails((value) => !value)} > {showDetails ? "Hide details" : "Details"} </Button> <div className="flex-1" /> <Button type="button" size="sm" onClick={onReload}> Reload </Button> </div> </div> </div> );} function ErrorIcon({ className }: { className?: string }) { return ( <svg viewBox="0 0 64 64" fill="none" stroke="currentColor" strokeWidth="2.5" className={className} > <path d="M16 12 L48 12 L48 52 L16 52 Z" /> <path d="M22 22 L42 22 M22 30 L36 30 M22 38 L40 38" strokeLinecap="round" /> <path d="M52 8 L12 56" strokeLinecap="round" /> </svg> );} function safeHost(url: string): string | null { try { return new URL(url).host; } catch { return null; }}
import { useState } from "react";
import { Button } from "~/components/ui/button";
import { describePreviewError } from "./errorCodeMessages";
interface Props {
url: string;
/** Chromium net error code, e.g. -105. */
code: number;
/** Stringified Chromium error, e.g. "ERR_NAME_NOT_RESOLVED". */
description: string;
onReload: () => void;
}
/** Theme-aware tailwind port of Chromium's "This site can't be reached" page. */
export function PreviewUnreachable({ url, code, description, onReload }: Props) {
const [showDetails, setShowDetails] = useState(false);
const host = safeHost(url) ?? url;
const friendly = describePreviewError(description);
const errorLabel = description.length > 0 ? description : `ERR_${Math.abs(code) || "FAILED"}`;
return (
<div className="relative flex h-full min-h-0 w-full overflow-y-auto bg-background">
<div className="mx-auto flex w-full max-w-xl flex-1 flex-col px-8 py-12 sm:py-16">
<ErrorIcon className="mb-6 size-12 text-muted-foreground/70" />
<h1 className="mb-3 text-2xl font-semibold leading-tight text-foreground">
This site can’t be reached
</h1>
<p className="text-sm leading-relaxed text-muted-foreground">
<span className="font-semibold text-foreground">{host}</span>: {friendly}.
</p>
{showDetails ? (
<div className="mt-6 rounded-lg border border-border bg-muted/40 p-4 text-sm">
<p className="mb-2 font-medium text-foreground">Try:</p>
<ul className="list-disc space-y-1 pl-5 text-muted-foreground">
<li>Checking your connection</li>
<li>Confirming the dev server is running</li>
<li>Checking the proxy and the firewall</li>
</ul>
</div>
) : null}
<div className="mt-8 text-xs uppercase tracking-wide text-muted-foreground/70">
{errorLabel}
</div>
<div className="mt-auto flex items-center gap-2 pt-8">
<Button
type="button"
variant="outline"
size="sm"
onClick={() => setShowDetails((value) => !value)}
>
{showDetails ? "Hide details" : "Details"}
</Button>
<div className="flex-1" />
<Button type="button" size="sm" onClick={onReload}>
Reload
</Button>
</div>
</div>
</div>
);
}
function ErrorIcon({ className }: { className?: string }) {
return (
<svg
viewBox="0 0 64 64"
fill="none"
stroke="currentColor"
strokeWidth="2.5"
className={className}
>
<path d="M16 12 L48 12 L48 52 L16 52 Z" />
<path d="M22 22 L42 22 M22 30 L36 30 M22 38 L40 38" strokeLinecap="round" />
<path d="M52 8 L12 56" strokeLinecap="round" />
</svg>
);
}
function safeHost(url: string): string | null {
try {
return new URL(url).host;
} catch {
return null;
}
}