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 {
title: 'Headscale API Error',
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}
)}
)}
>
),
};
}
const authError =
error.data.statusCode === 401 || error.data.statusCode === 403;
return {
title: 'Invalid response from Headscale API',
jsxMessage: (
<>
{requestUrl}
{/* @ts-expect-error */}
{data === null ? (
<>
{statusCode} {rawData}
>
) : (
<>
{statusCode} {error.statusText}
>
)}
{JSON.stringify(error.data, null, 2)}
>
),
};
}
if (isConnectionError(error.data)) {
const { requestUrl, errorCode, errorMessage, extraData } = error.data;
return {
title: 'Cannot connect to Headscale API',
jsxMessage: (
<>
{requestUrl}
{errorCode}: {errorMessage}
{extraData != null && (
<>
{JSON.stringify(extraData, null, 2)}
>
)}
>
),
};
}
return {
title: `Error ${error.status}`,
jsxMessage: (
<>
There was an error processing your request.
{JSON.stringify(error, null, 2)}
>
),
};
}
// 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 {
title:
rootError.name.length > 0 && rootError.name !== 'Error'
? `Error: ${rootError.name}`
: 'Error',
jsxMessage: rootError.message,
};
}
interface ErrorBannerProps {
error: unknown;
className?: string;
}
export function ErrorBanner({ error, className }: ErrorBannerProps) {
const { title, jsxMessage } = getErrorMessage(error);
return (