@@ -8,12 +8,51 @@ import {
8
8
} from '@pythnetwork/xc-admin-common'
9
9
import toast from 'react-hot-toast'
10
10
import Loadbar from '../loaders/Loadbar'
11
- import { LazerState } from '@pythnetwork/xc-admin-common/src/programs/types'
11
+ import Spinner from '../common/Spinner'
12
+ import { LazerState , LazerFeed , LazerPublisher } from '@pythnetwork/xc-admin-common/src/programs/types'
13
+ import { capitalizeFirstLetter } from '../../utils/capitalizeFirstLetter'
12
14
13
15
interface PythLazerProps {
14
16
proposerServerUrl : string
15
17
}
16
18
19
+ interface ShardChanges {
20
+ prev ?: {
21
+ shardId : number
22
+ shardName : string
23
+ minRate : string
24
+ }
25
+ new : {
26
+ shardId : number
27
+ shardName : string
28
+ minRate : string
29
+ }
30
+ }
31
+
32
+ interface FeedChanges {
33
+ prev ?: LazerFeed
34
+ new ?: LazerFeed
35
+ }
36
+
37
+ interface PublisherChanges {
38
+ prev ?: LazerPublisher
39
+ new ?: LazerPublisher
40
+ }
41
+
42
+ interface ShardChangesRowsProps {
43
+ changes : ShardChanges
44
+ }
45
+
46
+ interface FeedChangesRowsProps {
47
+ changes : FeedChanges
48
+ feedId : string
49
+ }
50
+
51
+ interface PublisherChangesRowsProps {
52
+ changes : PublisherChanges
53
+ publisherId : string
54
+ }
55
+
17
56
interface ModalContentProps {
18
57
changes : Record <
19
58
string ,
@@ -26,6 +65,167 @@ interface ModalContentProps {
26
65
isSendProposalButtonLoading : boolean
27
66
}
28
67
68
+ const ShardChangesRows : React . FC < ShardChangesRowsProps > = ( { changes } ) => {
69
+ const isNewShard = ! changes . prev && changes . new
70
+
71
+ return (
72
+ < >
73
+ { Object . entries ( changes . new ) . map ( ( [ key , newValue ] ) =>
74
+ ( isNewShard || ( changes . prev && changes . prev [ key as keyof typeof changes . prev ] !== newValue ) ) && (
75
+ < tr key = { key } >
76
+ < td className = "base16 py-4 pl-6 pr-2 lg:pl-6" >
77
+ { key
78
+ . split ( / (? = [ A - Z ] ) / )
79
+ . join ( ' ' )
80
+ . split ( '_' )
81
+ . map ( ( word ) => capitalizeFirstLetter ( word ) )
82
+ . join ( ' ' ) }
83
+ </ td >
84
+ < td className = "base16 py-4 pl-1 pr-2 lg:pl-6" >
85
+ { ! isNewShard && changes . prev ? (
86
+ < >
87
+ < s > { String ( changes . prev [ key as keyof typeof changes . prev ] ) } </ s >
88
+ < br />
89
+ </ >
90
+ ) : null }
91
+ { String ( newValue ) }
92
+ </ td >
93
+ </ tr >
94
+ )
95
+ ) }
96
+ </ >
97
+ )
98
+ }
99
+
100
+ const FeedChangesRows : React . FC < FeedChangesRowsProps > = ( { changes, feedId } ) => {
101
+ const isNewFeed = ! changes . prev && changes . new
102
+ const isDeletedFeed = changes . prev && ! changes . new
103
+
104
+ if ( isDeletedFeed ) {
105
+ return (
106
+ < tr >
107
+ < td className = "base16 py-4 pl-6 pr-2 lg:pl-6" > Feed ID</ td >
108
+ < td className = "base16 py-4 pl-1 pr-2 lg:pl-6" > { feedId . replace ( 'feed_' , '' ) } </ td >
109
+ </ tr >
110
+ )
111
+ }
112
+
113
+ if ( ! changes . new ) return null
114
+
115
+ const renderMetadataChanges = ( ) => {
116
+ if ( ! changes . new ?. metadata ) return null
117
+
118
+ return Object . entries ( changes . new . metadata ) . map ( ( [ key , newValue ] ) => {
119
+ const prevValue = changes . prev ?. metadata ?. [ key as keyof typeof changes . prev . metadata ]
120
+ const hasChanged = isNewFeed || prevValue !== newValue
121
+
122
+ if ( ! hasChanged ) return null
123
+
124
+ return (
125
+ < tr key = { key } >
126
+ < td className = "base16 py-4 pl-6 pr-2 lg:pl-6" >
127
+ { key
128
+ . split ( / (? = [ A - Z ] ) / )
129
+ . join ( ' ' )
130
+ . split ( '_' )
131
+ . map ( ( word ) => capitalizeFirstLetter ( word ) )
132
+ . join ( ' ' ) }
133
+ </ td >
134
+ < td className = "base16 py-4 pl-1 pr-2 lg:pl-6" >
135
+ { ! isNewFeed && prevValue !== undefined ? (
136
+ < >
137
+ < s > { String ( prevValue ) } </ s >
138
+ < br />
139
+ </ >
140
+ ) : null }
141
+ { String ( newValue ) }
142
+ </ td >
143
+ </ tr >
144
+ )
145
+ } )
146
+ }
147
+
148
+ const renderPendingActivationChanges = ( ) => {
149
+ if ( changes . new ?. pendingActivation !== undefined || changes . prev ?. pendingActivation !== undefined ) {
150
+ const hasChanged = isNewFeed || changes . prev ?. pendingActivation !== changes . new ?. pendingActivation
151
+
152
+ if ( hasChanged ) {
153
+ return (
154
+ < tr key = "pendingActivation" >
155
+ < td className = "base16 py-4 pl-6 pr-2 lg:pl-6" > Pending Activation</ td >
156
+ < td className = "base16 py-4 pl-1 pr-2 lg:pl-6" >
157
+ { ! isNewFeed && changes . prev ?. pendingActivation ? (
158
+ < >
159
+ < s > { changes . prev . pendingActivation } </ s >
160
+ < br />
161
+ </ >
162
+ ) : null }
163
+ { changes . new ?. pendingActivation || 'None' }
164
+ </ td >
165
+ </ tr >
166
+ )
167
+ }
168
+ }
169
+ return null
170
+ }
171
+
172
+ return (
173
+ < >
174
+ { renderMetadataChanges ( ) }
175
+ { renderPendingActivationChanges ( ) }
176
+ </ >
177
+ )
178
+ }
179
+
180
+ const PublisherChangesRows : React . FC < PublisherChangesRowsProps > = ( { changes, publisherId } ) => {
181
+ const isNewPublisher = ! changes . prev && changes . new
182
+ const isDeletedPublisher = changes . prev && ! changes . new
183
+
184
+ if ( isDeletedPublisher ) {
185
+ return (
186
+ < tr >
187
+ < td className = "base16 py-4 pl-6 pr-2 lg:pl-6" > Publisher ID</ td >
188
+ < td className = "base16 py-4 pl-1 pr-2 lg:pl-6" > { publisherId . replace ( 'publisher_' , '' ) } </ td >
189
+ </ tr >
190
+ )
191
+ }
192
+
193
+ if ( ! changes . new ) return null
194
+
195
+ return (
196
+ < >
197
+ { Object . entries ( changes . new ) . map ( ( [ key , newValue ] ) => {
198
+ const prevValue = changes . prev ?. [ key as keyof LazerPublisher ]
199
+ const hasChanged = isNewPublisher || JSON . stringify ( prevValue ) !== JSON . stringify ( newValue )
200
+
201
+ if ( ! hasChanged ) return null
202
+
203
+ return (
204
+ < tr key = { key } >
205
+ < td className = "base16 py-4 pl-6 pr-2 lg:pl-6" >
206
+ { key
207
+ . split ( / (? = [ A - Z ] ) / )
208
+ . join ( ' ' )
209
+ . split ( '_' )
210
+ . map ( ( word ) => capitalizeFirstLetter ( word ) )
211
+ . join ( ' ' ) }
212
+ </ td >
213
+ < td className = "base16 py-4 pl-1 pr-2 lg:pl-6" >
214
+ { ! isNewPublisher && prevValue !== undefined ? (
215
+ < >
216
+ < s > { Array . isArray ( prevValue ) ? prevValue . join ( ', ' ) : String ( prevValue ) } </ s >
217
+ < br />
218
+ </ >
219
+ ) : null }
220
+ { Array . isArray ( newValue ) ? newValue . join ( ', ' ) : String ( newValue ) }
221
+ </ td >
222
+ </ tr >
223
+ )
224
+ } ) }
225
+ </ >
226
+ )
227
+ }
228
+
29
229
const ModalContent : React . FC < ModalContentProps > = ( {
30
230
changes,
31
231
onSendProposal,
@@ -35,23 +235,68 @@ const ModalContent: React.FC<ModalContentProps> = ({
35
235
< >
36
236
{ Object . keys ( changes ) . length > 0 ? (
37
237
< table className = "mb-10 w-full table-auto bg-darkGray text-left" >
38
- < tbody >
39
- { Object . entries ( changes ) . map ( ( [ key , change ] ) => (
40
- < tr key = { key } >
41
- < td
42
- className = "base16 py-4 pl-6 pr-2 font-bold lg:pl-6"
43
- colSpan = { 2 }
44
- >
45
- { key }
46
- </ td >
47
- < td className = "py-3 pl-6 pr-1 lg:pl-6" >
48
- < pre className = "whitespace-pre-wrap rounded bg-gray-100 p-2 text-xs dark:bg-gray-700 dark:text-gray-300" >
49
- { JSON . stringify ( change , null , 2 ) }
50
- </ pre >
51
- </ td >
52
- </ tr >
53
- ) ) }
54
- </ tbody >
238
+ { Object . entries ( changes ) . map ( ( [ key , change ] ) => {
239
+ const { prev, new : newChanges } = change
240
+ const isAddition = ! prev && newChanges
241
+ const isDeletion = prev && ! newChanges
242
+
243
+ let title = key
244
+ if ( key === 'shard' ) {
245
+ title = isAddition ? 'Add New Shard' : isDeletion ? 'Delete Shard' : 'Shard Configuration'
246
+ } else if ( key . startsWith ( 'feed_' ) ) {
247
+ const feedId = key . replace ( 'feed_' , '' )
248
+ title = isAddition ? `Add New Feed (ID: ${ feedId } )` : isDeletion ? `Delete Feed (ID: ${ feedId } )` : `Feed ${ feedId } `
249
+ } else if ( key . startsWith ( 'publisher_' ) ) {
250
+ const publisherId = key . replace ( 'publisher_' , '' )
251
+ title = isAddition ? `Add New Publisher (ID: ${ publisherId } )` : isDeletion ? `Delete Publisher (ID: ${ publisherId } )` : `Publisher ${ publisherId } `
252
+ }
253
+
254
+ return (
255
+ < tbody key = { key } >
256
+ < tr >
257
+ < td
258
+ className = "base16 py-4 pl-6 pr-2 font-bold lg:pl-6"
259
+ colSpan = { 2 }
260
+ >
261
+ { title }
262
+ </ td >
263
+ </ tr >
264
+
265
+ { key === 'shard' && newChanges ? (
266
+ < ShardChangesRows
267
+ changes = { {
268
+ prev : prev as ShardChanges [ 'prev' ] ,
269
+ new : newChanges as ShardChanges [ 'new' ] ,
270
+ } }
271
+ />
272
+ ) : key . startsWith ( 'feed_' ) ? (
273
+ < FeedChangesRows
274
+ feedId = { key }
275
+ changes = { {
276
+ prev : prev as LazerFeed ,
277
+ new : newChanges as LazerFeed ,
278
+ } }
279
+ />
280
+ ) : key . startsWith ( 'publisher_' ) ? (
281
+ < PublisherChangesRows
282
+ publisherId = { key }
283
+ changes = { {
284
+ prev : prev as LazerPublisher ,
285
+ new : newChanges as LazerPublisher ,
286
+ } }
287
+ />
288
+ ) : null }
289
+
290
+ { Object . keys ( changes ) . indexOf ( key ) !== Object . keys ( changes ) . length - 1 ? (
291
+ < tr >
292
+ < td className = "base16 py-4 pl-6 pr-6" colSpan = { 2 } >
293
+ < hr className = "border-gray-700" />
294
+ </ td >
295
+ </ tr >
296
+ ) : null }
297
+ </ tbody >
298
+ )
299
+ } ) }
55
300
</ table >
56
301
) : (
57
302
< p className = "mb-8 leading-6" > No proposed changes.</ p >
@@ -62,7 +307,7 @@ const ModalContent: React.FC<ModalContentProps> = ({
62
307
onClick = { onSendProposal }
63
308
disabled = { isSendProposalButtonLoading }
64
309
>
65
- { isSendProposalButtonLoading ? 'Sending...' : 'Send Proposal' }
310
+ { isSendProposalButtonLoading ? < Spinner /> : 'Send Proposal' }
66
311
</ button >
67
312
) }
68
313
</ >
@@ -138,7 +383,7 @@ const PythLazer = ({
138
383
openModal ( )
139
384
} catch ( error ) {
140
385
if ( error instanceof Error ) {
141
- toast . error ( error . message )
386
+ toast . error ( capitalizeFirstLetter ( error . message ) )
142
387
}
143
388
}
144
389
}
@@ -161,7 +406,7 @@ const PythLazer = ({
161
406
toast . success ( 'Proposal sent successfully!' )
162
407
} catch ( error ) {
163
408
if ( error instanceof Error ) {
164
- toast . error ( error . message )
409
+ toast . error ( capitalizeFirstLetter ( error . message ) )
165
410
}
166
411
} finally {
167
412
setIsSendProposalButtonLoading ( false )
0 commit comments