import type { ReactNode } from "react";
import { useTranslation } from "react-i18next";
import { Icon, type IconName } from "./Icons";
export function PageHeader({
eyebrow,
title,
description,
actions,
}: {
eyebrow: string;
title: string;
description?: string;
actions?: ReactNode;
}) {
return (
{eyebrow}
{title}
{description &&
{description}
}
{actions && {actions}
}
);
}
export function SectionHeading({
title,
description,
action,
headingId,
}: {
title: string;
description?: string;
action?: ReactNode;
headingId?: string;
}) {
return (
{title}
{description &&
{description}
}
{action}
);
}
export function LoadingState({ label }: { label?: string }) {
const { t } = useTranslation("common");
return (
{label ?? t("states.loadingDefault")}
);
}
export function ErrorState({ message }: { message: string }) {
const { t } = useTranslation("common");
return (
{t("states.errorTitle")}{message}
);
}
// Shared rendering for describeApiError()'s output: a localized title + explanation +
// optional next step, with the raw backend/network text demoted to a "Technical
// details" disclosure -- never shown as the primary message. See
// frontend/src/api/errorMessages.ts and docs/fleet-ops-final-localization/audit.md.
export function ApiErrorNotice({ error }: { error: import("../api/errorMessages").ApiErrorInfo | null }) {
const { t } = useTranslation("common");
if (!error) return null;
return (
{error.title}
{error.explanation}
{error.nextStep &&
{error.nextStep}
}
{t("actions.technicalDetails")}
{error.technical}
);
}
export function EmptyState({
icon = "check",
title,
detail,
}: {
icon?: IconName;
title: string;
detail: string;
}) {
return (
);
}
export function IntegrationMark({ kind }: { kind: "n8n" | "rag" | "mcp" }) {
const labels = { n8n: "n8n", rag: "RA", mcp: "MC" };
return {labels[kind]};
}