Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/openstatusHQ/openstatus. ๐ซ Status page with uptime monitoring & API monitoring as code ๐ซ openstatus.dev
bun drizzle-orm monitoring monitoring-as-code nextjs observability on-call open-source shadcn-ui status-page statuspage synthetic-monitoring tinybird turso uptime uptime-checker uptime-monitor
Something went wrong. Try again.
10 kB ยท 344 lines
TSX
at incident-api
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345"use client";
import { UTCDate } from "@date-fns/utc";import { HoverCard, HoverCardContent, HoverCardTrigger,} from "@openstatus/ui/components/ui/hover-card";import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger,} from "@openstatus/ui/components/ui/tooltip";import { useCopyToClipboard } from "@openstatus/ui/hooks/use-copy-to-clipboard";import { useMediaQuery } from "@openstatus/ui/hooks/use-media-query";import { cn } from "@openstatus/ui/lib/utils";import type { HoverCardContentProps } from "@radix-ui/react-hover-card";import { format, formatDistanceToNowStrict } from "date-fns";import { Check, Copy } from "lucide-react";import { useEffect, useState } from "react";
type BaseProps = { date: Date; variant?: "simple" | "rich"; className?: string;};
type SimpleVariantProps = BaseProps & React.ComponentProps<typeof TooltipTrigger> & { variant?: "simple"; };
type RichVariantProps = BaseProps & React.ComponentProps<typeof HoverCardTrigger> & { variant: "rich"; side?: HoverCardContentProps["side"]; align?: HoverCardContentProps["align"]; alignOffset?: HoverCardContentProps["alignOffset"]; sideOffset?: HoverCardContentProps["sideOffset"]; };
type StatusTimestampProps = SimpleVariantProps | RichVariantProps;
/** * StatusTimestamp - Polymorphic timestamp display component with timezone support * * A flexible timestamp component that can display dates in two variants: * - **simple**: Shows a tooltip on hover with the formatted date (default) * - **rich**: Shows a hover card with local timezone, UTC, and relative time, plus copy-to-clipboard * * The component automatically detects the user's timezone using `Intl.DateTimeFormat()` * and displays the date in both the local timezone and UTC. The rich variant includes * a live-updating relative time display ("2 minutes ago") that refreshes every second * while the hover card is open. * * Touch device support is built-in for the rich variant, toggling the hover card on tap * instead of requiring hover. * * @param date - The date to display * @param variant - Display style: "simple" for tooltip, "rich" for hover card with details * @param side - (rich variant only) Placement of the hover card: "top" | "right" | "bottom" | "left" * @param align - (rich variant only) Alignment of the hover card: "start" | "center" | "end" * @param alignOffset - (rich variant only) Pixel offset for alignment (default: -4) * @param sideOffset - (rich variant only) Pixel offset from the trigger * * @example * // Simple variant with tooltip * ```tsx * <StatusTimestamp date={new Date()} /> * ``` * * @example * // Simple variant with custom children * ```tsx * <StatusTimestamp date={createdAt}> * Created at * </StatusTimestamp> * ``` * * @example * // Rich variant with hover card showing timezone details * ```tsx * <StatusTimestamp * date={new Date()} * variant="rich" * side="right" * > * 2 hours ago * </StatusTimestamp> * ``` * * @example * // Rich variant with custom positioning * ```tsx * <StatusTimestamp * date={incidentDate} * variant="rich" * side="bottom" * align="start" * alignOffset={0} * > * {format(incidentDate, "MMM d, HH:mm")} * </StatusTimestamp> * ``` */export function StatusTimestamp(props: StatusTimestampProps) { const { date, variant = "simple", className, ...rest } = props;
if (variant === "rich") { const { side = "right", align = "start", alignOffset = -4, sideOffset, children, onClick, ...triggerProps } = rest as Omit<RichVariantProps, "date" | "variant" | "className">;
return ( <RichTimestamp data-slot="status-timestamp" date={date} side={side} align={align} alignOffset={alignOffset} sideOffset={sideOffset} className={className} onClick={onClick} {...triggerProps} > {children} </RichTimestamp> ); }
const { children, ...triggerProps } = rest as Omit< SimpleVariantProps, "date" | "variant" | "className" >;
return ( <SimpleTimestamp data-slot="status-timestamp" date={date} className={className} {...triggerProps} > {children} </SimpleTimestamp> );}StatusTimestamp.displayName = "StatusTimestamp";
/** * SimpleTimestamp - Internal tooltip-based timestamp display * * Displays a formatted timestamp with an underlined, dashed decoration and shows * the full formatted date in a tooltip on hover. The timestamp is shown in monospace * font with muted foreground color. * * If no children are provided, displays the date in UTC format. If children are * provided, they are used as the trigger text while the tooltip still shows the * formatted date in the user's local timezone. * * @param date - The date to display * @param children - Optional custom text to display (falls back to formatted UTC date) * * @example * ```tsx * <SimpleTimestamp date={new Date()}> * 2 hours ago * </SimpleTimestamp> * ``` */function SimpleTimestamp({ date, className, children, ...props}: Omit<SimpleVariantProps, "variant">) { return ( <TooltipProvider> <Tooltip> <TooltipTrigger className={cn( "text-muted-foreground decoration-muted-foreground/30 font-mono underline decoration-dashed underline-offset-4", className, )} {...props} > {children || format(new UTCDate(date), "LLL dd, y HH:mm '(UTC)'")} </TooltipTrigger> <TooltipContent data-slot="status-timestamp-content"> <p className="font-mono">{format(date, "LLL dd, y HH:mm (z)")}</p> </TooltipContent> </Tooltip> </TooltipProvider> );}SimpleTimestamp.displayName = "SimpleTimestamp";
/** * RichTimestamp - Internal hover card timestamp display with timezone details * * Displays a hover card with comprehensive timestamp information: * - Local timezone timestamp with timezone abbreviation (e.g., "PST", "EST") * - UTC timestamp * - Relative time ("2 hours ago") that updates every second while open * * Each row in the hover card is clickable to copy the value to clipboard, with * a copy icon that appears on hover and changes to a check mark after copying. * * Touch device support: On touch devices (detected via `(hover: none)` media query), * tapping toggles the hover card open/closed instead of requiring hover. * * The relative time automatically updates every second while the hover card is * open, providing live feedback for recent timestamps. * * @param date - The date to display * @param side - Placement of the hover card (default: "right") * @param align - Alignment of the hover card (default: "start") * @param alignOffset - Pixel offset for alignment (default: -4) * @param sideOffset - Pixel offset from the trigger * @param children - Custom trigger content * * @example * ```tsx * <RichTimestamp * date={new Date()} * side="bottom" * align="center" * > * Click for details * </RichTimestamp> * ``` */function RichTimestamp({ date, side = "right", align = "start", alignOffset = -4, sideOffset, className, children, onClick, ...props}: Omit<RichVariantProps, "variant">) { const [open, setOpen] = useState(false); const isTouch = useMediaQuery("(hover: none)"); const [_, setRerender] = useState(0);
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; const relative = formatDistanceToNowStrict(date, { addSuffix: true }); const formatted = format(date, "LLL dd, y HH:mm:ss"); const utc = format(new UTCDate(date), "LLL dd, y HH:mm:ss");
useEffect(() => { // only setInterval if open if (!open) return;
const interval = setInterval(() => { setRerender((prev) => prev + 1); }, 1000);
return () => clearInterval(interval); }, [open]);
return ( <HoverCard openDelay={0} closeDelay={0} open={open} onOpenChange={setOpen}> <HoverCardTrigger className={className} onClick={(e) => { // NOTE: support touch devices if (isTouch) setOpen((prev) => !prev); onClick?.(e); }} {...props} > {children} </HoverCardTrigger> <HoverCardContent data-slot="status-timestamp-content" className="z-10 w-auto p-2" {...{ side, align, alignOffset, sideOffset }} > <dl className="flex flex-col gap-1"> <StatusTimestampRow value={formatted} label={timezone} /> <StatusTimestampRow value={utc} label="UTC" /> <StatusTimestampRow value={relative} label="Relative" /> </dl> </HoverCardContent> </HoverCard> );}RichTimestamp.displayName = "RichTimestamp";
/** * StatusTimestampRow - Internal component for hover card timestamp rows * * Displays a single row in the rich timestamp hover card with a label (e.g., "UTC") * and value (e.g., "Jan 15, 2024 10:30:45"). The entire row is clickable to copy * the value to clipboard. * * The copy icon appears on hover and changes to a check mark after successful copy. * A toast notification is shown when the value is copied. * * @param value - The timestamp string to display and copy * @param label - The label for this timestamp (e.g., "UTC", "PST", "Relative") */function StatusTimestampRow({ value, label,}: { value: string; label: string;}) { const { copy, isCopied } = useCopyToClipboard();
return ( <div data-slot="status-timestamp-row" className="group flex items-center justify-between gap-4 text-sm" onClick={(e) => { e.stopPropagation(); copy(value, { withToast: true }); }} > <dt className="text-muted-foreground">{label}</dt> <dd className="flex items-center gap-1 truncate font-mono"> <span className="invisible group-hover:visible"> {!isCopied ? ( <Copy className="h-3 w-3" /> ) : ( <Check className="h-3 w-3" /> )} </span> {value} </dd> </div> );}StatusTimestampRow.displayName = "StatusTimestampRow";