Skip to content

Commit 36bcd75

Browse files
authored
improvement: usage-indicator UI (#1948)
1 parent 9db969b commit 36bcd75

File tree

2 files changed

+100
-51
lines changed

2 files changed

+100
-51
lines changed

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/usage-indicator/usage-indicator.tsx

Lines changed: 99 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,32 @@
11
'use client'
22

3-
import { useEffect } from 'react'
4-
import { Badge, Progress, Skeleton } from '@/components/ui'
3+
import { useEffect, useMemo } from 'react'
4+
import { Button } from '@/components/emcn'
5+
import { Skeleton } from '@/components/ui'
56
import { createLogger } from '@/lib/logs/console/logger'
6-
import { cn } from '@/lib/utils'
7+
import { MIN_SIDEBAR_WIDTH, useSidebarStore } from '@/stores/sidebar/store'
78
import { useSubscriptionStore } from '@/stores/subscription/store'
89

9-
// Constants for reusable styles
10-
const GRADIENT_BADGE_STYLES =
11-
'gradient-text h-[1.125rem] rounded-[6px] border-gradient-primary/20 bg-gradient-to-b from-gradient-primary via-gradient-secondary to-gradient-primary px-2 py-0 font-medium text-xs'
12-
const GRADIENT_TEXT_STYLES =
13-
'gradient-text bg-gradient-to-b from-gradient-primary via-gradient-secondary to-gradient-primary'
14-
const CONTAINER_STYLES =
15-
'pointer-events-auto flex-shrink-0 rounded-[10px] border bg-background px-3 py-2.5 shadow-xs cursor-pointer transition-colors hover:bg-muted/50'
16-
1710
const logger = createLogger('UsageIndicator')
1811

19-
// Plan name mapping
12+
/**
13+
* Minimum number of pills to display (at minimum sidebar width)
14+
*/
15+
const MIN_PILL_COUNT = 6
16+
17+
/**
18+
* Maximum number of pills to display
19+
*/
20+
const MAX_PILL_COUNT = 8
21+
22+
/**
23+
* Width increase (in pixels) required to add one additional pill
24+
*/
25+
const WIDTH_PER_PILL = 50
26+
27+
/**
28+
* Plan name mapping
29+
*/
2030
const PLAN_NAMES = {
2131
enterprise: 'Enterprise',
2232
team: 'Team',
@@ -30,26 +40,40 @@ interface UsageIndicatorProps {
3040

3141
export function UsageIndicator({ onClick }: UsageIndicatorProps) {
3242
const { getUsage, getSubscriptionStatus, isLoading } = useSubscriptionStore()
43+
const sidebarWidth = useSidebarStore((state) => state.sidebarWidth)
3344

3445
useEffect(() => {
3546
useSubscriptionStore.getState().loadData()
3647
}, [])
3748

49+
/**
50+
* Calculate pill count based on sidebar width
51+
* Starts at MIN_PILL_COUNT at minimum width, adds 1 pill per WIDTH_PER_PILL increase
52+
*/
53+
const pillCount = useMemo(() => {
54+
const widthDelta = sidebarWidth - MIN_SIDEBAR_WIDTH
55+
const additionalPills = Math.floor(widthDelta / WIDTH_PER_PILL)
56+
const calculatedCount = MIN_PILL_COUNT + additionalPills
57+
return Math.max(MIN_PILL_COUNT, Math.min(MAX_PILL_COUNT, calculatedCount))
58+
}, [sidebarWidth])
59+
3860
const usage = getUsage()
3961
const subscription = getSubscriptionStatus()
4062

4163
if (isLoading) {
4264
return (
43-
<div className={CONTAINER_STYLES} onClick={() => onClick?.()}>
44-
<div className='space-y-2'>
45-
{/* Plan and usage info skeleton */}
46-
<div className='flex items-center justify-between'>
47-
<Skeleton className='h-5 w-12' />
48-
<Skeleton className='h-4 w-20' />
49-
</div>
65+
<div className='flex flex-shrink-0 flex-col gap-[10px] border-t px-[13.5px] pt-[10px] pb-[8px] dark:border-[var(--border)]'>
66+
{/* Top row skeleton */}
67+
<div className='flex items-center justify-between'>
68+
<Skeleton className='h-[16px] w-[120px] rounded-[4px]' />
69+
<Skeleton className='h-[16px] w-[50px] rounded-[4px]' />
70+
</div>
5071

51-
{/* Progress Bar skeleton */}
52-
<Skeleton className='h-2 w-full' />
72+
{/* Pills skeleton */}
73+
<div className='flex items-center gap-[4px]'>
74+
{Array.from({ length: pillCount }).map((_, i) => (
75+
<Skeleton key={i} className='h-[6px] flex-1 rounded-[2px]' />
76+
))}
5377
</div>
5478
</div>
5579
)
@@ -67,7 +91,13 @@ export function UsageIndicator({ onClick }: UsageIndicatorProps) {
6791

6892
const billingStatus = useSubscriptionStore.getState().getBillingStatus()
6993
const isBlocked = billingStatus === 'blocked'
70-
const badgeText = isBlocked ? 'Payment Failed' : planType === 'free' ? 'Upgrade' : undefined
94+
const showUpgradeButton = planType === 'free' || isBlocked
95+
96+
/**
97+
* Calculate which pills should be filled based on usage percentage
98+
*/
99+
const filledPillsCount = Math.ceil((progressPercentage / 100) * pillCount)
100+
const isAlmostOut = filledPillsCount === pillCount
71101

72102
const handleClick = () => {
73103
try {
@@ -91,32 +121,56 @@ export function UsageIndicator({ onClick }: UsageIndicatorProps) {
91121
}
92122

93123
return (
94-
<div className={CONTAINER_STYLES} onClick={handleClick}>
95-
<div className='space-y-2'>
96-
{/* Plan and usage info */}
97-
<div className='flex items-center justify-between'>
98-
<div className='flex items-center gap-2'>
99-
<span
100-
className={cn(
101-
'font-medium text-sm',
102-
planType === 'free' ? 'text-foreground' : GRADIENT_TEXT_STYLES
103-
)}
104-
>
105-
{PLAN_NAMES[planType]}
106-
</span>
107-
{badgeText ? <Badge className={GRADIENT_BADGE_STYLES}>{badgeText}</Badge> : null}
124+
<div className='flex flex-shrink-0 flex-col gap-[10px] border-t px-[13.5px] pt-[8px] pb-[8px] dark:border-[var(--border)]'>
125+
{/* Top row */}
126+
<div className='flex items-center justify-between'>
127+
<div className='flex items-center gap-[6px]'>
128+
<span className='font-medium text-[#FFFFFF] text-[12px]'>{PLAN_NAMES[planType]}</span>
129+
<div className='h-[14px] w-[1.5px] bg-[#4A4A4A]' />
130+
<div className='flex items-center gap-[4px]'>
131+
{isBlocked ? (
132+
<>
133+
<span className='font-medium text-[#B1B1B1] text-[12px]'>Over</span>
134+
<span className='font-medium text-[#B1B1B1] text-[12px]'>limit</span>
135+
</>
136+
) : (
137+
<>
138+
<span className='font-medium text-[#B1B1B1] text-[12px] tabular-nums'>
139+
${usage.current.toFixed(2)}
140+
</span>
141+
<span className='font-medium text-[#B1B1B1] text-[12px]'>/</span>
142+
<span className='font-medium text-[#B1B1B1] text-[12px] tabular-nums'>
143+
${usage.limit}
144+
</span>
145+
</>
146+
)}
108147
</div>
109-
<span className='text-muted-foreground text-xs tabular-nums'>
110-
{isBlocked ? 'Payment required' : `$${usage.current.toFixed(2)} / $${usage.limit}`}
111-
</span>
112148
</div>
149+
{showUpgradeButton && (
150+
<Button
151+
variant='ghost'
152+
className='!h-auto !px-1 !py-0 -mx-1 mt-[-2px] text-[#D4D4D4]'
153+
onClick={handleClick}
154+
>
155+
Upgrade
156+
</Button>
157+
)}
158+
</div>
113159

114-
{/* Progress Bar */}
115-
<Progress
116-
value={isBlocked ? 100 : progressPercentage}
117-
className='h-2'
118-
indicatorClassName='bg-black dark:bg-white'
119-
/>
160+
{/* Pills row */}
161+
<div className='flex items-center gap-[4px]'>
162+
{Array.from({ length: pillCount }).map((_, i) => {
163+
const isFilled = i < filledPillsCount
164+
return (
165+
<div
166+
key={i}
167+
className='h-[6px] flex-1 rounded-[2px]'
168+
style={{
169+
backgroundColor: isFilled ? (isAlmostOut ? '#ef4444' : '#34B5FF') : '#414141',
170+
}}
171+
/>
172+
)
173+
})}
120174
</div>
121175
</div>
122176
)

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/sidebar-new.tsx

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ const logger = createLogger('SidebarNew')
3333

3434
// Feature flag: Billing usage indicator visibility (matches legacy sidebar behavior)
3535
const isBillingEnabled = isTruthy(getEnv('NEXT_PUBLIC_BILLING_ENABLED'))
36-
// const isBillingEnabled = true
3736

3837
/**
3938
* Sidebar component with resizable width that persists across page refreshes.
@@ -610,11 +609,7 @@ export function SidebarNew() {
610609
</div>
611610

612611
{/* Usage Indicator */}
613-
{isBillingEnabled && (
614-
<div className='flex flex-shrink-0 flex-col gap-[2px] border-t px-[7.75px] pt-[8px] pb-[8px] dark:border-[var(--border)]'>
615-
<UsageIndicator />
616-
</div>
617-
)}
612+
{isBillingEnabled && <UsageIndicator />}
618613

619614
{/* Footer Navigation */}
620615
<FooterNavigation />

0 commit comments

Comments
 (0)