feat(usage): show Zed plan quota on the dashboard
Add a Zed usage handler so connected Zed accounts appear on /dashboard/quota. Reads GET /client/users/me for plan, edit predictions, optional hosted model requests and billing-cycle reset. Render unlimited rows as "N used · Unlimited" instead of 0 / ∞, and surface overdue-invoice / token-billing messages.
This commit is contained in:
@@ -150,6 +150,7 @@ export default function QuotaTable({
|
||||
|
||||
<div className="space-y-px">
|
||||
{currentPageRows.map((quota) => {
|
||||
const isUnlimited = quota.unlimited === true;
|
||||
const colors = getColorClasses(quota.remaining);
|
||||
const countdown = formatResetTime(quota.resetAt);
|
||||
const resetDisplay = formatResetTimeDisplay(quota.resetAt);
|
||||
@@ -174,6 +175,7 @@ export default function QuotaTable({
|
||||
|
||||
{/* Progress + used/total */}
|
||||
<div className={`min-w-0 flex-1 ${compact ? "space-y-1" : "space-y-1.5"}`}>
|
||||
{!isUnlimited && (
|
||||
<div className={`${compact ? "h-1" : "h-1.5"} rounded-full overflow-hidden border ${colors.bgLight} ${
|
||||
quota.remaining === 0 ? "border-black/10 dark:border-white/10" : "border-transparent"
|
||||
}`}>
|
||||
@@ -182,16 +184,23 @@ export default function QuotaTable({
|
||||
style={{ width: `${Math.min(quota.remaining, 100)}%` }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={`flex items-center justify-between gap-1 min-w-0 ${compact ? "text-[10px]" : "text-xs"}`}>
|
||||
<span
|
||||
className="text-text-muted truncate"
|
||||
title={`${quota.used.toLocaleString()} / ${quota.total > 0 ? quota.total.toLocaleString() : "∞"}`}
|
||||
title={
|
||||
isUnlimited
|
||||
? `${quota.used.toLocaleString()} used · Unlimited`
|
||||
: `${quota.used.toLocaleString()} / ${quota.total > 0 ? quota.total.toLocaleString() : "∞"}`
|
||||
}
|
||||
>
|
||||
{quota.used.toLocaleString()} / {quota.total > 0 ? quota.total.toLocaleString() : "∞"}
|
||||
{isUnlimited
|
||||
? `${quota.used.toLocaleString()} used · Unlimited`
|
||||
: `${quota.used.toLocaleString()} / ${quota.total > 0 ? quota.total.toLocaleString() : "∞"}`}
|
||||
</span>
|
||||
<span className={`font-medium ${colors.text} shrink-0`}>
|
||||
{quota.remaining}%
|
||||
<span className={`font-medium ${isUnlimited ? "text-green-600 dark:text-green-400" : colors.text} shrink-0`}>
|
||||
{isUnlimited ? "Unlimited" : `${quota.remaining}%`}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1265,6 +1265,11 @@ export default function ProviderLimits() {
|
||||
onHideQuota={(quotaRow) => handleHideQuota(conn.provider, quotaRow)}
|
||||
/>
|
||||
)}
|
||||
{quota?.message && !error && !isLoading && (
|
||||
<p className="mt-2 px-1 text-[10px] leading-relaxed text-text-muted">
|
||||
{quota.message}
|
||||
</p>
|
||||
)}
|
||||
{hiddenQuotaRows.length > 0 && (
|
||||
<div className="mt-2 flex min-w-0 items-center gap-1 border-t border-black/5 pt-2 text-[10px] text-text-muted dark:border-white/5">
|
||||
<span className="material-symbols-outlined shrink-0 text-[14px]">
|
||||
|
||||
@@ -558,6 +558,22 @@ export function parseQuotaData(provider, data) {
|
||||
}
|
||||
break;
|
||||
|
||||
case "zed":
|
||||
// Edit predictions + optional hosted model_requests; unlimited uses remainingPercentage.
|
||||
if (data.quotas) {
|
||||
Object.entries(data.quotas).forEach(([name, quota]) => {
|
||||
normalizedQuotas.push({
|
||||
name,
|
||||
used: quota.used || 0,
|
||||
total: quota.total || 0,
|
||||
resetAt: quota.resetAt || null,
|
||||
remainingPercentage: quota.remainingPercentage,
|
||||
unlimited: quota.unlimited,
|
||||
});
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// Generic fallback for unknown providers
|
||||
if (data.quotas) {
|
||||
|
||||
Reference in New Issue
Block a user