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'
56import { createLogger } from '@/lib/logs/console/logger'
6- import { cn } from '@/lib/utils '
7+ import { MIN_SIDEBAR_WIDTH , useSidebarStore } from '@/stores/sidebar/store '
78import { 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-
1710const 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+ */
2030const PLAN_NAMES = {
2131 enterprise : 'Enterprise' ,
2232 team : 'Team' ,
@@ -30,26 +40,40 @@ interface UsageIndicatorProps {
3040
3141export 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 )
0 commit comments