"use client"; /** * `` — admin-overview version card. * * v1.15.12 H1 — a dedicated tile that surfaces the running build's * version, short build SHA, and build timestamp, plus a best-effort * "update available" hint computed against the latest GitHub release * tag. The release comparison reuses the existing * `GET /api/version/check-updates` proxy (server-side `safeFetch` past * the CSP), so no new route or OpenAPI schema is introduced. The check * is day-stale (`staleTime`) and never blocks the render: on any * failure the tile simply omits the update hint. * * Cookie-admin only by construction — it lives under the admin layout * (`/admin`), which gates on `requireAdmin()`. */ import { ArrowUpCircle, CheckCircle2, Clock, GitCommit, Tag } from "lucide-react"; import { useQuery } from "@tanstack/react-query"; import { formatDateTime } from "@/lib/format"; import { useTranslations } from "@/lib/i18n/context"; import { queryKeys } from "@/lib/query-keys"; import { StatusItem, usePublicVersion } from "./_shared"; type CheckUpdatesResult = | { status: "up_to_date"; current: string; latest_tag: string; checked_at: string; } | { status: "newer_available"; current: string; latest_tag: string; html_url: string | null; published_at: string | null; checked_at: string; } | { status: "unknown"; current: string; reason: string }; async function fetchUpdateCheck(): Promise { const res = await fetch("/api/version/check-updates"); if (!res.ok) { return { status: "unknown", current: "", reason: `http_${res.status}` }; } return (await res.json()).data as CheckUpdatesResult; } function shortSha(sha: string | null): string | null { if (!sha) return null; return sha.length > 7 ? sha.slice(0, 7) : sha; } export function VersionTileSection() { const { t } = useTranslations(); const { data: version } = usePublicVersion(); // Best-effort, day-stale, never throws into the render. The route is // auth-gated and proxies the GitHub Releases API through the server so // the production CSP doesn't block the call. const { data: update } = useQuery({ queryKey: queryKeys.versionUpdateCheck(), queryFn: fetchUpdateCheck, staleTime: 24 * 60 * 60_000, retry: false, }); const newer = update?.status === "newer_available"; const upToDate = update?.status === "up_to_date"; const sha = shortSha(version?.buildSha ?? null); return (
); }