@@ -3,6 +3,9 @@ import React, { useEffect } from 'react'
33import open from 'open'
44
55import { BottomBanner } from './bottom-banner'
6+ import { Button } from './button'
7+ import { ProgressBar } from './progress-bar'
8+ import { useClaudeQuotaQuery } from '../hooks/use-claude-quota-query'
69import { usageQueryKeys , useUsageQuery } from '../hooks/use-usage-query'
710import { useChatStore } from '../state/chat-store'
811import {
@@ -12,16 +15,44 @@ import {
1215} from '../utils/usage-banner-state'
1316import { WEBSITE_URL } from '../login/constants'
1417import { useTheme } from '../hooks/use-theme'
15- import { Button } from './button '
18+ import { isClaudeOAuthValid } from '@codebuff/sdk '
1619
1720const MANUAL_SHOW_TIMEOUT = 60 * 1000 // 1 minute
1821const USAGE_POLL_INTERVAL = 30 * 1000 // 30 seconds
1922
23+ /**
24+ * Format time until reset in human-readable form
25+ */
26+ const formatResetTime = ( resetDate : Date | null ) : string => {
27+ if ( ! resetDate ) return ''
28+ const now = new Date ( )
29+ const diffMs = resetDate . getTime ( ) - now . getTime ( )
30+ if ( diffMs <= 0 ) return 'now'
31+
32+ const diffMins = Math . floor ( diffMs / ( 1000 * 60 ) )
33+ const diffHours = Math . floor ( diffMins / 60 )
34+ const remainingMins = diffMins % 60
35+
36+ if ( diffHours > 0 ) {
37+ return `${ diffHours } h ${ remainingMins } m`
38+ }
39+ return `${ diffMins } m`
40+ }
41+
2042export const UsageBanner = ( { showTime } : { showTime : number } ) => {
2143 const queryClient = useQueryClient ( )
2244 const sessionCreditsUsed = useChatStore ( ( state ) => state . sessionCreditsUsed )
2345 const setInputMode = useChatStore ( ( state ) => state . setInputMode )
2446
47+ // Check if Claude OAuth is connected
48+ const isClaudeConnected = isClaudeOAuthValid ( )
49+
50+ // Fetch Claude quota data if connected
51+ const { data : claudeQuota , isLoading : isClaudeLoading } = useClaudeQuotaQuery ( {
52+ enabled : isClaudeConnected ,
53+ refetchInterval : 30 * 1000 , // Refresh every 30 seconds when banner is open
54+ } )
55+
2556 const {
2657 data : apiData ,
2758 isLoading,
@@ -91,13 +122,50 @@ export const UsageBanner = ({ showTime }: { showTime: number }) => {
91122 borderColorKey = { isLoadingData ? 'muted' : colorLevel }
92123 onClose = { ( ) => setInputMode ( 'default' ) }
93124 >
94- < Button
95- onClick = { ( ) => {
96- open ( WEBSITE_URL + '/usage' )
97- } }
98- >
99- < text style = { { fg : theme . foreground } } > { text } </ text >
100- </ Button >
125+ < box style = { { flexDirection : 'column' , gap : 0 } } >
126+ { /* Codebuff credits section */ }
127+ < Button
128+ onClick = { ( ) => {
129+ open ( WEBSITE_URL + '/usage' )
130+ } }
131+ >
132+ < text style = { { fg : theme . foreground } } > { text } </ text >
133+ </ Button >
134+
135+ { /* Claude subscription section - only show if connected */ }
136+ { isClaudeConnected && (
137+ < box style = { { flexDirection : 'column' , marginTop : 1 } } >
138+ < text style = { { fg : theme . primary } } > Claude subscription</ text >
139+ { isClaudeLoading ? (
140+ < text style = { { fg : theme . muted } } > Loading quota...</ text >
141+ ) : claudeQuota ? (
142+ < box style = { { flexDirection : 'column' , gap : 0 } } >
143+ < box style = { { flexDirection : 'row' , alignItems : 'center' , gap : 1 } } >
144+ < text style = { { fg : theme . muted } } > 5-hour:</ text >
145+ < ProgressBar value = { claudeQuota . fiveHourRemaining } width = { 15 } />
146+ { claudeQuota . fiveHourResetsAt && (
147+ < text style = { { fg : theme . muted } } >
148+ (resets in { formatResetTime ( claudeQuota . fiveHourResetsAt ) } )
149+ </ text >
150+ ) }
151+ </ box >
152+ { /* Only show 7-day bar if the user has a 7-day limit */ }
153+ { claudeQuota . sevenDayResetsAt && (
154+ < box style = { { flexDirection : 'row' , alignItems : 'center' , gap : 1 } } >
155+ < text style = { { fg : theme . muted } } > 7-day: </ text >
156+ < ProgressBar value = { claudeQuota . sevenDayRemaining } width = { 15 } />
157+ < text style = { { fg : theme . muted } } >
158+ (resets in { formatResetTime ( claudeQuota . sevenDayResetsAt ) } )
159+ </ text >
160+ </ box >
161+ ) }
162+ </ box >
163+ ) : (
164+ < text style = { { fg : theme . muted } } > Unable to fetch quota</ text >
165+ ) }
166+ </ box >
167+ ) }
168+ </ box >
101169 </ BottomBanner >
102170 )
103171}
0 commit comments