77 GiftIcon ,
88 LightningIcon ,
99 UsersIcon ,
10+ WarningIcon ,
1011} from "@phosphor-icons/react" ;
1112import Link from "next/link" ;
1213import { memo } from "react" ;
@@ -18,6 +19,16 @@ import {
1819 getResetText ,
1920} from "../utils/feature-usage" ;
2021
22+ function formatCurrency ( amount : number ) : string {
23+ if ( amount >= 1000 ) {
24+ return `$${ ( amount / 1000 ) . toFixed ( 1 ) } K` ;
25+ }
26+ if ( amount >= 1 ) {
27+ return `$${ amount . toFixed ( 2 ) } ` ;
28+ }
29+ return `$${ amount . toFixed ( 4 ) } ` ;
30+ }
31+
2132const FEATURE_ICONS : Record < string , typeof ChartBarIcon > = {
2233 event : ChartBarIcon ,
2334 storage : DatabaseIcon ,
@@ -37,28 +48,19 @@ function getFeatureIcon(name: string): typeof ChartBarIcon {
3748 return ChartBarIcon ;
3849}
3950
40- type UsageRowProps = {
41- feature : FeatureUsage ;
42- } ;
43-
4451export const UsageRow = memo ( function UsageRowComponent ( {
4552 feature,
46- } : UsageRowProps ) {
47- const percentage = feature . unlimited
48- ? 0
49- : feature . limit > 0
50- ? Math . min ( ( feature . used / feature . limit ) * 100 , 100 )
51- : 0 ;
52-
53- const isNearLimit =
54- ! ( feature . unlimited || feature . hasExtraCredits ) &&
55- ( percentage > 80 || feature . balance < feature . limit * 0.2 ) ;
56-
57- const isOverLimit =
58- ! feature . unlimited && ( percentage >= 100 || feature . balance <= 0 ) ;
53+ } : {
54+ feature : FeatureUsage ;
55+ } ) {
56+ const remainingPercent = feature . unlimited
57+ ? 100
58+ : Math . min ( Math . max ( ( feature . balance / feature . limit ) * 100 , 0 ) , 100 ) ;
59+ const hasNormalLimit = ! ( feature . unlimited || feature . hasExtraCredits ) ;
60+ const isLow = hasNormalLimit && remainingPercent < 20 ;
61+ const hasOverage = feature . overage !== null ;
5962
6063 const Icon = getFeatureIcon ( feature . name ) ;
61- const resetText = getResetText ( feature ) ;
6264
6365 return (
6466 < div className = "px-5 py-4" >
@@ -83,53 +85,76 @@ export const UsageRow = memo(function UsageRowComponent({
8385 Bonus
8486 </ Badge >
8587 ) }
88+ { hasOverage && (
89+ < Badge
90+ className = "bg-destructive/10 text-destructive"
91+ variant = "secondary"
92+ >
93+ < WarningIcon className = "mr-1" size = { 10 } weight = "fill" />
94+ Overage
95+ </ Badge >
96+ ) }
8697 </ div >
8798 < div className = "flex items-center gap-1 text-muted-foreground text-sm" >
8899 < ClockIcon size = { 12 } />
89- { resetText }
100+ { getResetText ( feature ) }
90101 </ div >
91102 </ div >
92103 </ div >
104+
93105 { feature . unlimited ? (
94106 < Badge variant = "secondary" >
95107 < LightningIcon className = "mr-1" size = { 12 } />
96108 Unlimited
97109 </ Badge >
110+ ) : feature . hasExtraCredits ? (
111+ < div className = "text-right" >
112+ < span className = "font-mono text-base" >
113+ { formatCompactNumber ( feature . balance ) }
114+ </ span >
115+ < div className = "text-muted-foreground text-xs" > remaining</ div >
116+ </ div >
117+ ) : feature . overage ? (
118+ < div className = "text-right" >
119+ < span className = "font-mono text-base text-destructive" >
120+ +{ formatCompactNumber ( feature . overage . amount ) } over
121+ </ span >
122+ < div className = "text-destructive text-xs" >
123+ ~{ formatCurrency ( feature . overage . cost ) } overage
124+ </ div >
125+ </ div >
98126 ) : (
99- < span
100- className = { cn (
101- "font-mono text-base" ,
102- isOverLimit
103- ? "text-destructive"
104- : isNearLimit
105- ? "text-warning"
106- : "text-foreground"
107- ) }
108- >
109- { formatCompactNumber ( feature . used ) } /{ " " }
110- { formatCompactNumber ( feature . limit ) }
111- </ span >
127+ < div className = "text-right" >
128+ < span
129+ className = { cn (
130+ "font-mono text-base" ,
131+ isLow ? "text-warning" : "text-foreground"
132+ ) }
133+ >
134+ { formatCompactNumber ( feature . balance ) } /{ " " }
135+ { formatCompactNumber ( feature . limit ) }
136+ </ span >
137+ < div className = "text-muted-foreground text-xs" > remaining</ div >
138+ </ div >
112139 ) }
113140 </ div >
114141
115- { ! feature . unlimited && (
142+ { hasNormalLimit && (
116143 < div className = "flex items-center gap-3" >
117144 < div className = "h-2 flex-1 overflow-hidden rounded-full bg-muted" >
118145 < div
119146 className = { cn (
120147 "h-full transition-all" ,
121- feature . hasExtraCredits
122- ? "bg-primary"
123- : isOverLimit
124- ? "bg-destructive"
125- : isNearLimit
126- ? "bg-warning"
127- : "bg-primary"
148+ hasOverage
149+ ? "bg-destructive"
150+ : isLow
151+ ? "bg-warning"
152+ : "bg-primary"
128153 ) }
129- style = { { width : `${ percentage } %` } }
154+ style = { { width : hasOverage ? "100%" : `${ remainingPercent } %` } }
130155 />
131156 </ div >
132- { isNearLimit && (
157+ { ( isLow || hasOverage ) && (
133158 < Link
134159 className = "shrink-0 font-medium text-primary text-sm hover:underline"
135160 href = "/billing/plans"
0 commit comments