Skip to content

Commit 9355fa5

Browse files
committed
minor ux improvement
1 parent c360daa commit 9355fa5

3 files changed

Lines changed: 84 additions & 6 deletions

File tree

apps/sim/app/workspace/[workspaceId]/settings/components/billing/billing.tsx

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
cn,
1010
Switch,
1111
toast,
12+
Tooltip,
1213
} from '@sim/emcn'
1314
import { createLogger } from '@sim/logger'
1415
import { isOrgAdminRole } from '@sim/platform-authz/predicates'
@@ -18,7 +19,12 @@ import { useParams, useRouter } from 'next/navigation'
1819
import { useSession, useSubscription } from '@/lib/auth/auth-client'
1920
import { ON_DEMAND_UNLIMITED } from '@/lib/billing/constants'
2021
import { CREDIT_MULTIPLIER } from '@/lib/billing/credits/conversion'
21-
import { getCoveredUsage, getIsOnDemandActive, getOnDemandOffLimit } from '@/lib/billing/on-demand'
22+
import {
23+
getCoveredUsage,
24+
getIsOnDemandActive,
25+
getOnDemandOffLimit,
26+
isOnDemandOffDisabled,
27+
} from '@/lib/billing/on-demand'
2228
import {
2329
getDisplayPlanName,
2430
getPlanTierCredits,
@@ -223,6 +229,19 @@ export function Billing() {
223229
covered,
224230
})
225231

232+
/**
233+
* When usage already sits above `covered`, turning on-demand off would re-cap
234+
* the limit at current usage and the switch would bounce straight back on
235+
* (see `getOnDemandOffLimit`). Disable it and explain why via tooltip instead
236+
* of accepting a no-op click; it re-enables once usage drops back to/below
237+
* covered (e.g. the next billing reset).
238+
*/
239+
const onDemandLockedOn = isOnDemandOffDisabled({
240+
isOnDemandActive,
241+
effectiveCurrentUsage,
242+
covered,
243+
})
244+
226245
const permissions = getSubscriptionPermissions(
227246
{
228247
isFree: subscription.isFree,
@@ -452,11 +471,28 @@ export function Billing() {
452471
<span className='text-[var(--text-body)] text-small'>
453472
Allow usage to go past included usage
454473
</span>
455-
<Switch
456-
checked={isOnDemandActive}
457-
disabled={isTogglingOnDemand || !canManageBilling}
458-
onCheckedChange={handleToggleOnDemand}
459-
/>
474+
{onDemandLockedOn ? (
475+
<Tooltip.Root>
476+
<Tooltip.Trigger asChild>
477+
<span className='inline-flex'>
478+
<Switch checked disabled onCheckedChange={handleToggleOnDemand} />
479+
</span>
480+
</Tooltip.Trigger>
481+
<Tooltip.Content className='max-w-[260px]'>
482+
<p>
483+
{
484+
"Your usage is above your plan's included amount, so on-demand can't be turned off yet. It turns off once usage drops below it — at the latest when your billing period resets."
485+
}
486+
</p>
487+
</Tooltip.Content>
488+
</Tooltip.Root>
489+
) : (
490+
<Switch
491+
checked={isOnDemandActive}
492+
disabled={isTogglingOnDemand || !canManageBilling}
493+
onCheckedChange={handleToggleOnDemand}
494+
/>
495+
)}
460496
</div>
461497
</SettingsSection>
462498
)}

apps/sim/lib/billing/on-demand.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
getIsOnDemandActive,
1010
getOnDemandOffLimit,
1111
getPooledCreditsRemaining,
12+
isOnDemandOffDisabled,
1213
} from '@/lib/billing/on-demand'
1314

1415
describe('getPooledCreditsRemaining', () => {
@@ -130,3 +131,27 @@ describe('getOnDemandOffLimit', () => {
130131
expect(getOnDemandOffLimit(120, 120)).toBe(120)
131132
})
132133
})
134+
135+
describe('isOnDemandOffDisabled', () => {
136+
it('disables the toggle when on-demand is on and usage is above covered', () => {
137+
// Turning off here would re-cap at usage (150) and bounce back on, so lock it.
138+
expect(
139+
isOnDemandOffDisabled({ isOnDemandActive: true, effectiveCurrentUsage: 150, covered: 120 })
140+
).toBe(true)
141+
})
142+
143+
it('allows turning off when usage is at or below covered', () => {
144+
expect(
145+
isOnDemandOffDisabled({ isOnDemandActive: true, effectiveCurrentUsage: 120, covered: 120 })
146+
).toBe(false)
147+
expect(
148+
isOnDemandOffDisabled({ isOnDemandActive: true, effectiveCurrentUsage: 62, covered: 120 })
149+
).toBe(false)
150+
})
151+
152+
it('never disables when on-demand is already off (turning on stays allowed)', () => {
153+
expect(
154+
isOnDemandOffDisabled({ isOnDemandActive: false, effectiveCurrentUsage: 150, covered: 120 })
155+
).toBe(false)
156+
})
157+
})

apps/sim/lib/billing/on-demand.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,20 @@ export function getIsOnDemandActive(params: {
6363
export function getOnDemandOffLimit(currentUsage: number, covered: number): number {
6464
return Math.max(currentUsage, covered)
6565
}
66+
67+
/**
68+
* Whether the on-demand toggle should render disabled: it is on, but usage has
69+
* already passed the covered ceiling, so turning it off would only re-cap the
70+
* limit at current usage ({@link getOnDemandOffLimit}) and the control would
71+
* spring straight back on. The UI disables it with an explanatory tooltip rather
72+
* than accepting a no-op click. The state clears on its own once usage drops back
73+
* to or below covered (e.g. at the next billing reset).
74+
*/
75+
export function isOnDemandOffDisabled(params: {
76+
isOnDemandActive: boolean
77+
effectiveCurrentUsage: number
78+
covered: number
79+
}): boolean {
80+
const { isOnDemandActive, effectiveCurrentUsage, covered } = params
81+
return isOnDemandActive && effectiveCurrentUsage > covered
82+
}

0 commit comments

Comments
 (0)