1+ import React , { useState , useEffect } from 'react' ;
2+ import { BlockHeader } from '../types/Message' ;
3+
4+ interface BlockDetailsModalProps {
5+ blockHeader : BlockHeader | null ;
6+ isOpen : boolean ;
7+ onClose : ( ) => void ;
8+ }
9+
10+ export const BlockDetailsModal : React . FC < BlockDetailsModalProps > = ( { blockHeader, isOpen, onClose } ) => {
11+ const [ coinbaseData , setCoinbaseData ] = useState < any > ( null ) ;
12+ const [ loading , setLoading ] = useState ( false ) ;
13+
14+ useEffect ( ( ) => {
15+ if ( blockHeader && isOpen ) {
16+ // TODO: Fetch coinbase transaction data when API is available
17+ // For now, we'll display the block header information
18+ setCoinbaseData ( null ) ;
19+ }
20+ } , [ blockHeader , isOpen ] ) ;
21+
22+ if ( ! isOpen || ! blockHeader ) return null ;
23+
24+ const formatTimestamp = ( timestamp : number ) => {
25+ const date = new Date ( timestamp * 1000 ) ;
26+ return date . toLocaleString ( ) ;
27+ } ;
28+
29+ const getTimeSince = ( timestamp : number ) => {
30+ const now = Date . now ( ) / 1000 ;
31+ const diff = now - timestamp ;
32+
33+ if ( diff < 60 ) return 'Just now' ;
34+ if ( diff < 3600 ) return `${ Math . floor ( diff / 60 ) } minutes ago` ;
35+ if ( diff < 86400 ) return `${ Math . floor ( diff / 3600 ) } hours ago` ;
36+ return `${ Math . floor ( diff / 86400 ) } days ago` ;
37+ } ;
38+
39+ const calculateDifficulty = ( bits : number ) => {
40+ // Convert bits to difficulty
41+ const nShift = ( bits >> 24 ) & 0xff ;
42+ let dDiff = 0x0000ffff / ( bits & 0x00ffffff ) ;
43+ let nShiftAmount = 256 - nShift - 32 ;
44+
45+ while ( nShiftAmount > 0 ) {
46+ dDiff = dDiff * 256.0 ;
47+ nShiftAmount -= 8 ;
48+ }
49+
50+ return dDiff . toExponential ( 2 ) ;
51+ } ;
52+
53+ const getNetworkColor = ( network : string ) => {
54+ switch ( network ) {
55+ case 'mainnet' : return 'from-green-400 to-green-600' ;
56+ case 'testnet' : return 'from-blue-400 to-blue-600' ;
57+ case 'teratestnet' : return 'from-indigo-400 to-indigo-600' ;
58+ default : return 'from-gray-400 to-gray-600' ;
59+ }
60+ } ;
61+
62+ return (
63+ < div className = "fixed inset-0 z-50 overflow-y-auto" >
64+ < div className = "flex items-center justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:block sm:p-0" >
65+ { /* Background overlay */ }
66+ < div className = "fixed inset-0 transition-opacity" onClick = { onClose } >
67+ < div className = "absolute inset-0 bg-gray-900 opacity-75" > </ div >
68+ </ div >
69+
70+ { /* Modal panel */ }
71+ < div className = "inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-4xl sm:w-full" >
72+ { /* Header */ }
73+ < div className = { `bg-gradient-to-r ${ getNetworkColor ( blockHeader . Network ) } px-6 py-4` } >
74+ < div className = "flex items-center justify-between" >
75+ < div className = "flex items-center gap-4" >
76+ < div className = "w-12 h-12 bg-white/20 rounded-xl flex items-center justify-center" >
77+ < span className = "text-2xl" > ⛓️</ span >
78+ </ div >
79+ < div >
80+ < h3 className = "text-2xl font-bold text-white" > Block #{ blockHeader . Height . toLocaleString ( ) } </ h3 >
81+ < p className = "text-white/80" > { blockHeader . Network } </ p >
82+ </ div >
83+ </ div >
84+ < button
85+ onClick = { onClose }
86+ className = "text-white/80 hover:text-white transition-colors"
87+ >
88+ < svg className = "w-6 h-6" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
89+ < path strokeLinecap = "round" strokeLinejoin = "round" strokeWidth = { 2 } d = "M6 18L18 6M6 6l12 12" />
90+ </ svg >
91+ </ button >
92+ </ div >
93+ </ div >
94+
95+ { /* Content */ }
96+ < div className = "bg-gray-50 px-6 py-6" >
97+ < div className = "grid grid-cols-1 lg:grid-cols-2 gap-6" >
98+ { /* Block Information */ }
99+ < div className = "bg-white rounded-xl p-6 shadow-sm" >
100+ < h4 className = "text-lg font-semibold text-gray-900 mb-4 flex items-center gap-2" >
101+ < svg className = "w-5 h-5 text-[#1B1EA9]" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
102+ < path strokeLinecap = "round" strokeLinejoin = "round" strokeWidth = { 2 } d = "M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
103+ </ svg >
104+ Block Header
105+ </ h4 >
106+
107+ < div className = "space-y-3" >
108+ < div >
109+ < label className = "text-sm text-gray-500" > Hash</ label >
110+ < div className = "flex items-center gap-2" >
111+ < p className = "font-mono text-sm text-gray-900 break-all" > { blockHeader . Hash } </ p >
112+ < button
113+ onClick = { ( ) => navigator . clipboard . writeText ( blockHeader . Hash ) }
114+ className = "flex-shrink-0 text-gray-400 hover:text-gray-600"
115+ >
116+ < svg className = "w-4 h-4" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
117+ < path strokeLinecap = "round" strokeLinejoin = "round" strokeWidth = { 2 } d = "M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
118+ </ svg >
119+ </ button >
120+ </ div >
121+ </ div >
122+
123+ < div >
124+ < label className = "text-sm text-gray-500" > Previous Hash</ label >
125+ < p className = "font-mono text-sm text-gray-900 break-all" > { blockHeader . PreviousHash } </ p >
126+ </ div >
127+
128+ < div >
129+ < label className = "text-sm text-gray-500" > Merkle Root</ label >
130+ < p className = "font-mono text-sm text-gray-900 break-all" > { blockHeader . MerkleRoot } </ p >
131+ </ div >
132+
133+ < div className = "grid grid-cols-2 gap-4" >
134+ < div >
135+ < label className = "text-sm text-gray-500" > Version</ label >
136+ < p className = "text-gray-900" > { blockHeader . Version } </ p >
137+ </ div >
138+ < div >
139+ < label className = "text-sm text-gray-500" > Nonce</ label >
140+ < p className = "text-gray-900" > { blockHeader . Nonce . toLocaleString ( ) } </ p >
141+ </ div >
142+ </ div >
143+ </ div >
144+ </ div >
145+
146+ { /* Mining Information */ }
147+ < div className = "bg-white rounded-xl p-6 shadow-sm" >
148+ < h4 className = "text-lg font-semibold text-gray-900 mb-4 flex items-center gap-2" >
149+ < svg className = "w-5 h-5 text-[#FF2DAF]" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
150+ < path strokeLinecap = "round" strokeLinejoin = "round" strokeWidth = { 2 } d = "M13 10V3L4 14h7v7l9-11h-7z" />
151+ </ svg >
152+ Mining Details
153+ </ h4 >
154+
155+ < div className = "space-y-3" >
156+ < div >
157+ < label className = "text-sm text-gray-500" > Timestamp</ label >
158+ < p className = "text-gray-900" > { formatTimestamp ( blockHeader . Timestamp ) } </ p >
159+ < p className = "text-sm text-gray-600" > { getTimeSince ( blockHeader . Timestamp ) } </ p >
160+ </ div >
161+
162+ < div >
163+ < label className = "text-sm text-gray-500" > Difficulty</ label >
164+ < p className = "text-gray-900" > { calculateDifficulty ( blockHeader . Bits ) } </ p >
165+ < p className = "text-sm text-gray-600" > Bits: 0x{ blockHeader . Bits . toString ( 16 ) } </ p >
166+ </ div >
167+
168+ < div >
169+ < label className = "text-sm text-gray-500" > Received At</ label >
170+ < p className = "text-gray-900" > { new Date ( blockHeader . ReceivedAt ) . toLocaleString ( ) } </ p >
171+ </ div >
172+ </ div >
173+ </ div >
174+ </ div >
175+
176+ { /* Coinbase Transaction Section */ }
177+ < div className = "mt-6 bg-white rounded-xl p-6 shadow-sm" >
178+ < h4 className = "text-lg font-semibold text-gray-900 mb-4 flex items-center gap-2" >
179+ < svg className = "w-5 h-5 text-[#1B1EA9]" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
180+ < path strokeLinecap = "round" strokeLinejoin = "round" strokeWidth = { 2 } d = "M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
181+ </ svg >
182+ Coinbase Transaction
183+ </ h4 >
184+
185+ { loading ? (
186+ < div className = "flex items-center justify-center py-8" >
187+ < div className = "animate-spin rounded-full h-8 w-8 border-2 border-[#1B1EA9] border-t-transparent" > </ div >
188+ </ div >
189+ ) : coinbaseData ? (
190+ < div className = "space-y-3" >
191+ { /* Coinbase data will be displayed here when API is available */ }
192+ < div className = "text-gray-600" > Coinbase transaction details will be available soon.</ div >
193+ </ div >
194+ ) : (
195+ < div className = "text-center py-8" >
196+ < div className = "inline-flex items-center justify-center w-16 h-16 bg-gray-100 rounded-full mb-4" >
197+ < svg className = "w-8 h-8 text-gray-400" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
198+ < path strokeLinecap = "round" strokeLinejoin = "round" strokeWidth = { 2 } d = "M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
199+ </ svg >
200+ </ div >
201+ < p className = "text-gray-600" > Coinbase transaction data not yet available</ p >
202+ < p className = "text-sm text-gray-500 mt-2" > This feature requires additional API endpoints</ p >
203+ </ div >
204+ ) }
205+ </ div >
206+ </ div >
207+
208+ { /* Footer */ }
209+ < div className = "bg-gray-100 px-6 py-4 flex justify-end" >
210+ < button
211+ onClick = { onClose }
212+ className = "px-6 py-2 bg-gradient-to-r from-[#1B1EA9] to-[#FF2DAF] text-white rounded-lg hover:from-[#1515A0] hover:to-[#FF1FA0] transition-all"
213+ >
214+ Close
215+ </ button >
216+ </ div >
217+ </ div >
218+ </ div >
219+ </ div >
220+ ) ;
221+ } ;
0 commit comments