1
1
import { Tooltip } from "@namada/components" ;
2
2
import { indexerApiAtom } from "atoms/api" ;
3
+ import { shieldedBalanceAtom , shieldedSyncProgress } from "atoms/balance/atoms" ;
3
4
import { fetchBlockHeightByTimestamp } from "atoms/balance/services" ;
4
5
import { chainStatusAtom } from "atoms/chain" ;
5
6
import {
6
7
indexerServicesSyncStatusAtom ,
7
8
syncStatusAtom ,
8
9
} from "atoms/syncStatus/atoms" ;
9
10
import { useAtomValue } from "jotai" ;
10
- import { useEffect , useState } from "react" ;
11
+ import { useEffect , useMemo , useState } from "react" ;
11
12
import { twMerge } from "tailwind-merge" ;
13
+ import { PulsingRing } from "../Common/PulsingRing" ;
12
14
13
15
const formatError = (
14
16
errors : ( string | Error ) [ ] ,
@@ -32,22 +34,28 @@ const formatError = (
32
34
export const SyncIndicator = ( ) : JSX . Element => {
33
35
const syncStatus = useAtomValue ( syncStatusAtom ) ;
34
36
const indexerServicesSyncStatus = useAtomValue ( indexerServicesSyncStatusAtom ) ;
37
+ const api = useAtomValue ( indexerApiAtom ) ;
35
38
const chainStatus = useAtomValue ( chainStatusAtom ) ;
39
+ const shieldedProgress = useAtomValue ( shieldedSyncProgress ) ;
40
+ const { isFetching : isShieldedFetching } = useAtomValue ( shieldedBalanceAtom ) ;
36
41
const [ blockHeightSync , setBlockHeightSync ] = useState < boolean | null > ( null ) ;
37
42
const [ indexerBlockHeight , setIndexerBlockHeight ] = useState < number | null > (
38
43
null
39
44
) ;
45
+ const roundedProgress = useMemo ( ( ) => {
46
+ // Only update when the progress changes by at least 1%
47
+ return Math . min ( Math . floor ( shieldedProgress * 100 ) , 100 ) ;
48
+ } , [ Math . floor ( shieldedProgress * 100 ) ] ) ;
49
+
40
50
const { errors } = syncStatus ;
41
51
const { services } = indexerServicesSyncStatus ;
52
+
42
53
const isChainStatusError =
43
54
! chainStatus ?. height || ! chainStatus ?. epoch || ! blockHeightSync ;
44
- const api = useAtomValue ( indexerApiAtom ) ;
45
-
46
55
const isError =
47
56
syncStatus . isError ||
48
57
indexerServicesSyncStatus . isError ||
49
58
isChainStatusError ;
50
-
51
59
const isSyncing =
52
60
syncStatus . isSyncing ||
53
61
indexerServicesSyncStatus . isSyncing ||
@@ -65,35 +73,65 @@ export const SyncIndicator = (): JSX.Element => {
65
73
} , [ chainStatus ?. height ] ) ;
66
74
67
75
return (
68
- < div className = "relative group/tooltip px-1 py-3" >
69
- < div
70
- className = { twMerge (
71
- "w-2 h-2 rounded-full" ,
72
- "bg-green-500" ,
73
- isSyncing && "bg-yellow-500 animate-pulse" ,
74
- isError && ! isSyncing && "bg-red-500"
75
- ) }
76
- />
77
- < Tooltip
78
- position = "bottom"
79
- className = "z-10 w-max max-w-[200px] text-balance"
80
- >
81
- { isSyncing ?
82
- "Syncing"
83
- : isError ?
84
- < div >
85
- { formatError ( errors , "Error" ) }
86
- { formatError ( services , "Lagging services" ) }
87
- { isChainStatusError && "Chain status not loaded." }
76
+ < div className = "flex gap-10 px-2 py-3" >
77
+ { roundedProgress < 100 && ! isShieldedFetching && (
78
+ < div className = "relative group/tooltip" >
79
+ < div className = "relative mt-1" >
80
+ < PulsingRing size = "small" />
81
+ < div className = "absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-2 h-2 bg-yellow-500 rounded-full" />
88
82
</ div >
89
- : < div >
90
- < div > Fully synced:</ div >
91
- < div > RPC Height: { chainStatus ?. height } </ div >
92
- < div > Indexer Height: { indexerBlockHeight } </ div >
93
- < div > Epoch: { chainStatus ?. epoch } </ div >
94
- </ div >
95
- }
96
- </ Tooltip >
83
+ < Tooltip
84
+ position = "bottom"
85
+ className = "z-10 w-max max-w-[220px] rounded-md p-4 -mb-6"
86
+ >
87
+ < div className = "space-y-3" >
88
+ < div className = "text-md text-yellow" >
89
+ Shielded sync: { roundedProgress } %
90
+ </ div >
91
+ < div className = "w-full bg-yellow-900 h-1" >
92
+ < div
93
+ className = "bg-yellow-500 h-1 transition-all duration-300"
94
+ style = { { width : `${ roundedProgress } %` } }
95
+ />
96
+ </ div >
97
+ < div className = "text-sm text-neutral-400" >
98
+ Syncing your shielded assets now. Balances will update in a few
99
+ seconds.
100
+ </ div >
101
+ </ div >
102
+ </ Tooltip >
103
+ </ div >
104
+ ) }
105
+
106
+ < div className = "relative group/tooltip" >
107
+ < div
108
+ className = { twMerge (
109
+ "w-2 h-2 rounded-full" ,
110
+ "bg-green-500" ,
111
+ isError && "bg-red-500"
112
+ ) }
113
+ />
114
+ < Tooltip
115
+ position = "bottom"
116
+ className = "z-10 w-max max-w-[200px] text-balance -mb-6"
117
+ >
118
+ { isSyncing ?
119
+ "Syncing..."
120
+ : isError ?
121
+ < div >
122
+ { formatError ( errors , "Error" ) }
123
+ { formatError ( services , "Lagging services" ) }
124
+ { isChainStatusError && "Chain status not loaded." }
125
+ </ div >
126
+ : < div className = "py-2" >
127
+ < div className = "text-yellow font-medium" > Fully synced:</ div >
128
+ < div > RPC Height: { chainStatus ?. height } </ div >
129
+ < div > Indexer Height: { indexerBlockHeight } </ div >
130
+ < div > Epoch: { chainStatus ?. epoch } </ div >
131
+ </ div >
132
+ }
133
+ </ Tooltip >
134
+ </ div >
97
135
</ div >
98
136
) ;
99
137
} ;
0 commit comments