Skip to content

Commit 6207e8d

Browse files
committed
fix(sidebar): show latest version badge in desktop
1 parent a0f7c97 commit 6207e8d

File tree

3 files changed

+93
-6
lines changed

3 files changed

+93
-6
lines changed

dashboard/src/components/common/topbar-ad.tsx

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import useDirDetection from '@/hooks/use-dir-detection'
99

1010
const TOPBAR_AD_STORAGE_KEY = 'topbar_ad_closed'
1111
const HOURS_TO_HIDE = 24
12+
const TOPBAR_AD_CACHE_KEY = 'topbar_ad_cache'
13+
const CACHE_DURATION = 10 * 60 * 1000 // 10 minutes
1214

1315
interface TopbarAdConfig {
1416
enabled: boolean
@@ -25,6 +27,12 @@ interface TopbarAdConfig {
2527
}
2628
}
2729

30+
interface CachedAdData {
31+
config: TopbarAdConfig | null
32+
timestamp: number
33+
is404: boolean
34+
}
35+
2836

2937
export default function TopbarAd() {
3038
const { i18n } = useTranslation()
@@ -40,6 +48,25 @@ export default function TopbarAd() {
4048
const [iconError, setIconError] = useState(false)
4149

4250
useEffect(() => {
51+
const getCached = (): CachedAdData | null => {
52+
try {
53+
const cached = localStorage.getItem(TOPBAR_AD_CACHE_KEY)
54+
if (!cached) return null
55+
return JSON.parse(cached)
56+
} catch {
57+
return null
58+
}
59+
}
60+
61+
const setCache = (config: TopbarAdConfig | null, is404: boolean): void => {
62+
try {
63+
const data: CachedAdData = { config, timestamp: Date.now(), is404 }
64+
localStorage.setItem(TOPBAR_AD_CACHE_KEY, JSON.stringify(data))
65+
} catch {
66+
// Silently fail
67+
}
68+
}
69+
4370
const checkShouldFetch = () => {
4471
const closedTimestamp = localStorage.getItem(TOPBAR_AD_STORAGE_KEY)
4572

@@ -59,6 +86,23 @@ export default function TopbarAd() {
5986
return
6087
}
6188

89+
// Check cache first
90+
const cached = getCached()
91+
if (cached && Date.now() - cached.timestamp < CACHE_DURATION) {
92+
// Use cached data if still valid
93+
if (cached.is404) {
94+
// If it's a 404 cache, don't fetch and set config to null
95+
setConfig(null)
96+
setIsLoading(false)
97+
return
98+
} else {
99+
// Use cached successful response
100+
setConfig(cached.config)
101+
setIsLoading(false)
102+
return
103+
}
104+
}
105+
62106
const loadConfig = async () => {
63107
try {
64108
const githubApiUrl = 'https://api.github.com/repos/pasarguard/ads/contents/config'
@@ -74,15 +118,28 @@ export default function TopbarAd() {
74118
Array.from(binaryString, (char) => '%' + ('00' + char.charCodeAt(0).toString(16)).slice(-2)).join('')
75119
)
76120
const data = JSON.parse(utf8String)
121+
setCache(data, false)
77122
setConfig(data)
78123
} else {
124+
setCache(null, false)
79125
setConfig(null)
80126
}
81127
} else {
128+
// Cache 404 errors
129+
if (response.status === 404) {
130+
setCache(null, true)
131+
} else {
132+
setCache(null, false)
133+
}
82134
setConfig(null)
83135
}
84136
} catch (error) {
85-
setConfig(null)
137+
// On error, use cached data if available, otherwise set to null
138+
if (cached && !cached.is404) {
139+
setConfig(cached.config)
140+
} else {
141+
setConfig(null)
142+
}
86143
} finally {
87144
setIsLoading(false)
88145
}

dashboard/src/components/layout/sidebar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,7 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
402402
alt="PasarGuard Logo"
403403
className="h-8 w-8 flex-shrink-0 object-contain"
404404
/>
405-
<div className="flex flex-col overflow-hidden min-w-0 flex-1">
405+
<div className="flex flex-col overflow-hidden min-w-0 flex-1 items-start">
406406
<span className={cn(isRTL ? 'text-right' : 'text-left', 'truncate text-sm font-semibold leading-tight')}>{t('pasarguard')}</span>
407407
<div className="flex items-center gap-1.5 min-w-0">
408408
<span className="text-xs opacity-45 shrink-0">{version}</span>

dashboard/src/components/layout/version-badge.tsx

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,10 @@ export function VersionBadge({ currentVersion, className }: VersionBadgeProps) {
4848
href={releaseLink}
4949
target="_blank"
5050
rel="noopener noreferrer"
51-
className={cn('inline-flex items-center gap-1 text-xs text-amber-600 dark:text-amber-400 hover:underline min-w-0 max-w-full', className)}
51+
className={cn('inline-flex items-center gap-0.5 text-[10px] opacity-70 text-amber-600 dark:text-amber-400 hover:opacity-100 hover:underline min-w-0 max-w-full transition-opacity', className)}
5252
onClick={(e) => e.stopPropagation()}
5353
>
54-
<span className="h-1.5 w-1.5 rounded-full bg-amber-500 dark:bg-amber-400 shrink-0" />
54+
<span className="h-1 w-1 rounded-full bg-amber-500 dark:bg-amber-400 shrink-0" />
5555
<span className="truncate min-w-0">{t('version.needsUpdate')}</span>
5656
</a>
5757
</TooltipTrigger>
@@ -68,10 +68,40 @@ export function VersionBadge({ currentVersion, className }: VersionBadgeProps) {
6868
)
6969
}
7070

71-
// Default: show dot with tooltip (for non-mobile, non-expanded states)
71+
// Show "Up to date" text when expanded on desktop or mobile and there's no update
72+
if (showText && !hasUpdate) {
73+
return (
74+
<Tooltip delayDuration={100}>
75+
<TooltipTrigger asChild>
76+
<span className={cn('inline-flex items-center gap-0.5 text-[10px] opacity-70 text-emerald-600 dark:text-emerald-400 min-w-0 max-w-full', className)}>
77+
<span className="h-1 w-1 rounded-full bg-emerald-500 dark:bg-emerald-400 shrink-0" />
78+
<span className="truncate min-w-0">{t('version.upToDate')}</span>
79+
</span>
80+
</TooltipTrigger>
81+
<TooltipContent side="bottom" className="p-1.5">
82+
<div className="space-y-0.5 text-[10px]">
83+
<p className="font-medium">{t('version.runningLatest', { version: `v${currentVersion}` })}</p>
84+
<p className="text-[9px]">{t('version.upToDate')}</p>
85+
</div>
86+
</TooltipContent>
87+
</Tooltip>
88+
)
89+
}
90+
91+
// Default: show dot with tooltip (for collapsed desktop state when no update)
7292
if (!hasUpdate) {
7393
return (
74-
<span className="h-1.5 w-1.5 rounded-full bg-emerald-500/50 dark:bg-emerald-400/50" />
94+
<Tooltip delayDuration={100}>
95+
<TooltipTrigger asChild>
96+
<span className="h-1.5 w-1.5 rounded-full bg-emerald-500/50 dark:bg-emerald-400/50" />
97+
</TooltipTrigger>
98+
<TooltipContent side="bottom" className="p-1.5">
99+
<div className="space-y-0.5 text-[10px]">
100+
<p className="font-medium">{t('version.runningLatest', { version: `v${currentVersion}` })}</p>
101+
<p className="text-[9px]">{t('version.upToDate')}</p>
102+
</div>
103+
</TooltipContent>
104+
</Tooltip>
75105
)
76106
}
77107

0 commit comments

Comments
 (0)