@@ -8,12 +8,51 @@ import {
88} from '@pythnetwork/xc-admin-common'
99import toast from 'react-hot-toast'
1010import 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'
1214
1315interface PythLazerProps {
1416 proposerServerUrl : string
1517}
1618
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+
1756interface ModalContentProps {
1857 changes : Record <
1958 string ,
@@ -26,6 +65,167 @@ interface ModalContentProps {
2665 isSendProposalButtonLoading : boolean
2766}
2867
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+
29229const ModalContent : React . FC < ModalContentProps > = ( {
30230 changes,
31231 onSendProposal,
@@ -35,23 +235,68 @@ const ModalContent: React.FC<ModalContentProps> = ({
35235 < >
36236 { Object . keys ( changes ) . length > 0 ? (
37237 < 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+ } ) }
55300 </ table >
56301 ) : (
57302 < p className = "mb-8 leading-6" > No proposed changes.</ p >
@@ -62,7 +307,7 @@ const ModalContent: React.FC<ModalContentProps> = ({
62307 onClick = { onSendProposal }
63308 disabled = { isSendProposalButtonLoading }
64309 >
65- { isSendProposalButtonLoading ? 'Sending...' : 'Send Proposal' }
310+ { isSendProposalButtonLoading ? < Spinner /> : 'Send Proposal' }
66311 </ button >
67312 ) }
68313 </ >
@@ -138,7 +383,7 @@ const PythLazer = ({
138383 openModal ( )
139384 } catch ( error ) {
140385 if ( error instanceof Error ) {
141- toast . error ( error . message )
386+ toast . error ( capitalizeFirstLetter ( error . message ) )
142387 }
143388 }
144389 }
@@ -161,7 +406,7 @@ const PythLazer = ({
161406 toast . success ( 'Proposal sent successfully!' )
162407 } catch ( error ) {
163408 if ( error instanceof Error ) {
164- toast . error ( error . message )
409+ toast . error ( capitalizeFirstLetter ( error . message ) )
165410 }
166411 } finally {
167412 setIsSendProposalButtonLoading ( false )
0 commit comments