From 3ac40829c00ec3f38fe1d7cbf9d7b69e9f7fc4b2 Mon Sep 17 00:00:00 2001
From: Diego Rodrigues de Sa e Souza
<8016841+diegosouzapw@users.noreply.github.com>
Date: Thu, 4 Jun 2026 18:33:34 -0300
Subject: [PATCH] feat(quota): show OAuth token expiry on provider cards
(small, blue, informative) (#3178)
Show OAuth token expiry on provider cards (small, blue, informative). Integrated into release/v3.8.10.
---
.../ProviderLimits/parts/QuotaCardHeader.tsx | 32 +++++++++++-
src/i18n/messages/en.json | 2 +
src/i18n/messages/pt-BR.json | 2 +
tests/unit/quota-token-expiry-display.test.ts | 49 +++++++++++++++++++
4 files changed, 84 insertions(+), 1 deletion(-)
create mode 100644 tests/unit/quota-token-expiry-display.test.ts
diff --git a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/parts/QuotaCardHeader.tsx b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/parts/QuotaCardHeader.tsx
index 855c2b8579..2b3cf96acb 100644
--- a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/parts/QuotaCardHeader.tsx
+++ b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/parts/QuotaCardHeader.tsx
@@ -4,7 +4,8 @@ import { useTranslations } from "next-intl";
import Badge from "@/shared/components/Badge";
import ProviderIcon from "@/shared/components/ProviderIcon";
import { pickDisplayValue } from "@/shared/utils/maskEmail";
-import { STATUS_EMOJI, type CardStatus } from "../utils";
+import { STATUS_EMOJI, formatCountdown, type CardStatus } from "../utils";
+import { translateUsageOrFallback } from "../i18nFallback";
interface Props {
connection: any;
@@ -41,6 +42,27 @@ export default function QuotaCardHeader({
connection.provider
);
+ // OAuth token expiry — informative only. Shown small/blue for connections that
+ // expose a concrete token expiry (e.g. Codex), so an operator can see at a
+ // glance when the access token rotates. Hidden for API-key / no-expiry connections.
+ const tokenExpiryIso =
+ connection.authType === "oauth"
+ ? connection.tokenExpiresAt || connection.expiresAt || null
+ : null;
+ const tokenExpiryMs = tokenExpiryIso ? new Date(tokenExpiryIso).getTime() : NaN;
+ const hasTokenExpiry = Number.isFinite(tokenExpiryMs);
+ const tokenCountdown = hasTokenExpiry ? formatCountdown(tokenExpiryIso) : null;
+ const tokenExpiryLabel = !hasTokenExpiry
+ ? null
+ : tokenCountdown
+ ? translateUsageOrFallback(t, "tokenExpiresIn", `Token expires in ${tokenCountdown}`, {
+ time: tokenCountdown,
+ })
+ : translateUsageOrFallback(t, "tokenExpired", "Token expired");
+ const tokenExpiryTitle = hasTokenExpiry
+ ? new Date(tokenExpiryMs).toLocaleString()
+ : undefined;
+
return (
@@ -85,6 +107,14 @@ export default function QuotaCardHeader({
{accountName}
+ {tokenExpiryLabel && (
+
+ {tokenExpiryLabel}
+
+ )}
diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json
index 767ab319f7..5bcc85bd2a 100644
--- a/src/i18n/messages/en.json
+++ b/src/i18n/messages/en.json
@@ -6289,6 +6289,8 @@
"notApplicable": "N/A",
"rawPlanWithValue": "Raw plan: {plan}",
"noPlanFromProvider": "No plan from provider",
+ "tokenExpiresIn": "Token expires in {time}",
+ "tokenExpired": "Token expired",
"noQuotaData": "No quota data",
"cardExpand": "Expand",
"cardCollapse": "Collapse",
diff --git a/src/i18n/messages/pt-BR.json b/src/i18n/messages/pt-BR.json
index 948f2f2d39..f7566516da 100644
--- a/src/i18n/messages/pt-BR.json
+++ b/src/i18n/messages/pt-BR.json
@@ -7611,6 +7611,8 @@
"notApplicable": "N/D",
"rawPlanWithValue": "Plano bruto: {plan}",
"noPlanFromProvider": "Sem plano do provedor",
+ "tokenExpiresIn": "Token expira em {time}",
+ "tokenExpired": "Token expirado",
"noQuotaData": "Sem dados de cota",
"cardExpand": "Expandir",
"cardCollapse": "Recolher",
diff --git a/tests/unit/quota-token-expiry-display.test.ts b/tests/unit/quota-token-expiry-display.test.ts
new file mode 100644
index 0000000000..32c4616634
--- /dev/null
+++ b/tests/unit/quota-token-expiry-display.test.ts
@@ -0,0 +1,49 @@
+/**
+ * The quota card shows an informative (small, blue) OAuth token-expiry line for
+ * connections that expose a concrete token expiry (e.g. Codex), and an expired
+ * marker otherwise. API-key / no-expiry connections show nothing.
+ */
+import test from "node:test";
+import assert from "node:assert/strict";
+import fs from "node:fs";
+import path from "node:path";
+import { fileURLToPath } from "node:url";
+
+const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
+const headerPath = path.join(
+ repoRoot,
+ "src/app/(dashboard)/dashboard/usage/components/ProviderLimits/parts/QuotaCardHeader.tsx"
+);
+const source = fs.readFileSync(headerPath, "utf8");
+
+test("QuotaCardHeader derives token expiry only for OAuth connections with a known expiry", () => {
+ assert.match(source, /connection\.authType === "oauth"/, "must gate on oauth auth type");
+ assert.match(
+ source,
+ /connection\.tokenExpiresAt \|\| connection\.expiresAt/,
+ "must read tokenExpiresAt with expiresAt fallback"
+ );
+ assert.match(source, /formatCountdown\(/, "must format the countdown");
+});
+
+test("QuotaCardHeader renders the expiry as a small blue (sky) informative line", () => {
+ assert.match(source, /text-\[10px\]/, "expiry line must be small (10px)");
+ assert.match(source, /text-sky-500/, "active expiry must be blue (sky-500)");
+ assert.match(source, /tokenExpiresIn/, "must use the tokenExpiresIn i18n key");
+ assert.match(source, /tokenExpired/, "must use the tokenExpired i18n key");
+});
+
+test("token expiry i18n keys exist in en and pt-BR", () => {
+ for (const locale of ["en", "pt-BR"]) {
+ const msgs = JSON.parse(
+ fs.readFileSync(path.join(repoRoot, `src/i18n/messages/${locale}.json`), "utf8")
+ );
+ assert.ok(msgs.usage?.tokenExpiresIn, `${locale}: usage.tokenExpiresIn must exist`);
+ assert.ok(msgs.usage?.tokenExpired, `${locale}: usage.tokenExpired must exist`);
+ assert.match(
+ msgs.usage.tokenExpiresIn,
+ /\{time\}/,
+ `${locale}: tokenExpiresIn must interpolate {time}`
+ );
+ }
+});