2024-05-26 17:13:34 +02:00
|
|
|
import { useMemo } from "react";
|
2025-05-23 21:35:04 +03:00
|
|
|
import { IconExclamationCircle, IconShield } from "@tabler/icons-react";
|
2024-05-26 17:13:34 +02:00
|
|
|
import { TRPCClientError } from "@trpc/client";
|
|
|
|
|
import type { DefaultErrorData } from "@trpc/server/unstable-core-do-not-import";
|
|
|
|
|
|
|
|
|
|
import type { WidgetKind } from "@homarr/definitions";
|
|
|
|
|
|
2024-09-17 19:30:14 +02:00
|
|
|
import type { WidgetDefinition } from "..";
|
2024-05-26 17:13:34 +02:00
|
|
|
import { widgetImports } from "..";
|
|
|
|
|
import { ErrorBoundaryError } from "./base";
|
2025-05-23 21:35:04 +03:00
|
|
|
import type { BaseWidgetErrorProps } from "./base-component";
|
2024-05-26 17:13:34 +02:00
|
|
|
import { BaseWidgetError } from "./base-component";
|
|
|
|
|
|
|
|
|
|
interface WidgetErrorProps {
|
|
|
|
|
kind: WidgetKind;
|
|
|
|
|
error: unknown;
|
|
|
|
|
resetErrorBoundary: () => void;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const WidgetError = ({ error, resetErrorBoundary, kind }: WidgetErrorProps) => {
|
|
|
|
|
const currentDefinition = useMemo(() => widgetImports[kind].definition, [kind]);
|
|
|
|
|
|
|
|
|
|
if (error instanceof ErrorBoundaryError) {
|
|
|
|
|
return <BaseWidgetError {...error.getErrorBoundaryData()} onRetry={resetErrorBoundary} />;
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-23 21:35:04 +03:00
|
|
|
const widgetTrpcErrorData = handleWidgetTrpcError(error, currentDefinition);
|
|
|
|
|
if (widgetTrpcErrorData) {
|
|
|
|
|
return <BaseWidgetError {...widgetTrpcErrorData} onRetry={resetErrorBoundary} />;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const trpcErrorData = handleCommonTrpcError(error);
|
|
|
|
|
if (trpcErrorData) {
|
|
|
|
|
return <BaseWidgetError {...trpcErrorData} onRetry={resetErrorBoundary} />;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
2024-09-17 19:30:14 +02:00
|
|
|
<BaseWidgetError
|
|
|
|
|
icon={IconExclamationCircle}
|
|
|
|
|
message={(error as { toString: () => string }).toString()}
|
|
|
|
|
onRetry={resetErrorBoundary}
|
|
|
|
|
/>
|
|
|
|
|
);
|
2025-05-23 21:35:04 +03:00
|
|
|
};
|
2024-09-17 19:30:14 +02:00
|
|
|
|
2025-05-23 21:35:04 +03:00
|
|
|
const handleWidgetTrpcError = (
|
|
|
|
|
error: unknown,
|
|
|
|
|
currentDefinition: WidgetDefinition,
|
|
|
|
|
): Omit<BaseWidgetErrorProps, "onRetry"> | null => {
|
|
|
|
|
if (!(error instanceof TRPCClientError && "code" in error.data)) return null;
|
2024-05-26 17:13:34 +02:00
|
|
|
|
2025-05-23 21:35:04 +03:00
|
|
|
const errorData = error.data as DefaultErrorData;
|
|
|
|
|
|
|
|
|
|
if (!("errors" in currentDefinition) || currentDefinition.errors === undefined) return null;
|
|
|
|
|
|
|
|
|
|
const errors: Exclude<WidgetDefinition["errors"], undefined> = currentDefinition.errors;
|
|
|
|
|
const errorDefinition = errors[errorData.code];
|
|
|
|
|
|
|
|
|
|
if (!errorDefinition) return null;
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
...errorDefinition,
|
|
|
|
|
showLogsLink: !errorDefinition.hideLogsLink,
|
|
|
|
|
};
|
|
|
|
|
};
|
2024-05-26 17:13:34 +02:00
|
|
|
|
2025-05-23 21:35:04 +03:00
|
|
|
const handleCommonTrpcError = (error: unknown): Omit<BaseWidgetErrorProps, "onRetry"> | null => {
|
|
|
|
|
if (!(error instanceof TRPCClientError && "code" in error.data)) return null;
|
2024-05-26 17:13:34 +02:00
|
|
|
|
2025-05-23 21:35:04 +03:00
|
|
|
const errorData = error.data as DefaultErrorData;
|
2024-09-17 19:30:14 +02:00
|
|
|
|
2025-05-23 21:35:04 +03:00
|
|
|
if (errorData.code === "UNAUTHORIZED" || errorData.code === "FORBIDDEN") {
|
|
|
|
|
return {
|
|
|
|
|
icon: IconShield,
|
|
|
|
|
message: "You don't have permission to access this widget",
|
|
|
|
|
showLogsLink: false,
|
|
|
|
|
};
|
2024-05-26 17:13:34 +02:00
|
|
|
}
|
|
|
|
|
|
2025-05-23 21:35:04 +03:00
|
|
|
return null;
|
2024-05-26 17:13:34 +02:00
|
|
|
};
|