@@ -9,6 +9,13 @@ import { Button } from "@/components/ui/button";
9
9
import { CodeClient } from "@/components/ui/code/code.client" ;
10
10
import { Form , FormField , FormItem , FormMessage } from "@/components/ui/form" ;
11
11
import { Input } from "@/components/ui/input" ;
12
+ import {
13
+ Select ,
14
+ SelectContent ,
15
+ SelectItem ,
16
+ SelectTrigger ,
17
+ SelectValue ,
18
+ } from "@/components/ui/select" ;
12
19
import { Separator } from "@/components/ui/separator" ;
13
20
import { ToolTipLabel } from "@/components/ui/tooltip" ;
14
21
import { cn } from "@/lib/utils" ;
@@ -27,7 +34,11 @@ import {
27
34
import Link from "next/link" ;
28
35
import type { OpenAPIV3 } from "openapi-types" ;
29
36
import { useEffect , useMemo , useState } from "react" ;
30
- import { type UseFormReturn , useForm } from "react-hook-form" ;
37
+ import {
38
+ type ControllerRenderProps ,
39
+ type UseFormReturn ,
40
+ useForm ,
41
+ } from "react-hook-form" ;
31
42
import { z } from "zod" ;
32
43
import { useTrack } from "../../../../../../hooks/analytics/useTrack" ;
33
44
import { getVercelEnv } from "../../../../../../lib/vercel-utils" ;
@@ -412,14 +423,20 @@ function RequestConfigSection(props: {
412
423
supportedChainIds : number [ ] ;
413
424
} ) {
414
425
const pathVariables = props . parameters . filter ( ( param ) => param . in === "path" ) ;
415
-
416
426
const queryParams = props . parameters . filter ( ( param ) => param . in === "query" ) ;
427
+ const showError =
428
+ ! props . form . formState . isValid &&
429
+ props . form . formState . isDirty &&
430
+ props . form . formState . isSubmitted ;
417
431
418
432
return (
419
433
< div className = "flex grow flex-col overflow-hidden" >
420
- < div className = "flex min-h-[60px] items-center gap-2 border-b p-4 text-sm" >
421
- < ArrowUpRightIcon className = "size-5" />
422
- Request
434
+ < div className = "flex min-h-[60px] items-center justify-between gap-2 border-b p-4 text-sm" >
435
+ < div className = "flex items-center gap-2" >
436
+ < ArrowUpRightIcon className = "size-5" />
437
+ Request
438
+ </ div >
439
+ { showError && < Badge variant = "destructive" > Invalid Request</ Badge > }
423
440
</ div >
424
441
425
442
< ScrollShadow className = "flex-1" scrollableClassName = "max-h-full" >
@@ -474,7 +491,25 @@ function ParameterSection(props: {
474
491
? param . schema . description
475
492
: undefined ;
476
493
494
+ const example =
495
+ param . schema && "type" in param . schema
496
+ ? param . schema . example
497
+ : undefined ;
498
+ const exampleToShow =
499
+ typeof example === "string" || typeof example === "number"
500
+ ? example
501
+ : undefined ;
502
+
503
+ const showTip = description !== undefined || example !== undefined ;
504
+
477
505
const hasError = ! ! props . form . formState . errors [ param . name ] ;
506
+
507
+ const placeholder = url . includes ( `{${ param . name } }` )
508
+ ? `{${ param . name } }`
509
+ : url . includes ( `:${ param . name } ` )
510
+ ? `:${ param . name } `
511
+ : "Value" ;
512
+
478
513
return (
479
514
< FormField
480
515
key = { param . name }
@@ -530,23 +565,39 @@ function ParameterSection(props: {
530
565
/>
531
566
) : (
532
567
< >
533
- < Input
534
- { ...field }
535
- className = { cn (
536
- "h-auto truncate rounded-none border-0 bg-transparent py-3 font-mono text-sm focus-visible:ring-0 focus-visible:ring-offset-0" ,
537
- description && "lg:pr-10" ,
538
- hasError && "text-destructive-text" ,
539
- ) }
540
- placeholder = {
541
- url . includes ( `{${ param . name } }` )
542
- ? `{${ param . name } }`
543
- : url . includes ( `:${ param . name } ` )
544
- ? `:${ param . name } `
545
- : "Value"
546
- }
568
+ < ParameterInput
569
+ param = { param }
570
+ field = { field }
571
+ showTip = { showTip }
572
+ hasError = { hasError }
573
+ placeholder = { placeholder }
547
574
/>
548
- { description && (
549
- < ToolTipLabel label = { description } >
575
+
576
+ { showTip && (
577
+ < ToolTipLabel
578
+ hoverable
579
+ contentClassName = "max-w-[100vw] break-all"
580
+ label = {
581
+ < div className = "flex flex-col gap-2" >
582
+ { description && (
583
+ < p className = "text-foreground" >
584
+ { description }
585
+ </ p >
586
+ ) }
587
+
588
+ { exampleToShow !== undefined && (
589
+ < div >
590
+ < p className = "mb-1 text-muted-foreground" >
591
+ Example:{ " " }
592
+ < span className = "font-mono" >
593
+ { exampleToShow }
594
+ </ span >
595
+ </ p >
596
+ </ div >
597
+ ) }
598
+ </ div >
599
+ }
600
+ >
550
601
< Button
551
602
asChild
552
603
variant = "ghost"
@@ -573,6 +624,66 @@ function ParameterSection(props: {
573
624
) ;
574
625
}
575
626
627
+ function ParameterInput ( props : {
628
+ param : OpenAPIV3 . ParameterObject ;
629
+ field : ControllerRenderProps <
630
+ {
631
+ [ x : string ] : string | number ;
632
+ } ,
633
+ string
634
+ > ;
635
+ showTip : boolean ;
636
+ hasError : boolean ;
637
+ placeholder : string ;
638
+ } ) {
639
+ const { param, field, showTip, hasError, placeholder } = props ;
640
+
641
+ if ( param . schema && "type" in param . schema && param . schema . enum ) {
642
+ const { value, onChange, ...restField } = field ;
643
+ return (
644
+ < Select
645
+ { ...restField }
646
+ value = { value . toString ( ) }
647
+ onValueChange = { ( v ) => {
648
+ onChange ( { target : { value : v } } ) ;
649
+ } }
650
+ >
651
+ < SelectTrigger
652
+ className = { cn (
653
+ "border-none bg-transparent pr-10 font-mono focus:ring-0 focus:ring-offset-0" ,
654
+ value === "" && "text-muted-foreground" ,
655
+ ) }
656
+ chevronClassName = "hidden"
657
+ >
658
+ < SelectValue placeholder = "Select" />
659
+ </ SelectTrigger >
660
+
661
+ < SelectContent className = "font-mono" >
662
+ { param . schema . enum . map ( ( val ) => {
663
+ return (
664
+ < SelectItem value = { val } key = { val } >
665
+ { val }
666
+ </ SelectItem >
667
+ ) ;
668
+ } ) }
669
+ </ SelectContent >
670
+ </ Select >
671
+ ) ;
672
+ }
673
+
674
+ return (
675
+ < Input
676
+ { ...field }
677
+ className = { cn (
678
+ "h-auto truncate rounded-none border-0 bg-transparent py-3 font-mono text-sm focus-visible:ring-0 focus-visible:ring-offset-0" ,
679
+ showTip && "lg:pr-10" ,
680
+ hasError && "text-destructive-text" ,
681
+ ) }
682
+ placeholder = { placeholder }
683
+ />
684
+ ) ;
685
+ }
686
+
576
687
function formatMilliseconds ( ms : number ) {
577
688
if ( ms < 1000 ) {
578
689
return `${ Math . round ( ms ) } ms` ;
@@ -677,6 +788,21 @@ function openAPIV3ParamToZodFormSchema(param: BlueprintParameter) {
677
788
return ;
678
789
}
679
790
791
+ // if enum values
792
+ const enumValues = param . schema . enum ;
793
+ if ( enumValues ) {
794
+ const enumSchema = z . enum (
795
+ // @ts -expect-error - Its correct
796
+ enumValues ,
797
+ ) ;
798
+
799
+ if ( param . required ) {
800
+ return enumSchema ;
801
+ }
802
+
803
+ return enumSchema . or ( z . literal ( "" ) ) ;
804
+ }
805
+
680
806
switch ( param . schema . type ) {
681
807
case "integer" : {
682
808
const intSchema = z . coerce
0 commit comments