Spaces:
Running
Running
Download apps/web/src/components/UsageMeter.tsx from Benaah/MicProtocol: direct link, hf CLI and curl.
- Browser
- Download file 6.11 kB
-
https://huggingface.co/spaces/Benaah/MicProtocol/resolve/main/apps/web/src/components/UsageMeter.tsx
- Command line
-
hf download hf://spaces/Benaah/MicProtocol/apps/web/src/components/UsageMeter.tsx
-
curl -L -o UsageMeter.tsx https://huggingface.co/spaces/Benaah/MicProtocol/resolve/main/apps/web/src/components/UsageMeter.tsx
6.11 kB
| "use client"; | |
| import React from "react"; | |
| import { trpc } from "@/lib/trpc"; | |
| import { AlertTriangle, CheckCircle, XCircle } from "lucide-react"; | |
| interface UsageMeterProps { | |
| /** The workspace ID is needed only to gate the query (enabled). */ | |
| workspaceId: string; | |
| } | |
| type StatRow = { | |
| label: string; | |
| current: number; | |
| limit: number | null; | |
| barColor: string; | |
| }; | |
| function isAtLimit(current: number, limit: number | null): "ok" | "warn" | "exceeded" { | |
| if (limit === null) return "ok"; | |
| const ratio = current / limit; | |
| if (ratio >= 1) return "exceeded"; | |
| if (ratio >= 0.8) return "warn"; | |
| return "ok"; | |
| } | |
| export default function UsageMeter({ workspaceId }: UsageMeterProps) { | |
| const { data, isLoading, error } = trpc.billing.getUsage.useQuery( | |
| undefined, | |
| { enabled: !!workspaceId, refetchInterval: 60_000 }, | |
| ); | |
| if (isLoading) { | |
| return ( | |
| <div className="usage-meter-skeleton" aria-busy="true"> | |
| <div className="skeleton-line" style={{ height: 12, width: "60%", marginBottom: 12 }} /> | |
| <div className="skeleton-line" style={{ height: 8, width: "100%", marginBottom: 8 }} /> | |
| <div className="skeleton-line" style={{ height: 8, width: "100%", marginBottom: 8 }} /> | |
| </div> | |
| ); | |
| } | |
| if (error || !data) return null; | |
| const { usage, planId, status } = data; | |
| const rows: StatRow[] = []; | |
| if (usage.guestsPerMonth.limit !== 0) { | |
| rows.push({ | |
| label: "Guests (this month)", | |
| current: usage.guestsPerMonth.current, | |
| limit: usage.guestsPerMonth.limit, | |
| barColor: "var(--color-primary)", | |
| }); | |
| } | |
| if (usage.episodesPerMonth.limit !== 0) { | |
| rows.push({ | |
| label: "Episodes (this month)", | |
| current: usage.episodesPerMonth.current, | |
| limit: usage.episodesPerMonth.limit, | |
| barColor: "var(--color-secondary)", | |
| }); | |
| } | |
| if (usage.clipsPerMonth.limit !== 0) { | |
| rows.push({ | |
| label: "AI Clips (this month)", | |
| current: usage.clipsPerMonth.current, | |
| limit: usage.clipsPerMonth.limit, | |
| barColor: "var(--color-accent)", | |
| }); | |
| } | |
| if (usage.exportMinutesPerMonth.limit !== 0) { | |
| rows.push({ | |
| label: "Export Minutes (this month)", | |
| current: usage.exportMinutesPerMonth.current, | |
| limit: usage.exportMinutesPerMonth.limit, | |
| barColor: "#8B5CF6", | |
| }); | |
| } | |
| if (rows.length === 0) return null; | |
| return ( | |
| <div className="usage-meter" role="region" aria-label="Plan usage summary"> | |
| <div | |
| style={{ | |
| display: "flex", | |
| justifyContent: "space-between", | |
| alignItems: "center", | |
| marginBottom: "var(--space-4)", | |
| }} | |
| > | |
| <span | |
| style={{ | |
| fontSize: "var(--text-xs)", | |
| fontWeight: 600, | |
| textTransform: "uppercase", | |
| letterSpacing: "0.05em", | |
| color: "var(--text-tertiary)", | |
| }} | |
| > | |
| Usage | |
| </span> | |
| <span | |
| className="status-badge" | |
| style={{ | |
| textTransform: "capitalize", | |
| fontSize: "var(--text-xs)", | |
| }} | |
| > | |
| {planId} · {status} | |
| </span> | |
| </div> | |
| <div style={{ display: "flex", flexDirection: "column", gap: "var(--space-3)" }}> | |
| {rows.map((row) => { | |
| const state = isAtLimit(row.current, row.limit); | |
| const pct = | |
| row.limit !== null && row.limit > 0 | |
| ? Math.min(100, Math.round((row.current / row.limit) * 100)) | |
| : 0; | |
| return ( | |
| <div key={row.label}> | |
| <div | |
| style={{ | |
| display: "flex", | |
| justifyContent: "space-between", | |
| fontSize: "var(--text-xs)", | |
| marginBottom: 4, | |
| }} | |
| > | |
| <span style={{ color: "var(--text-secondary)" }}>{row.label}</span> | |
| <span | |
| style={{ | |
| fontWeight: 600, | |
| color: | |
| state === "exceeded" | |
| ? "var(--color-error)" | |
| : state === "warn" | |
| ? "var(--color-warning)" | |
| : "var(--text-primary)", | |
| }} | |
| > | |
| {state === "exceeded" && ( | |
| <XCircle size={12} style={{ display: "inline", marginRight: 4, verticalAlign: "middle" }} /> | |
| )} | |
| {state === "warn" && ( | |
| <AlertTriangle size={12} style={{ display: "inline", marginRight: 4, verticalAlign: "middle" }} /> | |
| )} | |
| {state === "ok" && row.limit !== null && ( | |
| <CheckCircle size={12} style={{ display: "inline", marginRight: 4, verticalAlign: "middle" }} /> | |
| )} | |
| {row.limit === null ? "Unlimited" : `${row.current} / ${row.limit}`} | |
| </span> | |
| </div> | |
| {row.limit !== null && ( | |
| <div | |
| style={{ | |
| height: 6, | |
| borderRadius: 3, | |
| background: "var(--bg-tertiary)", | |
| overflow: "hidden", | |
| }} | |
| role="progressbar" | |
| aria-valuenow={pct} | |
| aria-valuemin={0} | |
| aria-valuemax={100} | |
| aria-label={`${row.label}: ${row.current} of ${row.limit}`} | |
| > | |
| <div | |
| style={{ | |
| width: `${Math.min(pct, 100)}%`, | |
| height: "100%", | |
| borderRadius: 3, | |
| background: | |
| state === "exceeded" | |
| ? "var(--color-error)" | |
| : state === "warn" | |
| ? "var(--color-warning)" | |
| : row.barColor, | |
| transition: "width 0.3s ease", | |
| }} | |
| /> | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| })} | |
| </div> | |
| </div> | |
| ); | |
| } | |