@@ -4,12 +4,16 @@ import { CURRENT_VERSION, GITHUB_API_URL, GITHUB_REPO } from '@/env';
4
4
import { useTranslation } from 'react-i18next' ;
5
5
6
6
import { Badge } from './ui/badge' ;
7
+ import { ShootingStar } from '@phosphor-icons/react' ;
7
8
8
9
interface GithubRelease {
9
10
tag_name : string ;
10
11
html_url : string ;
11
12
}
12
13
14
+ const CACHE_KEY = 'version_check' ;
15
+ const CACHE_DURATION = 12 * 60 * 60 * 1000 ; // 12 hours in milliseconds
16
+
13
17
export function VersionChecker ( ) {
14
18
const { t } = useTranslation ( ) ;
15
19
const [ hasUpdate , setHasUpdate ] = useState ( false ) ;
@@ -18,40 +22,99 @@ export function VersionChecker() {
18
22
useEffect ( ( ) => {
19
23
const checkVersion = async ( ) => {
20
24
try {
21
- const response = await fetch ( `${ GITHUB_API_URL } /releases` ) ;
25
+ // Check cache first
26
+ const cachedData = localStorage . getItem ( CACHE_KEY ) ;
27
+ if ( cachedData ) {
28
+ const { timestamp, data } = JSON . parse ( cachedData ) ;
29
+ if ( Date . now ( ) - timestamp < CACHE_DURATION ) {
30
+ setLatestVersion ( data . version ) ;
31
+ setHasUpdate ( compareVersions ( data . version , CURRENT_VERSION ) > 0 ) ;
32
+ return ;
33
+ }
34
+ }
35
+
36
+ // Make API call with headers to avoid rate limiting
37
+ const response = await fetch ( `${ GITHUB_API_URL } /releases` , {
38
+ headers : {
39
+ 'Accept' : 'application/vnd.github.v3+json' ,
40
+ 'If-None-Match' : localStorage . getItem ( 'version_etag' ) || ''
41
+ }
42
+ } ) ;
43
+
44
+ // Handle 304 Not Modified
45
+ if ( response . status === 304 ) {
46
+ return ;
47
+ }
48
+
49
+ // Save ETag for future requests
50
+ const etag = response . headers . get ( 'ETag' ) ;
51
+ if ( etag ) {
52
+ localStorage . setItem ( 'version_etag' , etag ) ;
53
+ }
54
+
22
55
const data : GithubRelease [ ] = await response . json ( ) ;
23
- const latest = data [ 0 ] . tag_name . replace ( 'v' , '' ) ;
24
- setLatestVersion ( latest ) ;
56
+ if ( data . length > 0 ) {
57
+ const latest = data [ 0 ] . tag_name . replace ( 'v' , '' ) ;
58
+ setLatestVersion ( latest ) ;
59
+ const needsUpdate = compareVersions ( latest , CURRENT_VERSION ) > 0 ;
60
+ setHasUpdate ( needsUpdate ) ;
25
61
26
- if ( compareVersions ( latest , CURRENT_VERSION ) > 0 ) {
27
- setHasUpdate ( true ) ;
62
+ // Cache the result
63
+ localStorage . setItem ( CACHE_KEY , JSON . stringify ( {
64
+ timestamp : Date . now ( ) ,
65
+ data : { version : latest }
66
+ } ) ) ;
28
67
}
29
68
} catch ( error ) {
30
69
console . error ( 'Failed to check version:' , error ) ;
70
+ // On error, use cached data if available
71
+ const cachedData = localStorage . getItem ( CACHE_KEY ) ;
72
+ if ( cachedData ) {
73
+ const { data } = JSON . parse ( cachedData ) ;
74
+ setLatestVersion ( data . version ) ;
75
+ setHasUpdate ( compareVersions ( data . version , CURRENT_VERSION ) > 0 ) ;
76
+ }
31
77
}
32
78
} ;
33
79
34
80
checkVersion ( ) ;
35
- // Check every 24 hours
36
- const interval = setInterval ( checkVersion , 24 * 60 * 60 * 1000 ) ;
81
+ // Check every 12 hours
82
+ const interval = setInterval ( checkVersion , CACHE_DURATION ) ;
37
83
return ( ) => clearInterval ( interval ) ;
38
84
} , [ CURRENT_VERSION ] ) ;
39
85
40
86
return (
41
87
< >
42
- < a href = 'https://lanms.net' target = '_blank' rel = 'noopener noreferrer' className = 'text-xs text-gray-300 dark :text-gray-500 hover :text-muted-foreground dark:hover:text-secondary-foreground ' > LANMS { CURRENT_VERSION } </ a >
88
+ < a href = 'https://lanms.net' target = '_blank' rel = 'noopener noreferrer' className = 'text-xs text-muted-foreground hover :text-primary dark :text-muted-foreground dark:hover:text-primary cursor-pointer ' > LANMS { CURRENT_VERSION } </ a >
43
89
{ hasUpdate && (
44
- < Badge variant = "outline" >
90
+ < Badge variant = "update" className = 'flex items-center justify-center my-2' >
45
91
< a
46
92
href = { `https://github.com/${ GITHUB_REPO } /releases/latest` }
47
93
target = "_blank"
48
94
rel = "noopener noreferrer"
49
- className = "ml-2"
95
+ className = "relative flex items-center justify-center w-full"
96
+ onMouseEnter = { ( e ) => {
97
+ const target = e . currentTarget ;
98
+ target . querySelector ( '.version-default' ) ?. classList . add ( 'opacity-0' ) ;
99
+ target . querySelector ( '.version-hover' ) ?. classList . remove ( 'opacity-0' ) ;
100
+ } }
101
+ onMouseLeave = { ( e ) => {
102
+ const target = e . currentTarget ;
103
+ target . querySelector ( '.version-default' ) ?. classList . remove ( 'opacity-0' ) ;
104
+ target . querySelector ( '.version-hover' ) ?. classList . add ( 'opacity-0' ) ;
105
+ } }
50
106
>
51
- { t ( 'version.newAvailable' ) } : { latestVersion }
107
+ < div className = "version-default flex items-center justify-center transition-opacity duration-200" >
108
+ < ShootingStar className = "size-4 mr-1" weight = "fill" />
109
+ < span > { t ( 'common.update_available' ) } </ span >
110
+ </ div >
111
+ < div className = "version-hover absolute inset-0 flex items-center justify-center opacity-0 transition-opacity duration-200" >
112
+ < ShootingStar className = "size-4 mr-1" weight = "fill" />
113
+ < span > { latestVersion } </ span >
114
+ </ div >
52
115
</ a >
53
116
</ Badge >
54
117
) }
55
118
</ >
56
119
) ;
57
- }
120
+ }
0 commit comments