1
1
import { StatusBar } from "expo-status-bar" ;
2
2
import { StyleSheet , View , Linking } from "react-native" ;
3
3
import { useLocalSearchParams , Stack } from "expo-router" ;
4
- import React , { useState } from "react" ;
4
+ import React from "react" ;
5
5
import {
6
6
Layout ,
7
7
Text ,
@@ -14,12 +14,42 @@ import {
14
14
import { useFacade } from "../../data/facades" ;
15
15
import { CurrencyUtils } from "@ironfish/sdk" ;
16
16
import { useQueries } from "@tanstack/react-query" ;
17
- import { IRON_ASSET_ID_HEX } from "../../data/constants " ;
17
+ import { setStringAsync } from "expo-clipboard " ;
18
18
19
19
const ExternalLinkIcon = ( props : IconProps ) => (
20
20
< Icon { ...props } name = "external-link-outline" />
21
21
) ;
22
22
23
+ const formatTimestamp = ( date : Date ) => {
24
+ return date . toLocaleString ( undefined , {
25
+ year : "numeric" ,
26
+ month : "short" ,
27
+ day : "numeric" ,
28
+ hour : "2-digit" ,
29
+ minute : "2-digit" ,
30
+ } ) ;
31
+ } ;
32
+
33
+ const CopyableText = ( { text, style } : { text : string ; style ?: any } ) => {
34
+ const copyToClipboard = async ( ) => {
35
+ await setStringAsync ( text ) ;
36
+ } ;
37
+
38
+ return (
39
+ < View style = { styles . copyContainer } >
40
+ < Text style = { [ style , styles . copyText ] } selectable >
41
+ { text }
42
+ </ Text >
43
+ < Button
44
+ appearance = "ghost"
45
+ accessoryLeft = { ( props ) => < Icon { ...props } name = "copy-outline" /> }
46
+ onPress = { copyToClipboard }
47
+ style = { styles . copyButton }
48
+ />
49
+ </ View >
50
+ ) ;
51
+ } ;
52
+
23
53
export default function TransactionDetails ( ) {
24
54
const { hash } = useLocalSearchParams < { hash : string } > ( ) ;
25
55
const facade = useFacade ( ) ;
@@ -51,7 +81,33 @@ export default function TransactionDetails() {
51
81
if ( transactionQuery . isLoading || assetQueries . some ( ( q ) => q . isLoading ) ) {
52
82
return (
53
83
< Layout style = { styles . container } >
54
- < Spinner size = "large" />
84
+ < Stack . Screen
85
+ options = { {
86
+ headerTitle : "" ,
87
+ headerTransparent : true ,
88
+ } }
89
+ />
90
+
91
+ { /* Header Section Skeleton */ }
92
+ < View style = { styles . headerSection } >
93
+ < Spinner size = "large" style = { styles . spinner } />
94
+ < Text category = "s1" appearance = "hint" >
95
+ Loading transaction details...
96
+ </ Text >
97
+ </ View >
98
+
99
+ { /* Details Section Skeleton */ }
100
+ < View style = { styles . detailsSection } >
101
+ { [ 1 , 2 , 3 , 4 ] . map ( ( i ) => (
102
+ < React . Fragment key = { i } >
103
+ < View style = { styles . section } >
104
+ < View style = { styles . skeletonLabel } />
105
+ < View style = { styles . skeletonValue } />
106
+ </ View >
107
+ < Divider style = { styles . divider } />
108
+ </ React . Fragment >
109
+ ) ) }
110
+ </ View >
55
111
</ Layout >
56
112
) ;
57
113
}
@@ -65,9 +121,6 @@ export default function TransactionDetails() {
65
121
}
66
122
67
123
const transaction = transactionQuery . data ;
68
- console . log ( `########################` ) ;
69
- console . log ( "transaction" , transaction ) ;
70
- console . log ( `########################` ) ;
71
124
72
125
// Create a map of assetId to asset data
73
126
const assetMap = new Map ( ) ;
@@ -77,33 +130,27 @@ export default function TransactionDetails() {
77
130
}
78
131
} ) ;
79
132
80
- // Calculate total amount for each asset
81
- const assetAmounts = transaction . assetBalanceDeltas . reduce (
82
- ( acc , delta ) => {
83
- const asset = assetMap . get ( delta . assetId ) ;
84
- console . log ( "asset" , asset ) ;
85
- const assetName =
86
- asset ?. verification . status === "verified"
87
- ? asset . verification . symbol
88
- : ( asset ?. name ?? delta . assetId ) ;
89
-
90
- return {
91
- ...acc ,
92
- [ assetName ] : ( acc [ assetName ] || 0n ) + BigInt ( delta . delta ) ,
93
- } ;
94
- } ,
95
- { } as Record < string , bigint > ,
96
- ) ;
97
-
98
- console . log ( `$$$$$$` ) ;
99
- console . log ( "assetAmounts" , assetAmounts ) ;
100
- console . log ( `$$$$$$` ) ;
101
-
102
- // Get the main asset and transaction type
103
- const mainAssetAmount = Object . entries ( assetAmounts ) [ 0 ] || [ ] ;
104
- const [ mainAssetName , mainAmount ] = mainAssetAmount ;
133
+ // TEMPORARY: Currently assuming the first balance delta represents the main transaction amount
134
+ const mainDelta = transaction . assetBalanceDeltas [ 0 ] ;
135
+ const asset = assetMap . get ( mainDelta . assetId ) ;
136
+ const mainAssetName =
137
+ asset ?. verification . status === "verified"
138
+ ? asset . verification . symbol
139
+ : ( asset ?. name ?? mainDelta . assetId ) ;
140
+ const mainAmount = BigInt ( mainDelta . delta ) ;
105
141
const isReceived = mainAmount > 0n ;
106
142
143
+ if ( transactionQuery . error ) {
144
+ return (
145
+ < Layout style = { [ styles . container , styles . centerContent ] } >
146
+ < Text category = "h6" style = { styles . errorText } >
147
+ Failed to load transaction
148
+ </ Text >
149
+ < Button onPress = { ( ) => transactionQuery . refetch ( ) } > Try Again</ Button >
150
+ </ Layout >
151
+ ) ;
152
+ }
153
+
107
154
return (
108
155
< Layout style = { styles . container } >
109
156
< Stack . Screen
@@ -115,19 +162,35 @@ export default function TransactionDetails() {
115
162
116
163
{ /* Header Section */ }
117
164
< View style = { styles . headerSection } >
118
- < Text category = "h3" style = { styles . transactionType } >
119
- { isReceived ? "Received" : "Sent" }
120
- </ Text >
121
- < Text category = "h2" style = { styles . mainAmount } >
122
- { CurrencyUtils . render (
123
- ( mainAmount < 0n ? - mainAmount : mainAmount ) . toString ( ) ,
124
- false ,
125
- ) } { " " }
126
- { mainAssetName }
127
- </ Text >
128
- < Text category = "s1" appearance = "hint" style = { styles . timestamp } >
129
- { new Date ( transaction . timestamp ) . toLocaleString ( ) }
130
- </ Text >
165
+ { transaction . notes [ 0 ] ?. sender === transaction . notes [ 0 ] ?. owner ? (
166
+ < >
167
+ < Text category = "h3" style = { styles . transactionType } >
168
+ Self Transaction
169
+ </ Text >
170
+ < Text category = "s1" appearance = "hint" style = { styles . timestamp } >
171
+ Balances may not be accurately shown
172
+ </ Text >
173
+ < Text category = "s1" appearance = "hint" style = { styles . timestamp } >
174
+ { formatTimestamp ( new Date ( transaction . timestamp ) ) }
175
+ </ Text >
176
+ </ >
177
+ ) : (
178
+ < >
179
+ < Text category = "h3" style = { styles . transactionType } >
180
+ { isReceived ? "Received" : "Sent" }
181
+ </ Text >
182
+ < Text category = "h2" style = { styles . mainAmount } >
183
+ { CurrencyUtils . render (
184
+ ( mainAmount < 0n ? - mainAmount : mainAmount ) . toString ( ) ,
185
+ false ,
186
+ ) } { " " }
187
+ { mainAssetName }
188
+ </ Text >
189
+ < Text category = "s1" appearance = "hint" style = { styles . timestamp } >
190
+ { formatTimestamp ( new Date ( transaction . timestamp ) ) }
191
+ </ Text >
192
+ </ >
193
+ ) }
131
194
</ View >
132
195
133
196
{ /* Transaction Details */ }
@@ -136,11 +199,14 @@ export default function TransactionDetails() {
136
199
< Text category = "s1" style = { styles . label } >
137
200
{ isReceived ? "From" : "To" }
138
201
</ Text >
139
- < Text style = { styles . value } selectable >
140
- { isReceived
141
- ? transaction . notes [ 0 ] ?. sender
142
- : transaction . notes [ 0 ] ?. owner }
143
- </ Text >
202
+ < CopyableText
203
+ text = {
204
+ isReceived
205
+ ? transaction . notes [ 0 ] ?. sender
206
+ : transaction . notes [ 0 ] ?. owner
207
+ }
208
+ style = { styles . value }
209
+ />
144
210
</ View >
145
211
146
212
< Divider style = { styles . divider } />
@@ -149,9 +215,7 @@ export default function TransactionDetails() {
149
215
< Text category = "s1" style = { styles . label } >
150
216
Transaction Hash
151
217
</ Text >
152
- < Text style = { styles . value } selectable >
153
- { hash }
154
- </ Text >
218
+ < CopyableText text = { hash } style = { styles . value } />
155
219
</ View >
156
220
157
221
< Divider style = { styles . divider } />
@@ -215,8 +279,8 @@ const styles = StyleSheet.create({
215
279
flex : 1 ,
216
280
} ,
217
281
headerSection : {
218
- padding : 32 ,
219
- paddingTop : 80 ,
282
+ padding : 24 ,
283
+ paddingTop : 100 ,
220
284
alignItems : "center" ,
221
285
backgroundColor : "#f5f5f5" ,
222
286
} ,
@@ -248,4 +312,39 @@ const styles = StyleSheet.create({
248
312
explorerButton : {
249
313
marginTop : 24 ,
250
314
} ,
315
+ copyContainer : {
316
+ flexDirection : "row" ,
317
+ alignItems : "center" ,
318
+ } ,
319
+ copyText : {
320
+ flex : 1 ,
321
+ } ,
322
+ copyButton : {
323
+ padding : 0 ,
324
+ marginLeft : 8 ,
325
+ } ,
326
+ centerContent : {
327
+ justifyContent : "center" ,
328
+ alignItems : "center" ,
329
+ } ,
330
+ errorText : {
331
+ marginBottom : 16 ,
332
+ textAlign : "center" ,
333
+ } ,
334
+ spinner : {
335
+ marginBottom : 16 ,
336
+ } ,
337
+ skeletonLabel : {
338
+ height : 20 ,
339
+ width : 100 ,
340
+ backgroundColor : "#f5f5f5" ,
341
+ borderRadius : 4 ,
342
+ marginBottom : 8 ,
343
+ } ,
344
+ skeletonValue : {
345
+ height : 24 ,
346
+ backgroundColor : "#f5f5f5" ,
347
+ borderRadius : 4 ,
348
+ width : "100%" ,
349
+ } ,
251
350
} ) ;
0 commit comments