import { AlertCircle } from "lucide-react";
import { isRouteErrorResponse } from "react-router";
import { isApiError, isConnectionError } from "~/server/headscale/api/error-client";
import cn from "~/utils/cn";
import Card from "./Card";
import Code from "./Code";
import Link from "./link";
export function getErrorMessage(error: Error | unknown): {
title: string;
jsxMessage: React.ReactNode;
} {
if (isRouteErrorResponse(error)) {
if (isApiError(error.data)) {
const { statusCode, rawData, data, requestUrl } = error.data;
if (statusCode >= 500) {
return {
jsxMessage: (
<>
The server responded with a status code of {statusCode}, indicating
a server-side issue. Please check the Headscale server status and try again later.
{error.data.data != null ? (
{JSON.stringify(error.data.data, null, 2)}
) : (
{error.data.rawData}
)}
)}
>
),
title: "Headscale API Error",
};
}
const authError = error.data.statusCode === 401 || error.data.statusCode === 403;
return {
jsxMessage: (
<>
{requestUrl}
{/* @ts-expect-error */}
{data === null ? (
<>
{statusCode} {rawData}
>
) : (
<>
{statusCode} {error.statusText}
>
)}
{JSON.stringify(error.data, null, 2)}
>
),
title: "Invalid response from Headscale API",
};
}
if (isConnectionError(error.data)) {
const { requestUrl, errorCode, errorMessage, extraData } = error.data;
return {
jsxMessage: (
<>
{requestUrl}
{errorCode}: {errorMessage}
{extraData != null && (
<>
{JSON.stringify(extraData, null, 2)}
>
)}
>
),
title: "Cannot connect to Headscale API",
};
}
return {
jsxMessage: (
<>
There was an error processing your request.
{JSON.stringify(error, null, 2)}
>
),
title: "Unexpected Error",
};
}
// Traverse the error chain to find the root cause
let rootError = error;
if (error.cause != null) {
rootError = error.cause as Error;
while (rootError.cause != null) {
rootError = rootError.cause as Error;
}
}
// TODO: If we are aggregate, concat into a single message
if (rootError instanceof AggregateError) {
throw new Error("AggregateError handling not implemented yet");
}
return {
jsxMessage: rootError.message,
title:
rootError.name.length > 0 && rootError.name !== "Error"
? `Error: ${rootError.name}`
: "Error",
};
}
interface ErrorBannerProps {
error: unknown;
className?: string;
}
export function ErrorBanner({ error, className }: ErrorBannerProps) {
const { title, jsxMessage } = getErrorMessage(error);
return (