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
Original file line number Diff line number Diff line change
@@ -1,22 +1,32 @@
'use client'

import { useEffect } from 'react'
import { Badge, Progress, Skeleton } from '@/components/ui'
import { useEffect, useMemo } from 'react'
import { Button } from '@/components/emcn'
import { Skeleton } from '@/components/ui'
import { createLogger } from '@/lib/logs/console/logger'
import { cn } from '@/lib/utils'
import { MIN_SIDEBAR_WIDTH, useSidebarStore } from '@/stores/sidebar/store'
import { useSubscriptionStore } from '@/stores/subscription/store'

// Constants for reusable styles
const GRADIENT_BADGE_STYLES =
'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'
const GRADIENT_TEXT_STYLES =
'gradient-text bg-gradient-to-b from-gradient-primary via-gradient-secondary to-gradient-primary'
const CONTAINER_STYLES =
'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'

const logger = createLogger('UsageIndicator')

// Plan name mapping
/**
* Minimum number of pills to display (at minimum sidebar width)
*/
const MIN_PILL_COUNT = 6

/**
* Maximum number of pills to display
*/
const MAX_PILL_COUNT = 8

/**
* Width increase (in pixels) required to add one additional pill
*/
const WIDTH_PER_PILL = 50

/**
* Plan name mapping
*/
const PLAN_NAMES = {
enterprise: 'Enterprise',
team: 'Team',
Expand All @@ -30,26 +40,40 @@ interface UsageIndicatorProps {

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

useEffect(() => {
useSubscriptionStore.getState().loadData()
}, [])

/**
* Calculate pill count based on sidebar width
* Starts at MIN_PILL_COUNT at minimum width, adds 1 pill per WIDTH_PER_PILL increase
*/
const pillCount = useMemo(() => {
const widthDelta = sidebarWidth - MIN_SIDEBAR_WIDTH
const additionalPills = Math.floor(widthDelta / WIDTH_PER_PILL)
const calculatedCount = MIN_PILL_COUNT + additionalPills
return Math.max(MIN_PILL_COUNT, Math.min(MAX_PILL_COUNT, calculatedCount))
}, [sidebarWidth])

const usage = getUsage()
const subscription = getSubscriptionStatus()

if (isLoading) {
return (
<div className={CONTAINER_STYLES} onClick={() => onClick?.()}>
<div className='space-y-2'>
{/* Plan and usage info skeleton */}
<div className='flex items-center justify-between'>
<Skeleton className='h-5 w-12' />
<Skeleton className='h-4 w-20' />
</div>
<div className='flex flex-shrink-0 flex-col gap-[10px] border-t px-[13.5px] pt-[10px] pb-[8px] dark:border-[var(--border)]'>
{/* Top row skeleton */}
<div className='flex items-center justify-between'>
<Skeleton className='h-[16px] w-[120px] rounded-[4px]' />
<Skeleton className='h-[16px] w-[50px] rounded-[4px]' />
</div>

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

const billingStatus = useSubscriptionStore.getState().getBillingStatus()
const isBlocked = billingStatus === 'blocked'
const badgeText = isBlocked ? 'Payment Failed' : planType === 'free' ? 'Upgrade' : undefined
const showUpgradeButton = planType === 'free' || isBlocked

/**
* Calculate which pills should be filled based on usage percentage
*/
const filledPillsCount = Math.ceil((progressPercentage / 100) * pillCount)
const isAlmostOut = filledPillsCount === pillCount

const handleClick = () => {
try {
Expand All @@ -91,32 +121,56 @@ export function UsageIndicator({ onClick }: UsageIndicatorProps) {
}

return (
<div className={CONTAINER_STYLES} onClick={handleClick}>
<div className='space-y-2'>
{/* Plan and usage info */}
<div className='flex items-center justify-between'>
<div className='flex items-center gap-2'>
<span
className={cn(
'font-medium text-sm',
planType === 'free' ? 'text-foreground' : GRADIENT_TEXT_STYLES
)}
>
{PLAN_NAMES[planType]}
</span>
{badgeText ? <Badge className={GRADIENT_BADGE_STYLES}>{badgeText}</Badge> : null}
<div className='flex flex-shrink-0 flex-col gap-[10px] border-t px-[13.5px] pt-[8px] pb-[8px] dark:border-[var(--border)]'>
{/* Top row */}
<div className='flex items-center justify-between'>
<div className='flex items-center gap-[6px]'>
<span className='font-medium text-[#FFFFFF] text-[12px]'>{PLAN_NAMES[planType]}</span>
<div className='h-[14px] w-[1.5px] bg-[#4A4A4A]' />
<div className='flex items-center gap-[4px]'>
{isBlocked ? (
<>
<span className='font-medium text-[#B1B1B1] text-[12px]'>Over</span>
<span className='font-medium text-[#B1B1B1] text-[12px]'>limit</span>
</>
) : (
<>
<span className='font-medium text-[#B1B1B1] text-[12px] tabular-nums'>
${usage.current.toFixed(2)}
</span>
<span className='font-medium text-[#B1B1B1] text-[12px]'>/</span>
<span className='font-medium text-[#B1B1B1] text-[12px] tabular-nums'>
${usage.limit}
</span>
</>
)}
</div>
<span className='text-muted-foreground text-xs tabular-nums'>
{isBlocked ? 'Payment required' : `$${usage.current.toFixed(2)} / $${usage.limit}`}
</span>
</div>
{showUpgradeButton && (
<Button
variant='ghost'
className='!h-auto !px-1 !py-0 -mx-1 mt-[-2px] text-[#D4D4D4]'
onClick={handleClick}
>
Upgrade
</Button>
)}
</div>

{/* Progress Bar */}
<Progress
value={isBlocked ? 100 : progressPercentage}
className='h-2'
indicatorClassName='bg-black dark:bg-white'
/>
{/* Pills row */}
<div className='flex items-center gap-[4px]'>
{Array.from({ length: pillCount }).map((_, i) => {
const isFilled = i < filledPillsCount
return (
<div
key={i}
className='h-[6px] flex-1 rounded-[2px]'
style={{
backgroundColor: isFilled ? (isAlmostOut ? '#ef4444' : '#34B5FF') : '#414141',
}}
Comment on lines +168 to +170
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: hardcoded hex colors violate style guidelines - use CSS variables or Tailwind classes

Suggested change
style={{
backgroundColor: isFilled ? (isAlmostOut ? '#ef4444' : '#34B5FF') : '#414141',
}}
className={`h-[6px] flex-1 rounded-[2px] ${
isFilled
? isAlmostOut
? 'bg-red-500'
: 'bg-[var(--color-primary)]'
: 'bg-[#414141]'
}`}

Context Used: Context from dashboard - apps/sim/.cursorrules (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/usage-indicator/usage-indicator.tsx
Line: 168:170

Comment:
**style:** hardcoded hex colors violate style guidelines - use CSS variables or Tailwind classes

```suggestion
              className={`h-[6px] flex-1 rounded-[2px] ${
                isFilled
                  ? isAlmostOut
                    ? 'bg-red-500'
                    : 'bg-[var(--color-primary)]'
                  : 'bg-[#414141]'
              }`}
```

**Context Used:** Context from `dashboard` - apps/sim/.cursorrules ([source](https://app.greptile.com/review/custom-context?memory=867b3993-a968-40e2-b2e0-8ce533acea2e))

How can I resolve this? If you propose a fix, please make it concise.

/>
)
})}
</div>
</div>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ const logger = createLogger('SidebarNew')

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

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

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

{/* Footer Navigation */}
<FooterNavigation />
Expand Down