11import { useSimulateTransaction } from '@solana/react-hooks' ;
2- import { type FormEvent , useState } from 'react' ;
2+ import { type ChangeEvent , type FormEvent , Suspense , useState } from 'react' ;
33
44import { Button } from './ui/button' ;
55import { Card , CardContent , CardDescription , CardFooter , CardHeader , CardTitle } from './ui/card' ;
66
77export function SimulateTransactionCard ( ) {
88 const [ payload , setPayload ] = useState ( '' ) ;
99 const [ submittedPayload , setSubmittedPayload ] = useState < string | null > ( null ) ;
10- const simulation = useSimulateTransaction ( submittedPayload ?? undefined , {
11- disabled : submittedPayload === null ,
12- } ) ;
10+
11+ const handlePayloadChange = ( event : ChangeEvent < HTMLTextAreaElement > ) => {
12+ setPayload ( event . target . value ) ;
13+ } ;
1314
1415 const handleSubmit = ( event : FormEvent < HTMLFormElement > ) => {
1516 event . preventDefault ( ) ;
@@ -22,6 +23,95 @@ export function SimulateTransactionCard() {
2223 setPayload ( '' ) ;
2324 } ;
2425
26+ const canClear = payload . trim ( ) !== '' || submittedPayload !== null ;
27+ const shouldSimulate = submittedPayload !== null ;
28+
29+ return (
30+ < Suspense
31+ fallback = {
32+ < SimulateTransactionCardShell
33+ canClear = { canClear }
34+ logOutput = { shouldSimulate ? 'Simulating transaction…' : 'No simulation yet.' }
35+ onClear = { handleClear }
36+ onPayloadChange = { handlePayloadChange }
37+ onSubmit = { handleSubmit }
38+ payload = { payload }
39+ />
40+ }
41+ >
42+ < SimulateTransactionCardContent
43+ canClear = { canClear }
44+ onClear = { handleClear }
45+ onPayloadChange = { handlePayloadChange }
46+ onSubmit = { handleSubmit }
47+ payload = { payload }
48+ submittedPayload = { submittedPayload }
49+ />
50+ </ Suspense >
51+ ) ;
52+ }
53+
54+ type SimulateTransactionCardContentProps = Readonly < {
55+ canClear : boolean ;
56+ onClear ( ) : void ;
57+ onPayloadChange ( event : ChangeEvent < HTMLTextAreaElement > ) : void ;
58+ onSubmit ( event : FormEvent < HTMLFormElement > ) : void ;
59+ payload : string ;
60+ submittedPayload : string | null ;
61+ } > ;
62+
63+ function SimulateTransactionCardContent ( {
64+ canClear,
65+ onClear,
66+ onPayloadChange,
67+ onSubmit,
68+ payload,
69+ submittedPayload,
70+ } : SimulateTransactionCardContentProps ) {
71+ const simulation = useSimulateTransaction ( submittedPayload ?? undefined , {
72+ disabled : submittedPayload === null ,
73+ } ) ;
74+
75+ const logOutput =
76+ simulation . status === 'idle' && ! submittedPayload
77+ ? 'No simulation yet.'
78+ : simulation . status === 'error'
79+ ? `Simulation failed: ${ formatError ( simulation . error ) } `
80+ : simulation . logs . length
81+ ? simulation . logs . map ( ( log , index ) => `${ index + 1 } . ${ log } ` ) . join ( '\n' )
82+ : 'Simulation succeeded with no logs.' ;
83+
84+ return (
85+ < SimulateTransactionCardShell
86+ canClear = { canClear }
87+ logOutput = { logOutput }
88+ onClear = { onClear }
89+ onPayloadChange = { onPayloadChange }
90+ onSubmit = { onSubmit }
91+ payload = { payload }
92+ />
93+ ) ;
94+ }
95+
96+ type SimulateTransactionCardShellProps = Readonly < {
97+ canClear : boolean ;
98+ logOutput : string ;
99+ onClear ( ) : void ;
100+ onPayloadChange ( event : ChangeEvent < HTMLTextAreaElement > ) : void ;
101+ onSubmit ( event : FormEvent < HTMLFormElement > ) : void ;
102+ payload : string ;
103+ } > ;
104+
105+ function SimulateTransactionCardShell ( {
106+ canClear,
107+ logOutput,
108+ onClear,
109+ onPayloadChange,
110+ onSubmit,
111+ payload,
112+ } : SimulateTransactionCardShellProps ) {
113+ const isSimulateDisabled = payload . trim ( ) === '' ;
114+
25115 return (
26116 < Card >
27117 < CardHeader >
@@ -33,7 +123,7 @@ export function SimulateTransactionCard() {
33123 </ CardDescription >
34124 </ div >
35125 </ CardHeader >
36- < form onSubmit = { handleSubmit } >
126+ < form onSubmit = { onSubmit } >
37127 < CardContent className = "space-y-4" >
38128 < div className = "space-y-2 text-sm text-muted-foreground" >
39129 < label htmlFor = "simulation-wire" className = "text-foreground" >
@@ -42,7 +132,7 @@ export function SimulateTransactionCard() {
42132 < textarea
43133 className = "min-h-[120px] w-full rounded-lg border border-border bg-background p-3 font-mono text-xs outline-none focus-visible:ring-2 focus-visible:ring-ring/40"
44134 id = "simulation-wire"
45- onChange = { ( event ) => setPayload ( event . target . value ) }
135+ onChange = { onPayloadChange }
46136 placeholder = "Paste a serialized transaction"
47137 value = { payload }
48138 />
@@ -51,26 +141,15 @@ export function SimulateTransactionCard() {
51141 payload, then click simulate.
52142 </ p >
53143 </ div >
54- < div aria-live = "polite" className = "log-panel max-h-48 overflow-auto" >
55- { simulation . status === 'idle' && ! submittedPayload
56- ? 'No simulation yet.'
57- : simulation . status === 'error'
58- ? `Simulation failed: ${ formatError ( simulation . error ) } `
59- : simulation . logs ?. length
60- ? simulation . logs . map ( ( log , index ) => `${ index + 1 } . ${ log } ` ) . join ( '\n' )
61- : 'Simulation succeeded with no logs.' }
144+ < div aria-live = "polite" className = "log-panel max-h-48 overflow-auto whitespace-pre-wrap" >
145+ { logOutput }
62146 </ div >
63147 </ CardContent >
64148 < CardFooter className = "flex flex-wrap gap-2" >
65- < Button disabled = { payload . trim ( ) === '' } type = "submit" >
149+ < Button disabled = { isSimulateDisabled } type = "submit" >
66150 Simulate
67151 </ Button >
68- < Button
69- disabled = { ! payload && ! submittedPayload }
70- onClick = { handleClear }
71- type = "button"
72- variant = "ghost"
73- >
152+ < Button disabled = { ! canClear } onClick = { onClear } type = "button" variant = "ghost" >
74153 Clear
75154 </ Button >
76155 </ CardFooter >
0 commit comments