From 5c5833d37c4ebc7903dac7432756e9b2db995434 Mon Sep 17 00:00:00 2001 From: ArthurGamby Date: Wed, 4 Mar 2026 16:58:20 +0100 Subject: [PATCH] feat(docs): add live API status indicator to sidebar Adds a compact status indicator in the docs sidebar footer that fetches from the Prisma Statuspage API and polls every 5 minutes. Shows a colored dot with pulse animation for non-operational states. Co-Authored-By: Claude Opus 4.6 --- apps/docs/src/app/(docs)/(default)/layout.tsx | 13 +++- apps/docs/src/components/status-indicator.tsx | 67 +++++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 apps/docs/src/components/status-indicator.tsx diff --git a/apps/docs/src/app/(docs)/(default)/layout.tsx b/apps/docs/src/app/(docs)/(default)/layout.tsx index 834ff4f2c4..c71ffbf3e2 100644 --- a/apps/docs/src/app/(docs)/(default)/layout.tsx +++ b/apps/docs/src/app/(docs)/(default)/layout.tsx @@ -1,9 +1,12 @@ +import type { ComponentProps } from 'react'; import { source } from '@/lib/source'; import { baseOptions, links } from '@/lib/layout.shared'; import { VersionSwitcher } from '@/components/version-switcher'; import type { LinkItemType } from 'fumadocs-ui/layouts/shared'; import { DocsLayout } from '@/components/layout/notebook'; import { LATEST_VERSION } from '@/lib/version'; +import { StatusIndicator } from '@/components/status-indicator'; +import { cn } from '@prisma-docs/ui/lib/cn'; export default async function Layout({ children, }: { children: React.ReactNode; }) { const { nav, ...base } = baseOptions(); @@ -21,7 +24,15 @@ export default async function Layout({ children, }: { children: React.ReactNode; {...base} links={navbarLinks} nav={{ ...nav }} - sidebar={{ collapsible: false }} + sidebar={{ + collapsible: false, + footer: ({ className, ...props }: ComponentProps<'div'>) => ( +
+ + {props.children} +
+ ), + }} tree={source.pageTree} > {children} diff --git a/apps/docs/src/components/status-indicator.tsx b/apps/docs/src/components/status-indicator.tsx new file mode 100644 index 0000000000..0f6655ba7c --- /dev/null +++ b/apps/docs/src/components/status-indicator.tsx @@ -0,0 +1,67 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { cn } from "@prisma-docs/ui/lib/cn"; + +type StatusIndicator = "none" | "minor" | "major" | "critical"; + +interface StatusResponse { + status: { + indicator: StatusIndicator; + description: string; + }; +} + +const dotColors: Record = { + none: "bg-green-500", + minor: "bg-yellow-500", + major: "bg-orange-500", + critical: "bg-red-500", +}; + +const POLL_INTERVAL = 5 * 60 * 1000; // 5 minutes + +export function StatusIndicator() { + const [status, setStatus] = useState(null); + + useEffect(() => { + const fetchStatus = () => { + fetch("https://www.prisma-status.com/api/v2/status.json") + .then((res) => res.json()) + .then((data: StatusResponse) => setStatus(data.status)) + .catch(() => setStatus(null)); + }; + + fetchStatus(); + const interval = setInterval(fetchStatus, POLL_INTERVAL); + return () => clearInterval(interval); + }, []); + + if (!status) return null; + + const isOperational = status.indicator === "none"; + + return ( + + + {!isOperational && ( + + )} + + + {status.description} + + ); +}