feat(qoder): show in Quota Tracker dashboard

Wire Qoder credits into the Quota Tracker card grid:

- Add `qoder` to USAGE_SUPPORTED_PROVIDERS so the connection passes the
  isUsageEligible filter at /api/providers/client and shows up in
  providerOptions on the dashboard.
- Reshape getQoderUsage so quota records (user, organization) live under
  `quotas` and scalar metadata (totalUsagePercentage, isQuotaExceeded,
  expiresAt) are siblings — the parser used to walk Object.entries(quotas)
  and would have rendered `totalUsagePercentage: 0.42` as a "0/0" row.
- Surface Qoder's expiresAt as resetAt on each quota record so the card
  shows when credits reset.
- Add a parser branch in ProviderLimits/utils.js: rename internal keys
  (user → "Personal", organization → "Organization"), drop empty org
  buckets so personal accounts don't render a misleading "0/0 Organization"
  row, and forward remaining/unit so the QuotaProgressBar can use them.
- Add Qoder's brand color (#EC4899) to ProviderLimitCard's color map.

42 tests still pass; build clean.
This commit is contained in:
Simon Shi
2026-05-29 17:36:27 +07:00
committed by decolua
parent 69bc71cf11
commit af7f6b1de2
4 changed files with 40 additions and 2 deletions
@@ -44,6 +44,7 @@ export default function ProviderLimitCard({
antigravity: "#4285F4",
codex: "#10A37F",
kiro: "#FF9900",
qoder: "#EC4899",
claude: "#D97757",
};
return colors[provider?.toLowerCase()] || "#6B7280";
@@ -160,6 +160,28 @@ export function parseQuotaData(provider, data) {
}
break;
case "qoder":
// Qoder ships a `user` quota and (optionally) an `organization`
// quota, both with same shape: {total, used, remaining, unit, resetAt}.
// Skip an organization bucket when its total is 0 — most personal
// Qoder accounts won't have one and rendering "0/0" is misleading.
if (data.quotas) {
Object.entries(data.quotas).forEach(([quotaType, quota]) => {
if (quotaType === "organization" && (!quota || (Number(quota.total) || 0) === 0)) {
return;
}
normalizedQuotas.push({
name: quotaType === "user" ? "Personal" : quotaType === "organization" ? "Organization" : quotaType,
used: quota.used || 0,
total: quota.total || 0,
remaining: quota.remaining,
unit: quota.unit,
resetAt: quota.resetAt || null,
});
});
}
break;
case "claude":
if (data.message) {
// Handle error message case
+1
View File
@@ -267,6 +267,7 @@ export const USAGE_SUPPORTED_PROVIDERS = [
"claude",
"antigravity",
"kiro",
"qoder",
"github",
"codex",
"kimi-coding",