Files
Checkmate/client/src/Pages/StatusPage/Status/index.tsx
T
2026-08-17 13:30:03 -07:00

204 lines
6.0 KiB
TypeScript

import { BasePage, BaseFallback } from "@/Components/design-elements";
import Typography from "@mui/material/Typography";
import { Link, useSearchParams } from "react-router-dom";
import Stack from "@mui/material/Stack";
import Box from "@mui/material/Box";
import { useTheme } from "@mui/material";
import { useTranslation } from "react-i18next";
import { useIsAdmin } from "@/Hooks/useIsAdmin";
import { useLocation, useParams } from "react-router-dom";
import { useGet } from "@/Hooks/UseApi";
import {
isStatusPageRange,
resolveStatusPageTheme,
STATUS_PAGE_RANGES,
type StatusPageRange,
} from "@/Types/StatusPage";
import {
PUBLIC_STATUS_PAGE_PREFIX,
type StatusPageResponse,
type StatusPageTheme,
} from "@/Types/StatusPage";
import {
buildStatusPageApiPath,
getStatusPagePreviewUrl,
getStatusPagePublicUrl,
isCustomDomainHost,
} from "@/Utils/statusPageUrl";
import { cssReferencesExternalResource } from "@/Utils/customCss";
import { HeaderStatusPageControls } from "@/Pages/StatusPage/Status/Components/HeaderStatusPageControls";
import { StatusPageThemeProvider } from "@/Pages/StatusPage/Status/themes/StatusPageThemeProvider";
import {
BaseStatusPage,
type ThemeConfig,
} from "@/Pages/StatusPage/Status/themes/shared/BaseStatusPage";
import { BrowserFrame } from "@/Pages/StatusPage/Status/themes/BrowserFrame";
import { refinedStyles } from "@/Pages/StatusPage/Status/themes/refined/styles";
import { RefinedHeader } from "@/Pages/StatusPage/Status/themes/refined/RefinedHeader";
import { RefinedHero } from "@/Pages/StatusPage/Status/themes/refined/RefinedHero";
import { modernStyles } from "@/Pages/StatusPage/Status/themes/modern/styles";
import { ModernHeader } from "@/Pages/StatusPage/Status/themes/modern/ModernHeader";
import { ModernHero } from "@/Pages/StatusPage/Status/themes/modern/ModernHero";
import { boldStyles } from "@/Pages/StatusPage/Status/themes/bold/styles";
import { BoldHeader } from "@/Pages/StatusPage/Status/themes/bold/BoldHeader";
import { BoldHero } from "@/Pages/StatusPage/Status/themes/bold/BoldHero";
import { editorialStyles } from "@/Pages/StatusPage/Status/themes/editorial/styles";
import { EditorialHeader } from "@/Pages/StatusPage/Status/themes/editorial/EditorialHeader";
import { EditorialHero } from "@/Pages/StatusPage/Status/themes/editorial/EditorialHero";
import { minimalStyles } from "@/Pages/StatusPage/Status/themes/minimal/styles";
const THEME_CONFIGS: Record<StatusPageTheme, ThemeConfig<any>> = {
refined: {
createStyles: refinedStyles,
HeaderSlot: RefinedHeader,
HeroSlot: RefinedHero,
},
modern: {
createStyles: modernStyles,
HeaderSlot: ModernHeader,
HeroSlot: ModernHero,
overallStatusOptions: { iconSize: 20 },
},
bold: {
createStyles: boldStyles,
HeaderSlot: BoldHeader,
HeroSlot: BoldHero,
overallStatusOptions: { iconSize: 18 },
},
editorial: {
createStyles: editorialStyles,
HeaderSlot: EditorialHeader,
HeroSlot: EditorialHero,
overallStatusOptions: { allUpKey: "pages.statusPages.editorial.allUp" },
},
minimal: {
createStyles: minimalStyles,
HeaderSlot: RefinedHeader,
HeroSlot: RefinedHero,
},
};
const StatusPageView = () => {
const theme = useTheme();
const { t } = useTranslation();
const { url } = useParams();
const isAdmin = useIsAdmin();
const location = useLocation();
const [searchParams, setSearchParams] = useSearchParams();
const onCustomDomainHost = isCustomDomainHost();
const isPublic =
onCustomDomainHost || location.pathname.startsWith(PUBLIC_STATUS_PAGE_PREFIX);
const rawRange = searchParams.get("range");
const range = isStatusPageRange(rawRange) ? rawRange : "latest";
const apiUrl = buildStatusPageApiPath({
url,
useCustomDomain: onCustomDomainHost,
range,
});
const { data, isLoading, error } = useGet<StatusPageResponse>(
apiUrl,
{},
{
refreshInterval: range === "latest" ? 10000 : 60000,
}
);
const statusPage = data?.statusPage;
const monitors = data?.monitors ?? [];
if (!statusPage) return null;
if (monitors.length === 0) {
return (
<BasePage
loading={isLoading}
error={error}
breadcrumbOverride={isPublic ? [] : undefined}
>
<Stack alignItems={"center"}>
<BaseFallback>
<Typography
variant="h1"
marginY={theme.spacing(4)}
color={theme.palette.text.secondary}
>
{t("pages.statusPages.details.empty.title")}
</Typography>
{isAdmin && (
<Link to={`/status/configure/${url}`}>
{t("pages.statusPages.details.empty.addMonitor")}
</Link>
)}
</BaseFallback>
</Stack>
</BasePage>
);
}
// Public route: render directly on the viewport, themed background covers everything.
if (isPublic) {
const themeConfig = THEME_CONFIGS[resolveStatusPageTheme(statusPage.theme)];
const customCss =
statusPage.customCSS && !cssReferencesExternalResource(statusPage.customCSS)
? statusPage.customCSS
: "";
return (
<StatusPageThemeProvider
theme={statusPage.theme}
themeMode={statusPage.themeMode}
timezone={statusPage.timezone}
brandColor={statusPage.color}
paintBody
>
{customCss && <style>{customCss}</style>}
<BaseStatusPage
statusPage={statusPage}
monitors={monitors}
config={themeConfig}
/>
</StatusPageThemeProvider>
);
}
const publicUrl = getStatusPagePublicUrl(statusPage);
const previewUrl = getStatusPagePreviewUrl(statusPage);
return (
<BasePage
loading={isLoading}
error={error}
breadcrumbOverride={undefined}
sx={{ flex: 1, minHeight: 0 }}
>
<HeaderStatusPageControls
isAdmin={isAdmin}
statusPage={statusPage}
isPublic={false}
/>
<StatusPageThemeProvider
theme={statusPage.theme}
themeMode={statusPage.themeMode}
timezone={statusPage.timezone}
transparent
>
<BrowserFrame url={publicUrl}>
<Box
component="iframe"
src={previewUrl}
title={t("pages.statusPages.preview.title")}
flex={1}
width="100%"
border={0}
/>
</BrowserFrame>
</StatusPageThemeProvider>
</BasePage>
);
};
export default StatusPageView;