import type { UsageAllowancePeriod, WorkspaceUsageResponse } from "@opengeni/sdk/usage-allowances"; import { GaugeIcon, XIcon } from "lucide-react"; import { useEffect, useState, type ReactNode } from "react"; import { useUsage, type UsageClientLike } from "../hooks/use-usage"; import { cn } from "../lib/cn"; import { allowanceLabels, allowanceResetSentence, formatAllowanceInstant, type AllowanceLabels, } from "../usage/allowance-copy"; import { summarizeUsage, usagePercentLabel, type UsageSummary } from "../usage/summary"; import { useOpenGeniText, type OpenGeniText } from "../messages/runtime"; export type UsageLimitNoticeLabels = AllowanceLabels & { /** "You've used 85% of your usage limit." */ memberWarning: (percent: string) => string; /** "This workspace has used 85% of its usage budget." */ workspaceWarning: (percent: string) => string; memberExhausted: string; workspaceExhausted: string; dismiss: string; }; export const DEFAULT_USAGE_LIMIT_NOTICE_LABELS = (t: OpenGeniText): UsageLimitNoticeLabels => { return { ...allowanceLabels(t), memberWarning: (percent) => t("usageLimitNotice.youVeUsedPercentOf", { percent }), workspaceWarning: (percent) => t("usageLimitNotice.thisWorkspaceHasUsedPercent", { percent }), memberExhausted: t("usageLimitNotice.youVeReachedYourUsage"), workspaceExhausted: t("usageLimitNotice.thisWorkspaceHasReachedIts"), dismiss: t("usageLimitNotice.dismiss"), }; }; type NoticeBaseProps = { labels?: Partial | undefined; /** * Your own next step beside the message, for example an "Upgrade" or * "Request more" button. Receives the summary so it can differ by scope. */ action?: ((summary: UsageSummary) => ReactNode) | undefined; /** * Remember a dismissed warning for this browser tab. The key should name the * workspace; the period and scope are added for you, so a new warning (or a * new period) shows again. Reaching the limit is never dismissible. */ dismissStorageKey?: string | undefined; className?: string | undefined; }; export type UsageLimitNoticeProps = NoticeBaseProps & { /** A usage response you already loaded; omit it to read `/usage/me`. */ usage?: WorkspaceUsageResponse | null | undefined; client?: UsageClientLike | undefined; workspaceId?: string | undefined; period?: UsageAllowancePeriod | undefined; refreshKey?: unknown; }; /** * A calm line for the composer when a member is close to, or at, a usage * limit: what happened, who can raise it, and when it resets. Renders nothing * while usage is comfortable, unlimited, loading, or unreadable. */ export function UsageLimitNotice({ usage, client, workspaceId, period, refreshKey, ...view }: UsageLimitNoticeProps) { if (usage !== undefined) { return ; } return ( ); } function LoadingNotice({ client, workspaceId, period, refreshKey, ...view }: NoticeBaseProps & Pick) { const { summary } = useUsage({ client, workspaceId, period, refreshKey }); return ; } function readDismissed(key: string | null): boolean { if (!key || typeof sessionStorage === "undefined") return false; try { return sessionStorage.getItem(key) === "1"; } catch { return false; } } /** Presentational notice for a summary you computed (`summarizeUsage`). */ export function UsageLimitNoticeView({ summary, labels: overrides, action, dismissStorageKey, className, }: NoticeBaseProps & { summary: UsageSummary | null }) { const t = useOpenGeniText(); const labels = { ...DEFAULT_USAGE_LIMIT_NOTICE_LABELS(t), ...overrides }; const binding = summary?.binding ?? null; const state = summary?.state; const key = dismissStorageKey && binding && state === "warning" ? `${dismissStorageKey}:warning:${binding.scope}:${binding.resetsAt ?? "none"}` : null; const [dismissed, setDismissed] = useState(() => readDismissed(key)); useEffect(() => setDismissed(readDismissed(key)), [key]); if (!summary || !binding || (state !== "warning" && state !== "exhausted")) return null; if (state === "warning" && dismissed) return null; const workspace = binding.scope === "workspace"; const exhausted = state === "exhausted"; const headline = exhausted ? workspace ? labels.workspaceExhausted : labels.memberExhausted : workspace ? labels.workspaceWarning(usagePercentLabel(binding.fraction)) : labels.memberWarning(usagePercentLabel(binding.fraction)); const reset = allowanceResetSentence(labels, binding.resetsAt); const dismiss = () => { setDismissed(true); if (!key) return; try { sessionStorage.setItem(key, "1"); } catch { // Storage can be unavailable (privacy mode); the in-memory dismissal holds. } }; return (

{headline}{" "} {exhausted ? <>{workspace ? labels.workspaceRemedy : labels.memberRemedy} : null} {reset}

{action ?
{action(summary)}
: null} {!exhausted ? ( ) : null}
); }