11"use client" ;
22
33import { ChevronDown } from "lucide-react" ;
4- import { useState } from "react" ;
4+ import React , { useState } from "react" ;
55import { Input } from "@/components/ui/input" ;
66import { Label } from "@/components/ui/label" ;
77import {
@@ -114,9 +114,187 @@ function SchemaBuilderField(props: FieldProps) {
114114 ) ;
115115}
116116
117- const FIELD_RENDERERS : Record <
118- ActionConfigFieldBase [ "type" ] ,
119- React . ComponentType < FieldProps >
117+ type AbiFunctionSelectProps = FieldProps & {
118+ abiValue : string ;
119+ } ;
120+
121+ function AbiFunctionSelectField ( {
122+ field,
123+ value,
124+ onChange,
125+ disabled,
126+ abiValue,
127+ } : AbiFunctionSelectProps ) {
128+ // Parse ABI and extract functions
129+ const functions = React . useMemo ( ( ) => {
130+ if ( ! abiValue || abiValue . trim ( ) === "" ) {
131+ return [ ] ;
132+ }
133+
134+ try {
135+ const abi = JSON . parse ( abiValue ) ;
136+ if ( ! Array . isArray ( abi ) ) {
137+ return [ ] ;
138+ }
139+
140+ // Extract all functions from the ABI
141+ return abi
142+ . filter ( ( item ) => item . type === "function" )
143+ . map ( ( func ) => {
144+ const inputs = func . inputs || [ ] ;
145+ const params = inputs
146+ . map (
147+ ( input : { name : string ; type : string } ) =>
148+ `${ input . type } ${ input . name } `
149+ )
150+ . join ( ", " ) ;
151+ return {
152+ name : func . name ,
153+ label : `${ func . name } (${ params } )` ,
154+ stateMutability : func . stateMutability || "nonpayable" ,
155+ } ;
156+ } ) ;
157+ } catch {
158+ return [ ] ;
159+ }
160+ } , [ abiValue ] ) ;
161+
162+ if ( functions . length === 0 ) {
163+ return (
164+ < div className = "rounded-md border border-dashed p-3 text-center text-muted-foreground text-sm" >
165+ { abiValue
166+ ? "No functions found in ABI"
167+ : "Enter ABI above to see available functions" }
168+ </ div >
169+ ) ;
170+ }
171+
172+ return (
173+ < Select disabled = { disabled } onValueChange = { onChange } value = { value } >
174+ < SelectTrigger className = "w-full" id = { field . key } >
175+ < SelectValue placeholder = { field . placeholder || "Select a function" } />
176+ </ SelectTrigger >
177+ < SelectContent >
178+ { functions . map ( ( func ) => (
179+ < SelectItem key = { func . name } value = { func . name } >
180+ < div className = "flex flex-col" >
181+ < span > { func . label } </ span >
182+ < span className = "text-muted-foreground text-xs" >
183+ { func . stateMutability }
184+ </ span >
185+ </ div >
186+ </ SelectItem >
187+ ) ) }
188+ </ SelectContent >
189+ </ Select >
190+ ) ;
191+ }
192+
193+ type AbiFunctionArgsProps = FieldProps & {
194+ abiValue : string ;
195+ functionValue : string ;
196+ } ;
197+
198+ function AbiFunctionArgsField ( {
199+ field,
200+ value,
201+ onChange,
202+ disabled,
203+ abiValue,
204+ functionValue,
205+ } : AbiFunctionArgsProps ) {
206+ // Parse the function inputs from the ABI
207+ const functionInputs = React . useMemo ( ( ) => {
208+ if (
209+ ! ( abiValue && functionValue ) ||
210+ abiValue . trim ( ) === "" ||
211+ functionValue . trim ( ) === ""
212+ ) {
213+ return [ ] ;
214+ }
215+
216+ try {
217+ const abi = JSON . parse ( abiValue ) ;
218+ if ( ! Array . isArray ( abi ) ) {
219+ return [ ] ;
220+ }
221+
222+ const func = abi . find (
223+ ( item ) => item . type === "function" && item . name === functionValue
224+ ) ;
225+
226+ if ( ! func ?. inputs ) {
227+ return [ ] ;
228+ }
229+
230+ return func . inputs . map ( ( input : { name : string ; type : string } ) => ( {
231+ name : input . name || "unnamed" ,
232+ type : input . type ,
233+ } ) ) ;
234+ } catch {
235+ return [ ] ;
236+ }
237+ } , [ abiValue , functionValue ] ) ;
238+
239+ // Parse current value (JSON array) into individual arg values
240+ const argValues = React . useMemo ( ( ) => {
241+ if ( ! value || value . trim ( ) === "" ) {
242+ return [ ] ;
243+ }
244+ try {
245+ const parsed = JSON . parse ( value ) ;
246+ return Array . isArray ( parsed ) ? parsed : [ ] ;
247+ } catch {
248+ return [ ] ;
249+ }
250+ } , [ value ] ) ;
251+
252+ // Handle individual arg change
253+ const handleArgChange = ( index : number , newValue : string ) => {
254+ const newArgs = [ ...argValues ] ;
255+ // Ensure array is long enough
256+ while ( newArgs . length <= index ) {
257+ newArgs . push ( "" ) ;
258+ }
259+ newArgs [ index ] = newValue ;
260+ onChange ( JSON . stringify ( newArgs ) ) ;
261+ } ;
262+
263+ if ( functionInputs . length === 0 ) {
264+ return (
265+ < div className = "rounded-md border border-dashed p-3 text-center text-muted-foreground text-sm" >
266+ { functionValue
267+ ? "This function has no parameters"
268+ : "Select a function above to see parameters" }
269+ </ div >
270+ ) ;
271+ }
272+
273+ return (
274+ < div className = "space-y-3" >
275+ { functionInputs . map (
276+ ( input : { name : string ; type : string } , index : number ) => (
277+ < div className = "space-y-1.5" key = { `${ field . key } -arg-${ index } ` } >
278+ < Label className = "ml-1 text-xs" htmlFor = { `${ field . key } -${ index } ` } >
279+ { input . name } { " " }
280+ < span className = "text-muted-foreground" > ({ input . type } )</ span >
281+ </ Label >
282+ < TemplateBadgeInput
283+ disabled = { disabled }
284+ id = { `${ field . key } -${ index } ` }
285+ onChange = { ( val ) => handleArgChange ( index , val as string ) }
286+ placeholder = { `Enter ${ input . type } value or {{NodeName.value}}` }
287+ value = { ( argValues [ index ] as string ) || "" }
288+ />
289+ </ div >
290+ )
291+ ) }
292+ </ div >
293+ ) ;
294+ }
295+
296+ const FIELD_RENDERERS : Partial <
297+ Record < ActionConfigFieldBase [ "type" ] , React . ComponentType < FieldProps > >
120298> = {
121299 "template-input" : TemplateInputField ,
122300 "template-textarea" : TemplateTextareaField ,
@@ -145,8 +323,58 @@ function renderField(
145323
146324 const value =
147325 ( config [ field . key ] as string | undefined ) || field . defaultValue || "" ;
326+
327+ // Special handling for abi-function-select
328+ if ( field . type === "abi-function-select" ) {
329+ const abiField = field . abiField || "abi" ;
330+ const abiValue = ( config [ abiField ] as string | undefined ) || "" ;
331+
332+ return (
333+ < div className = "space-y-2" key = { field . key } >
334+ < Label className = "ml-1" htmlFor = { field . key } >
335+ { field . label }
336+ </ Label >
337+ < AbiFunctionSelectField
338+ abiValue = { abiValue }
339+ disabled = { disabled }
340+ field = { field }
341+ onChange = { ( val ) => onUpdateConfig ( field . key , val ) }
342+ value = { value }
343+ />
344+ </ div >
345+ ) ;
346+ }
347+
348+ // Special handling for abi-function-args
349+ if ( field . type === "abi-function-args" ) {
350+ const abiField = field . abiField || "abi" ;
351+ const functionField = field . abiFunctionField || "abiFunction" ;
352+ const abiValue = ( config [ abiField ] as string | undefined ) || "" ;
353+ const functionValue = ( config [ functionField ] as string | undefined ) || "" ;
354+
355+ return (
356+ < div className = "space-y-2" key = { field . key } >
357+ < Label className = "ml-1" htmlFor = { field . key } >
358+ { field . label }
359+ </ Label >
360+ < AbiFunctionArgsField
361+ abiValue = { abiValue }
362+ disabled = { disabled }
363+ field = { field }
364+ functionValue = { functionValue }
365+ onChange = { ( val ) => onUpdateConfig ( field . key , val ) }
366+ value = { value }
367+ />
368+ </ div >
369+ ) ;
370+ }
371+
148372 const FieldRenderer = FIELD_RENDERERS [ field . type ] ;
149373
374+ if ( ! FieldRenderer ) {
375+ return null ;
376+ }
377+
150378 return (
151379 < div className = "space-y-2" key = { field . key } >
152380 < Label className = "ml-1" htmlFor = { field . key } >
@@ -155,7 +383,7 @@ function renderField(
155383 < FieldRenderer
156384 disabled = { disabled }
157385 field = { field }
158- onChange = { ( val ) => onUpdateConfig ( field . key , val ) }
386+ onChange = { ( val : unknown ) => onUpdateConfig ( field . key , val ) }
159387 value = { value }
160388 />
161389 </ div >
0 commit comments