import { useEffect, useState } from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import * as stylex from "@stylexjs/stylex"; import { pauseMutation, pauseQuery } from "@/lib/queries"; import InlineError from "@/lib/InlineError"; import { styles as shared } from "@/ui/styles"; import { colors } from "@/ui/tokens.stylex"; const DURATIONS = [ { label: "60 seconds", seconds: 60 }, { label: "5 minutes", seconds: 300 }, { label: "30 minutes", seconds: 1800 }, { label: "Indefinitely", seconds: null }, ] as const; const styles = stylex.create({ /** * Disabled text darkens in light scheme and lightens in dark, the opposite * direction from `textMuted`, so the token cannot express it. */ trigger: { color: { default: null, ":disabled": "oklch(70.5% 0.015 286.067)", "@media (prefers-color-scheme: dark)": { default: null, ":disabled": "oklch(44.2% 0.017 285.786)" }, }, }, pausedRow: { display: "flex", flexDirection: "column", alignItems: "flex-end", }, pausedControls: { display: "flex", alignItems: "center", gap: "0.5rem", }, /** * Amber as standalone text on the app ground, not inside a warning banner, so * the `warn*` tokens — tuned against `warnSurface` — do not apply here. */ pausedLabel: { fontSize: "0.875rem", lineHeight: "1.25rem", color: { default: "oklch(55.5% 0.163 48.998)", "@media (prefers-color-scheme: dark)": "oklch(82.8% 0.189 84.429)", }, }, anchor: { position: "relative", }, menu: { position: "absolute", right: 0, top: "100%", zIndex: 10, marginTop: "0.25rem", display: "flex", width: "9rem", flexDirection: "column", borderRadius: "0.25rem", borderWidth: 1, borderStyle: "solid", borderColor: colors.border, backgroundColor: colors.surfaceRaised, paddingBlock: "0.25rem", boxShadow: "0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)", }, menuItem: { borderStyle: "none", backgroundColor: { default: "transparent", ":hover": colors.surfaceHover }, color: "inherit", paddingInline: "0.75rem", paddingBlock: "0.375rem", textAlign: "left", fontSize: "0.875rem", lineHeight: "1.25rem", }, }); export function formatRemaining(totalSeconds: number): string { const clamped = Math.max(0, totalSeconds); const hours = Math.floor(clamped / 3600); const minutes = Math.floor((clamped % 3600) / 60); const seconds = clamped % 60; const pad = (n: number) => String(n).padStart(2, "0"); return hours > 0 ? `${hours}:${pad(minutes)}:${pad(seconds)}` : `${minutes}:${pad(seconds)}`; } function nowSeconds(): number { return Math.floor(Date.now() / 1000); } function useNowSeconds(active: boolean): number { const [now, setNow] = useState(nowSeconds); useEffect(() => { if (!active) return; setNow(nowSeconds()); const id = setInterval(() => setNow(nowSeconds()), 1000); return () => clearInterval(id); }, [active]); return now; } export default function PauseWidget() { const queryClient = useQueryClient(); const { data } = useQuery({ ...pauseQuery(), refetchInterval: (query) => (query.state.data?.paused === true ? 5000 : false), }); const mutation = useMutation(pauseMutation(queryClient)); const [menuOpen, setMenuOpen] = useState(false); const now = useNowSeconds(data?.paused === true && data.until !== null); const paused = data?.paused === true; const { reset } = mutation; useEffect(() => reset(), [paused, reset]); if (data === undefined) { return ( ); } if (data.paused) { return (