diff --git a/src/platform/cloud/subscription/components/CreditsTile.test.ts b/src/platform/cloud/subscription/components/CreditsTile.test.ts index 2ca5ff4c499..3670771950e 100644 --- a/src/platform/cloud/subscription/components/CreditsTile.test.ts +++ b/src/platform/cloud/subscription/components/CreditsTile.test.ts @@ -194,7 +194,7 @@ describe('CreditsTile', () => { expect(container.textContent).toContain('422 left of 21K') }) - it('uses the team credit stop monthly grant for the monthly total', () => { + it('uses the full annual Team grant for the credit pool total', () => { state.isActiveSubscription = true state.subscription = { tier: 'TEAM', @@ -202,29 +202,51 @@ describe('CreditsTile', () => { renewalDate: '2026-02-20T12:00:00Z' } state.currentTeamCreditStop = { - id: 'team_2500', - credits_monthly: 527500, - stop_usd: 2500 + id: 'team_700', + credits_monthly: 147700, + stop_usd: 700 + } + state.balance = { + amountMicros: 840000, + cloudCreditBalanceMicros: 840000 + } + const { container } = renderTile() + expect(container.textContent).toContain('1,772,400 left of 1,772,400') + }) + + it('keeps the monthly Team grant as the monthly credit pool total', () => { + state.isActiveSubscription = true + state.subscription = { + tier: 'TEAM', + duration: 'MONTHLY', + renewalDate: '2026-02-20T12:00:00Z' + } + state.currentTeamCreditStop = { + id: 'team_700', + credits_monthly: 147700, + stop_usd: 700 + } + state.balance = { + amountMicros: 70000, + cloudCreditBalanceMicros: 70000 } - state.balance = { amountMicros: 0, cloudCreditBalanceMicros: 200 } const { container } = renderTile() - // Monthly total is the stop's raw monthly grant, not the tier fallback, - // and is not multiplied by 12 for annual billing. - expect(container.textContent).toContain('422 left of 527,500') + expect(container.textContent).toContain('147,700 left of 147,700') }) - it('uses the per-month nominal grant for an annual personal tier', () => { + it('uses the full annual grant for a personal tier credit pool', () => { state.isActiveSubscription = true state.subscription = { tier: 'PRO', duration: 'ANNUAL', renewalDate: '2026-02-20T12:00:00Z' } - state.balance = { amountMicros: 0, cloudCreditBalanceMicros: 200 } + state.balance = { + amountMicros: 120000, + cloudCreditBalanceMicros: 120000 + } const { container } = renderTile() - // Annual billing still grants the monthly nominal (21,100), not 12x. - expect(container.textContent).toContain('422 left of 21,100') - expect(container.textContent).not.toContain('253,200') + expect(container.textContent).toContain('253,200 left of 253,200') }) it('falls back to a dateless refills label when renewal date is missing', () => { diff --git a/src/platform/cloud/subscription/components/CreditsTile.vue b/src/platform/cloud/subscription/components/CreditsTile.vue index 8b9831f4b58..bb2494cbac8 100644 --- a/src/platform/cloud/subscription/components/CreditsTile.vue +++ b/src/platform/cloud/subscription/components/CreditsTile.vue @@ -57,7 +57,7 @@ role="progressbar" :aria-valuenow="usage.used" :aria-valuemin="0" - :aria-valuemax="monthlyTotalCredits ?? 0" + :aria-valuemax="creditPoolTotalCredits ?? 0" :aria-valuetext="monthlyUsageLabel" class="h-2 w-full overflow-hidden rounded-full bg-secondary-background-hover" > @@ -86,7 +86,7 @@ {{ $t('subscription.creditsLeftOfTotal', { remaining: monthlyBonusCredits, - total: monthlyTotalDisplay + total: creditPoolTotalDisplay }) }} @@ -94,7 +94,7 @@ {{ $t('subscription.creditsLeftOfTotal', { remaining: monthlyRemainingCompact, - total: monthlyTotalCompact + total: creditPoolTotalCompact }) }} @@ -233,16 +233,20 @@ const tierKey = computed(() => { return TIER_TO_KEY[tier] ?? DEFAULT_TIER_KEY }) -const monthlyTotalCredits = computed(() => { - const teamStop = currentTeamCreditStop.value - if (teamStop) return teamStop.credits_monthly - return getTierCredits(tierKey.value) +const creditPoolTotalCredits = computed(() => { + const monthlyCredits = + currentTeamCreditStop.value?.credits_monthly ?? + getTierCredits(tierKey.value) + if (monthlyCredits === null) return null + return subscription.value?.duration === 'ANNUAL' + ? monthlyCredits * 12 + : monthlyCredits }) const usage = computed(() => computeMonthlyUsage( monthlyBonusCreditsValue.value, - monthlyTotalCredits.value ?? 0 + creditPoolTotalCredits.value ?? 0 ) ) @@ -270,8 +274,8 @@ const formatCreditCount = (value: number) => numberOptions: { maximumFractionDigits: 0 } }) -const monthlyTotalDisplay = computed(() => { - const total = monthlyTotalCredits.value +const creditPoolTotalDisplay = computed(() => { + const total = creditPoolTotalCredits.value return total === null ? '—' : formatCreditCount(total) }) @@ -283,8 +287,8 @@ const compactNumber = computed( const monthlyRemainingCompact = computed(() => compactNumber.value.format(monthlyBonusCreditsValue.value) ) -const monthlyTotalCompact = computed(() => { - const total = monthlyTotalCredits.value +const creditPoolTotalCompact = computed(() => { + const total = creditPoolTotalCredits.value return total === null ? '—' : compactNumber.value.format(total) }) @@ -296,7 +300,7 @@ const usedBarWidth = computed( const monthlyUsageLabel = computed(() => t('subscription.monthlyUsageProgress', { used: usedDisplay.value, - total: monthlyTotalDisplay.value + total: creditPoolTotalDisplay.value }) ) @@ -304,8 +308,8 @@ const showBreakdown = computed(() => isActiveSubscription.value && !zeroState) const showBar = computed( () => showBreakdown.value && - monthlyTotalCredits.value !== null && - monthlyTotalCredits.value > 0 + creditPoolTotalCredits.value !== null && + creditPoolTotalCredits.value > 0 ) const showActionButton = computed( () => isActiveSubscription.value && !zeroState && permissions.value.canTopUp