1
1
import React , { useEffect , useState , useCallback } from 'react' ;
2
2
3
+ const RETRY_INTERVAL = 10000 ; // 10 seconds
4
+
5
+ // Reusable spinner component
6
+ const Spinner : React . FC = ( ) => (
7
+ < div className = "animate-spin rounded-full h-4 w-4 border-b-2 border-blue-600 mr-2" > </ div >
8
+ ) ;
9
+
3
10
// Connection modal component
4
- const ConnectionModal : React . FC < { onRetry : ( ) => void ; isRetrying : boolean } > = ( { onRetry, isRetrying } ) => {
5
- const [ retryCount , setRetryCount ] = useState ( 0 ) ;
11
+ const ConnectionModal : React . FC < {
12
+ nextRetryTime ?: number ;
13
+ } > = ( { nextRetryTime } ) => {
14
+ const [ secondsUntilRetry , setSecondsUntilRetry ] = useState ( 0 ) ;
6
15
7
16
useEffect ( ( ) => {
8
- const interval = setInterval ( ( ) => {
9
- setRetryCount ( count => count + 1 ) ;
10
- } , 1000 ) ;
17
+ if ( ! nextRetryTime ) return ;
18
+
19
+ const updateCountdown = ( ) => {
20
+ const now = Date . now ( ) ;
21
+ const remaining = Math . max ( 0 , Math . floor ( ( nextRetryTime - now ) / 1000 ) ) ;
22
+ setSecondsUntilRetry ( remaining ) ;
23
+ } ;
24
+
25
+ // Update immediately
26
+ updateCountdown ( ) ;
27
+
28
+ // Update every second
29
+ const interval = setInterval ( updateCountdown , 1000 ) ;
11
30
return ( ) => clearInterval ( interval ) ;
12
- } , [ ] ) ;
31
+ } , [ nextRetryTime ] ) ;
13
32
14
33
return (
15
34
< div className = "fixed inset-0 z-50 bg-black bg-opacity-50 flex items-center justify-center" >
@@ -31,90 +50,99 @@ const ConnectionModal: React.FC<{ onRetry: () => void; isRetrying: boolean }> =
31
50
installer is running and accessible.
32
51
</ p >
33
52
34
- < div className = "flex items-center justify-between " >
53
+ < div className = "flex items-center justify-center " >
35
54
< div className = "flex items-center text-sm font-semibold text-gray-600" >
36
- < div className = "animate-spin rounded-full h-4 w-4 border-b-2 border-blue-600 mr-2" > </ div >
37
- Trying again in { Math . max ( 1 , 10 - ( retryCount % 10 ) ) } second{ Math . max ( 1 , 10 - ( retryCount % 10 ) ) !== 1 ? 's' : '' }
55
+ { secondsUntilRetry > 0 ? (
56
+ < >
57
+ < Spinner />
58
+ Retrying in { secondsUntilRetry } second{ secondsUntilRetry !== 1 ? 's' : '' }
59
+ </ >
60
+ ) : (
61
+ < >
62
+ < Spinner />
63
+ Retrying now...
64
+ </ >
65
+ ) }
38
66
</ div >
39
- < button
40
- onClick = { onRetry }
41
- disabled = { isRetrying }
42
- className = "px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
43
- >
44
- { isRetrying ? 'Retrying...' : 'Try Now' }
45
- </ button >
46
67
</ div >
47
68
</ div >
48
69
</ div >
49
70
) ;
50
71
} ;
51
72
52
- const ConnectionMonitor : React . FC = ( ) => {
73
+ // Custom hook for connection monitoring logic
74
+ const useConnectionMonitor = ( ) => {
53
75
const [ isConnected , setIsConnected ] = useState ( true ) ;
54
- const [ isChecking , setIsChecking ] = useState ( false ) ;
76
+ const [ nextRetryTime , setNextRetryTime ] = useState < number | undefined > ( ) ;
77
+ const [ checkInterval , setCheckInterval ] = useState < NodeJS . Timeout | null > ( null ) ;
55
78
56
79
const checkConnection = useCallback ( async ( ) => {
57
- setIsChecking ( true ) ;
58
-
59
80
try {
60
- // Try up to 3 times before marking as disconnected
61
- let attempts = 0 ;
62
- const maxAttempts = 3 ;
81
+ const timeoutPromise = new Promise ( ( _ , reject ) =>
82
+ setTimeout ( ( ) => reject ( new Error ( 'Timeout' ) ) , 5000 )
83
+ ) ;
63
84
64
- while ( attempts < maxAttempts ) {
65
- try {
66
- // Create a timeout promise
67
- const timeoutPromise = new Promise ( ( _ , reject ) =>
68
- setTimeout ( ( ) => reject ( new Error ( 'Timeout' ) ) , 5000 )
69
- ) ;
70
-
71
- const fetchPromise = fetch ( '/api/health' , {
72
- method : 'GET' ,
73
- headers : { 'Content-Type' : 'application/json' } ,
74
- } ) ;
75
-
76
- const response = await Promise . race ( [ fetchPromise , timeoutPromise ] ) as Response ;
77
-
78
- if ( response . ok ) {
79
- setIsConnected ( true ) ;
80
- return ;
81
- } else {
82
- throw new Error ( `HTTP ${ response . status } ` ) ;
83
- }
84
- } catch {
85
- attempts ++ ;
86
- if ( attempts < maxAttempts ) {
87
- await new Promise ( resolve => setTimeout ( resolve , 1000 ) ) ;
88
- }
89
- }
90
- }
85
+ const fetchPromise = fetch ( '/api/health' , {
86
+ method : 'GET' ,
87
+ headers : { 'Content-Type' : 'application/json' } ,
88
+ } ) ;
91
89
92
- // All attempts failed - show modal immediately
93
- setIsConnected ( false ) ;
90
+ const response = await Promise . race ( [ fetchPromise , timeoutPromise ] ) as Response ;
94
91
92
+ if ( response . ok ) {
93
+ setIsConnected ( true ) ;
94
+ setNextRetryTime ( undefined ) ;
95
+ } else {
96
+ throw new Error ( `HTTP ${ response . status } ` ) ;
97
+ }
95
98
} catch {
99
+ // Connection failed - set up countdown for next retry
96
100
setIsConnected ( false ) ;
97
- } finally {
98
- setIsChecking ( false ) ;
101
+ const retryTime = Date . now ( ) + RETRY_INTERVAL ;
102
+ setNextRetryTime ( retryTime ) ;
99
103
}
100
104
} , [ ] ) ;
101
105
102
106
useEffect ( ( ) => {
103
107
// Initial check
104
108
checkConnection ( ) ;
105
109
106
- // Set up periodic health checks every 5 seconds
107
- const interval = setInterval ( checkConnection , 5000 ) ;
110
+ // Set up regular interval checks
111
+ const interval = setInterval ( checkConnection , RETRY_INTERVAL ) ;
112
+ setCheckInterval ( interval ) ;
108
113
109
- return ( ) => clearInterval ( interval ) ;
110
- } , [ checkConnection ] ) ;
114
+ // Cleanup on unmount
115
+ return ( ) => {
116
+ if ( interval ) {
117
+ clearInterval ( interval ) ;
118
+ }
119
+ } ;
120
+ // eslint-disable-next-line react-hooks/exhaustive-deps
121
+ } , [ ] ) ; // Empty dependency array to prevent infinite loops
122
+
123
+ // Cleanup interval when it changes
124
+ useEffect ( ( ) => {
125
+ return ( ) => {
126
+ if ( checkInterval ) {
127
+ clearInterval ( checkInterval ) ;
128
+ }
129
+ } ;
130
+ } , [ checkInterval ] ) ;
131
+
132
+ return {
133
+ isConnected,
134
+ nextRetryTime,
135
+ } ;
136
+ } ;
137
+
138
+ const ConnectionMonitor : React . FC = ( ) => {
139
+ const { isConnected, nextRetryTime } = useConnectionMonitor ( ) ;
111
140
112
141
return (
113
142
< >
114
143
{ ! isConnected && (
115
144
< ConnectionModal
116
- onRetry = { checkConnection }
117
- isRetrying = { isChecking }
145
+ nextRetryTime = { nextRetryTime }
118
146
/>
119
147
) }
120
148
</ >
0 commit comments