Files
Checkmate/client/src/Hooks/useStatusPageForm.ts
T
gorkem-bwl 1458cf4e4a fix(status-page): use operator-configured timezone on public pages
Anonymous visitors to a public status page have no persisted Redux
state, so ThemedHeatmap and ThemedHistogram tooltips always rendered
timestamps in the hardcoded "America/Toronto" default — regardless of
the timezone the operator configured. Even on the same domain,
ThemedHeatmap and ThemedHistogram had no code path to consume the
operator-configured statusPage.timezone returned by the API.

- Add SYSTEM_TIMEZONE module constant and a small resolveTimezone()
  helper to TimeUtils.ts. formatDateWithTz's timezone parameter is now
  optional and defaults to the system tz, so callers don't need to
  rebuild the prop -> browser -> UTC chain inline.
- Add a `timezone` field to StatusPageThemeContextValue so any themed
  component on the public page can read the page-wide tz without prop
  drilling (parallels how `theme` and `mode` are already exposed).
- Wire statusPage.timezone into StatusPageThemeProvider from
  Pages/StatusPage/Status/index.tsx (both the public route and the
  admin preview).
- ThemedHeatmap and ThemedHistogram now read tz from the context
  instead of useSelector(state.ui.timezone) — that selector was
  unreliable for anonymous visitors and removing it cuts a Redux
  subscription on the public page.
- Replace the inline `data?.timezone || Intl.DateTimeFormat()...`
  expression in useStatusPageForm.ts with resolveTimezone().

Closes #3628.
2026-05-01 22:28:12 -04:00

52 lines
1.7 KiB
TypeScript

import { useMemo } from "react";
import { statusPageSchema, type StatusPageFormData } from "@/Validation/statusPage";
import type { StatusPage } from "@/Types/StatusPage";
import {
DEFAULT_STATUS_PAGE_THEME,
DEFAULT_STATUS_PAGE_THEME_MODE,
} from "@/Types/StatusPage";
import type { Monitor } from "@/Types/Monitor";
import { resolveTimezone } from "@/Utils/TimeUtils";
interface UseStatusPageFormOptions {
data?: StatusPage | null;
monitors?: Monitor[] | null;
}
const generateDefaultUrl = () => Math.floor(Math.random() * 1000000).toString();
const transformLogo = (logo: StatusPage["logo"]): StatusPageFormData["logo"] => {
if (!logo || !logo.data) return null;
return {
data: `data:${logo.contentType};base64,${logo.data}`,
contentType: logo.contentType,
};
};
export const useStatusPageForm = ({
data = null,
monitors = null,
}: UseStatusPageFormOptions = {}) => {
return useMemo(() => {
const defaults: StatusPageFormData = {
companyName: data?.companyName || "",
url: data?.url || generateDefaultUrl(),
timezone: resolveTimezone(data?.timezone),
type: data?.type || ["uptime"],
color: data?.color || "#13715B",
monitors: data?.monitors || [],
isPublished: data?.isPublished ?? false,
showCharts: data?.showCharts ?? true,
showUptimePercentage: data?.showUptimePercentage ?? true,
showAdminLoginLink: data?.showAdminLoginLink ?? false,
showInfrastructure: data?.showInfrastructure ?? false,
customCSS: data?.customCSS || "",
theme: data?.theme ?? DEFAULT_STATUS_PAGE_THEME,
themeMode: data?.themeMode ?? DEFAULT_STATUS_PAGE_THEME_MODE,
logo: transformLogo(data?.logo),
};
return { schema: statusPageSchema, defaults };
}, [data, monitors]);
};