Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 35 additions & 13 deletions src/platform/cloud/subscription/components/CreditsTile.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,37 +194,59 @@ 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',
duration: 'ANNUAL',
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', () => {
Expand Down
34 changes: 19 additions & 15 deletions src/platform/cloud/subscription/components/CreditsTile.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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"
>
Expand Down Expand Up @@ -86,15 +86,15 @@
{{
$t('subscription.creditsLeftOfTotal', {
remaining: monthlyBonusCredits,
total: monthlyTotalDisplay
total: creditPoolTotalDisplay
})
}}
</span>
<span class="hidden @max-[180px]:inline">
{{
$t('subscription.creditsLeftOfTotal', {
remaining: monthlyRemainingCompact,
total: monthlyTotalCompact
total: creditPoolTotalCompact
})
}}
</span>
Expand Down Expand Up @@ -233,16 +233,20 @@ const tierKey = computed(() => {
return TIER_TO_KEY[tier] ?? DEFAULT_TIER_KEY
})

const monthlyTotalCredits = computed<number | null>(() => {
const teamStop = currentTeamCreditStop.value
if (teamStop) return teamStop.credits_monthly
return getTierCredits(tierKey.value)
const creditPoolTotalCredits = computed<number | null>(() => {
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
)
)

Expand Down Expand Up @@ -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)
})

Expand All @@ -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)
})

Expand All @@ -296,16 +300,16 @@ const usedBarWidth = computed(
const monthlyUsageLabel = computed(() =>
t('subscription.monthlyUsageProgress', {
used: usedDisplay.value,
total: monthlyTotalDisplay.value
total: creditPoolTotalDisplay.value
})
)

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
Expand Down
Loading