MicProtocol / apps /web /src /components /UsageMeter.tsx
Benaah's picture
Upload folder using huggingface_hub
fb12607 verified
Raw History Blame Contribute Delete
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>
);
}