diff --git a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/QuotaCard.tsx b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/QuotaCard.tsx index af76b84a773c..e50ec13be131 100644 --- a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/QuotaCard.tsx +++ b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/QuotaCard.tsx @@ -16,7 +16,9 @@ import { computeCanEditCutoff, computeCanRedeemResetCredit, hasQuotaCutoffOverrides, + getSoonestResetInfo, type CardStatus, + type SoonestResetInfo, } from "./utils"; import QuotaCardHeader from "./parts/QuotaCardHeader"; import QuotaCardExpanded from "./parts/QuotaCardExpanded"; @@ -92,6 +94,12 @@ export default function QuotaCard({ [connection.provider, rawQuotas, quotaVisibility] ); const cardStatus = useMemo(() => worstStatus(quotas), [quotas]); + // Soonest future reset across the card's rows — surfaced as a header chip so + // expiration is scannable across a whole grid of cards without reading rows. + const soonestReset = useMemo( + () => getSoonestResetInfo(quotas), + [quotas] + ); const tierMeta = useMemo( () => normalizePlanTier( @@ -140,6 +148,7 @@ export default function QuotaCard({ connection={connection} providerLabel={providerLabel} cardStatus={cardStatus} + soonestReset={soonestReset} tierMeta={tierMeta} resolvedPlan={resolvedPlan} emailsVisible={emailsVisible} diff --git a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/QuotaCardGrid.tsx b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/QuotaCardGrid.tsx index 5649e9ecb75f..d1057798b201 100644 --- a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/QuotaCardGrid.tsx +++ b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/QuotaCardGrid.tsx @@ -193,7 +193,7 @@ function ProviderQuotaSection({
-
+
{connections.map((conn) => ( @@ -310,8 +325,12 @@ function QuotaDetailRow({ {q.staleAfterReset ? ( ⟳ ) : cd ? ( - - ⏱ {t("resetsIn")} {cd} + + ⏱ {cd} + + ) : !q.unlimited ? ( + + — ) : null}
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 a5bd4ada00db..c8f4e70d2851 100644 --- a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/parts/QuotaCardHeader.tsx +++ b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/parts/QuotaCardHeader.tsx @@ -5,7 +5,7 @@ import Badge from "@/shared/components/Badge"; import ProviderIcon from "@/shared/components/ProviderIcon"; import { pickDisplayValue } from "@/shared/utils/maskEmail"; import { readCookieExpiresAt } from "@/shared/utils/webCookieExpiry"; -import { formatCountdown, type CardStatus } from "../utils"; +import { formatCountdown, type CardStatus, type SoonestResetInfo } from "../utils"; import { translateUsageOrFallback } from "../i18nFallback"; const STATUS_DOT_CLASS: Record = { @@ -19,6 +19,8 @@ interface Props { connection: any; providerLabel: string; cardStatus: CardStatus; + /** Soonest future quota reset across the card's rows (null when unknown). */ + soonestReset: SoonestResetInfo | null; tierMeta: { key: string; label: string; variant: any }; resolvedPlan: string | null; emailsVisible: boolean; @@ -29,10 +31,17 @@ interface Props { togglingActive: boolean; } +const RESET_URGENCY_CLASS: Record = { + critical: "text-rose-500", + warning: "text-amber-500", + calm: "text-text-muted", +}; + export default function QuotaCardHeader({ connection, providerLabel, cardStatus, + soonestReset, tierMeta, resolvedPlan, emailsVisible, @@ -127,7 +136,16 @@ export default function QuotaCardHeader({ )}
-
+
+ {soonestReset && ( + + schedule + {soonestReset.countdown} + + )}