@@ -3,7 +3,7 @@ import { Card, CardContent } from '@/components/ui/card'
33import { Button } from '@/components/ui/button'
44import { Popover , PopoverContent , PopoverTrigger } from '@/components/ui/popover'
55import { useAllGoals } from '@/hooks/use-goal'
6- import { Target , TrendingUp , Heart } from 'lucide-react'
6+ import { Target , TrendingUp , Heart , Star , Github } from 'lucide-react'
77import { useTranslation } from 'react-i18next'
88import { Skeleton } from '@/components/ui/skeleton'
99import { cn } from '@/lib/utils'
@@ -136,12 +136,48 @@ export function GoalProgress() {
136136 }
137137
138138 const currentGoal = pendingGoals [ currentGoalIndex ]
139- const progress = Math . min ( ( currentGoal . paid_amount / currentGoal . price ) * 100 , 100 )
140- const remaining = Math . max ( currentGoal . price - currentGoal . paid_amount , 0 )
139+ const isGithubGoal = currentGoal . type === 'github_stars'
140+ const unitLabel = isGithubGoal ? t ( 'goal.githubStarsUnit' , { defaultValue : 'stars' } ) : ''
141+ const goalTarget = currentGoal . price || 0
142+ const goalCurrent = currentGoal . paid_amount || 0
143+ const progress = Math . min ( goalTarget > 0 ? ( goalCurrent / goalTarget ) * 100 : 0 , 100 )
144+ const remaining = Math . max ( goalTarget - goalCurrent , 0 )
145+ const formattedCurrent = isGithubGoal
146+ ? `${ Math . round ( goalCurrent ) . toLocaleString ( ) } ${ unitLabel } `
147+ : `$${ goalCurrent . toLocaleString ( ) } `
148+ const formattedTarget = isGithubGoal
149+ ? `${ Math . round ( goalTarget ) . toLocaleString ( ) } ${ unitLabel } `
150+ : `$${ goalTarget . toLocaleString ( ) } `
151+ const formattedRemaining = isGithubGoal
152+ ? `${ Math . max ( Math . round ( remaining ) , 0 ) . toLocaleString ( ) } ${ unitLabel } `
153+ : `$${ remaining . toLocaleString ( ) } `
154+ const progressLabel = isGithubGoal
155+ ? t ( 'goal.githubProgress' , { defaultValue : 'Star progress' } )
156+ : t ( 'goal.progress' , { defaultValue : 'Progress' } )
157+ const remainingLabel = t ( 'goal.remaining' )
158+ const ctaLabel = isGithubGoal
159+ ? t ( 'goal.starOnGitHub' , { defaultValue : 'Star on GitHub' } )
160+ : t ( 'goal.contribute' )
161+ const ctaHref =
162+ isGithubGoal && currentGoal . repo_owner && currentGoal . repo_name
163+ ? `https://github.com/${ currentGoal . repo_owner } /${ currentGoal . repo_name } `
164+ : 'https://donate.pasarguard.org'
165+ const CtaIcon = isGithubGoal ? Star : Target
166+ const BadgeIcon = isGithubGoal ? Star : TrendingUp
167+ const badgeClasses = isGithubGoal
168+ ? 'bg-amber-500/20 text-amber-700 dark:text-amber-400'
169+ : 'bg-emerald-500/20 text-emerald-700 dark:text-emerald-400'
170+ const repoInfoAvailable = Boolean ( isGithubGoal && currentGoal . repo_owner && currentGoal . repo_name )
171+ const repoIdentifier =
172+ currentGoal . repo_owner && currentGoal . repo_name
173+ ? `${ currentGoal . repo_owner } /${ currentGoal . repo_name } `
174+ : 'owner/repo'
175+ const showRemaining = remaining > 0
141176
142177 // Collapsed state (desktop only) - simple donate button with popover
143178 // On mobile, always use expanded UI since there's no collapsed sidebar concept
144179 if ( state === 'collapsed' && ! isMobile ) {
180+ const SummaryIcon = isGithubGoal ? Star : Heart
145181 return (
146182 < div className = "mx-2 mb-2" >
147183 < Popover >
@@ -151,48 +187,52 @@ export function GoalProgress() {
151187 size = "icon"
152188 className = "h-8 w-8 rounded-md"
153189 >
154- < Heart className = "h-4 w-4 text-primary" />
190+ < SummaryIcon className = "h-4 w-4 text-primary" />
155191 </ Button >
156192 </ PopoverTrigger >
157193 < PopoverContent className = "w-80 p-4" side = "right" align = "start" >
158194 < div className = "space-y-3" >
159195 < div className = "flex items-center gap-2" >
160- < Heart className = "h-4 w-4 text-primary" />
196+ < SummaryIcon className = "h-4 w-4 text-primary" />
161197 < span className = "font-semibold text-sm" > { currentGoal . name } </ span >
162198 </ div >
163199
164200 < div className = "space-y-2" >
165201 < div className = "flex items-center justify-between text-xs" >
166- < span className = "text-muted-foreground" > Progress </ span >
202+ < span className = "text-muted-foreground" > { progressLabel } </ span >
167203 < span className = "font-medium" > { progress . toFixed ( 0 ) } %</ span >
168204 </ div >
169205 < Progress value = { progress } className = "h-2" />
170206 < div className = "flex items-center justify-between text-xs" >
171- < span className = "font-medium text-primary" > $ { currentGoal . paid_amount . toLocaleString ( ) } </ span >
207+ < span className = "font-medium text-primary" > { formattedCurrent } </ span >
172208 < span className = "text-muted-foreground" >
173- { t ( 'goal.of' ) } $ { currentGoal . price . toLocaleString ( ) }
209+ { t ( 'goal.of' ) } { formattedTarget }
174210 </ span >
175211 </ div >
176212 </ div >
177213
178- { remaining > 0 && (
179- < div className = "rounded-md bg-muted/50 px-3 py-2" >
180- < div className = "flex items-center justify-between text-xs" >
181- < span className = "text-muted-foreground" > { t ( 'goal.remaining' ) } </ span >
182- < span className = "font-semibold" > ${ remaining . toLocaleString ( ) } </ span >
183- </ div >
214+ < div className = "min-h-[32px]" >
215+ < div
216+ className = { cn (
217+ 'flex items-center justify-between rounded-md px-3 py-2 text-xs transition-opacity' ,
218+ showRemaining ? 'bg-muted/50 opacity-100' : 'opacity-0'
219+ ) }
220+ aria-hidden = { ! showRemaining }
221+ >
222+ < span className = "text-muted-foreground" > { remainingLabel } </ span >
223+ < span className = "font-semibold" > { formattedRemaining } </ span >
184224 </ div >
185- ) }
225+ </ div >
186226
187227 < Button asChild className = "w-full" >
188228 < a
189- href = "https://donate.pasarguard.org"
229+ href = { ctaHref }
190230 target = "_blank"
191231 rel = "noopener noreferrer"
192232 className = "flex items-center justify-center gap-2"
193233 >
194- < Target className = "h-4 w-4" />
195- { t ( 'goal.contribute' ) }
234+ < CtaIcon className = "h-4 w-4" />
235+ { ctaLabel }
196236 </ a >
197237 </ Button >
198238 </ div >
@@ -230,10 +270,22 @@ export function GoalProgress() {
230270 { t ( 'goal.currentGoal' ) } ({ currentGoalIndex + 1 } /{ pendingGoals . length } )
231271 </ span >
232272 < span className = "line-clamp-1 text-sm font-semibold leading-tight" > { currentGoal . name } </ span >
273+ < div className = "mt-1 h-4" >
274+ < div
275+ className = { cn (
276+ 'flex items-center gap-1 text-[11px] text-muted-foreground transition-opacity' ,
277+ repoInfoAvailable ? 'opacity-100' : 'opacity-0'
278+ ) }
279+ aria-hidden = { ! repoInfoAvailable }
280+ >
281+ < Github className = "h-3 w-3" aria-hidden />
282+ < span className = "truncate" > { repoIdentifier } </ span >
283+ </ div >
284+ </ div >
233285 </ div >
234286 </ div >
235- < div className = " flex items-center gap-1 rounded-full bg-amber-500/20 px-2 py-0.5 text-xs font-medium text-amber-700 dark:text-amber-400" >
236- < TrendingUp className = "h-3 w-3" />
287+ < div className = { cn ( ' flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium' , badgeClasses ) } >
288+ < BadgeIcon className = "h-3 w-3" />
237289 { progress . toFixed ( 0 ) } %
238290 </ div >
239291 </ div >
@@ -242,33 +294,43 @@ export function GoalProgress() {
242294 < div className = "space-y-1" >
243295 < Progress value = { progress } className = "h-2" />
244296 < div className = "flex items-center justify-between text-xs" >
245- < span className = "font-medium text-primary" > $ { currentGoal . paid_amount . toLocaleString ( ) } </ span >
297+ < span className = "font-medium text-primary" > { formattedCurrent } </ span >
246298 < span className = "text-muted-foreground" >
247- { t ( 'goal.of' ) } $ { currentGoal . price . toLocaleString ( ) }
299+ { t ( 'goal.of' ) } { formattedTarget }
248300 </ span >
249301 </ div >
250302 </ div >
251303
252304 { /* Details */ }
253- { currentGoal . detail && < p className = "line-clamp-2 text-xs leading-relaxed text-muted-foreground" > { currentGoal . detail } </ p > }
305+ < div className = "min-h-[38px]" >
306+ { currentGoal . detail && (
307+ < p className = "line-clamp-2 text-xs leading-relaxed text-muted-foreground" > { currentGoal . detail } </ p >
308+ ) }
309+ </ div >
254310
255311 { /* Remaining */ }
256- { remaining > 0 && (
257- < div className = "flex items-center justify-between rounded-md bg-background/50 px-2 py-1.5" >
258- < span className = "text-xs font-medium text-muted-foreground" > { t ( 'goal.remaining' ) } </ span >
259- < span className = "text-xs font-semibold text-foreground" > ${ remaining . toLocaleString ( ) } </ span >
312+ < div className = "min-h-[32px]" >
313+ < div
314+ className = { cn (
315+ 'flex items-center justify-between rounded-md px-2 py-1.5 text-xs transition-opacity' ,
316+ showRemaining ? 'bg-background/50 opacity-100' : 'opacity-0'
317+ ) }
318+ aria-hidden = { ! showRemaining }
319+ >
320+ < span className = "font-medium text-muted-foreground" > { remainingLabel } </ span >
321+ < span className = "font-semibold text-foreground" > { formattedRemaining } </ span >
260322 </ div >
261- ) }
323+ </ div >
262324
263325 { /* CTA Button */ }
264326 < a
265- href = "https://donate.pasarguard.org"
327+ href = { ctaHref }
266328 target = "_blank"
267329 rel = "noopener noreferrer"
268330 className = "flex w-full items-center justify-center gap-2 rounded-md bg-primary px-3 py-2 text-xs font-semibold text-primary-foreground transition-all hover:bg-primary/90 hover:shadow-md"
269331 >
270- < Target className = "h-3.5 w-3.5" />
271- { t ( 'goal.contribute' ) }
332+ < CtaIcon className = "h-3.5 w-3.5" />
333+ { ctaLabel }
272334 </ a >
273335 </ div >
274336 </ CardContent >
0 commit comments