|  | 
|  | 1 | +"use client"; | 
|  | 2 | + | 
|  | 3 | +import type React from "react"; | 
|  | 4 | +import { useId, useState } from "react"; | 
|  | 5 | +import { defineChain } from "thirdweb/chains"; | 
|  | 6 | +import { BridgeNetworkSelector } from "@/components/blocks/NetworkSelectors"; | 
|  | 7 | +import { Input } from "@/components/ui/input"; | 
|  | 8 | +import { Label } from "@/components/ui/label"; | 
|  | 9 | +import { | 
|  | 10 | +  Select, | 
|  | 11 | +  SelectContent, | 
|  | 12 | +  SelectItem, | 
|  | 13 | +  SelectTrigger, | 
|  | 14 | +  SelectValue, | 
|  | 15 | +} from "@/components/ui/select"; | 
|  | 16 | +import { TokenSelector } from "@/components/ui/TokenSelector"; | 
|  | 17 | +import { THIRDWEB_CLIENT } from "@/lib/client"; | 
|  | 18 | +import type { TokenMetadata } from "@/lib/types"; | 
|  | 19 | +import type { X402PlaygroundOptions } from "./types"; | 
|  | 20 | + | 
|  | 21 | +export function X402LeftSection(props: { | 
|  | 22 | +  options: X402PlaygroundOptions; | 
|  | 23 | +  setOptions: React.Dispatch<React.SetStateAction<X402PlaygroundOptions>>; | 
|  | 24 | +}) { | 
|  | 25 | +  const { options, setOptions } = props; | 
|  | 26 | + | 
|  | 27 | +  // Local state for chain and token selection | 
|  | 28 | +  const [selectedChain, setSelectedChain] = useState<number | undefined>(() => { | 
|  | 29 | +    return options.chain?.id; | 
|  | 30 | +  }); | 
|  | 31 | + | 
|  | 32 | +  const [selectedToken, setSelectedToken] = useState< | 
|  | 33 | +    { chainId: number; address: string } | undefined | 
|  | 34 | +  >(() => { | 
|  | 35 | +    if (options.tokenAddress && options.chain?.id) { | 
|  | 36 | +      return { | 
|  | 37 | +        address: options.tokenAddress, | 
|  | 38 | +        chainId: options.chain.id, | 
|  | 39 | +      }; | 
|  | 40 | +    } | 
|  | 41 | +    return undefined; | 
|  | 42 | +  }); | 
|  | 43 | + | 
|  | 44 | +  const chainId = useId(); | 
|  | 45 | +  const tokenId = useId(); | 
|  | 46 | +  const amountId = useId(); | 
|  | 47 | +  const payToId = useId(); | 
|  | 48 | +  const waitUntilId = useId(); | 
|  | 49 | + | 
|  | 50 | +  const handleChainChange = (chainId: number) => { | 
|  | 51 | +    setSelectedChain(chainId); | 
|  | 52 | +    // Clear token selection when chain changes | 
|  | 53 | +    setSelectedToken(undefined); | 
|  | 54 | + | 
|  | 55 | +    setOptions((v) => ({ | 
|  | 56 | +      ...v, | 
|  | 57 | +      chain: defineChain(chainId), | 
|  | 58 | +      tokenAddress: "0x0000000000000000000000000000000000000000" as const, | 
|  | 59 | +      tokenSymbol: "", | 
|  | 60 | +      tokenDecimals: 18, | 
|  | 61 | +    })); | 
|  | 62 | +  }; | 
|  | 63 | + | 
|  | 64 | +  const handleTokenChange = (token: TokenMetadata) => { | 
|  | 65 | +    setSelectedToken({ | 
|  | 66 | +      address: token.address, | 
|  | 67 | +      chainId: selectedChain!, | 
|  | 68 | +    }); | 
|  | 69 | + | 
|  | 70 | +    setOptions((v) => ({ | 
|  | 71 | +      ...v, | 
|  | 72 | +      tokenAddress: token.address as `0x${string}`, | 
|  | 73 | +      tokenSymbol: token.symbol ?? "", | 
|  | 74 | +      tokenDecimals: token.decimals ?? 18, | 
|  | 75 | +    })); | 
|  | 76 | +  }; | 
|  | 77 | + | 
|  | 78 | +  const handleAmountChange = (e: React.ChangeEvent<HTMLInputElement>) => { | 
|  | 79 | +    setOptions((v) => ({ | 
|  | 80 | +      ...v, | 
|  | 81 | +      amount: e.target.value, | 
|  | 82 | +    })); | 
|  | 83 | +  }; | 
|  | 84 | + | 
|  | 85 | +  const handlePayToChange = (e: React.ChangeEvent<HTMLInputElement>) => { | 
|  | 86 | +    setOptions((v) => ({ | 
|  | 87 | +      ...v, | 
|  | 88 | +      payTo: e.target.value as `0x${string}`, | 
|  | 89 | +    })); | 
|  | 90 | +  }; | 
|  | 91 | + | 
|  | 92 | +  const handleWaitUntilChange = ( | 
|  | 93 | +    value: "simulated" | "submitted" | "confirmed", | 
|  | 94 | +  ) => { | 
|  | 95 | +    setOptions((v) => ({ | 
|  | 96 | +      ...v, | 
|  | 97 | +      waitUntil: value, | 
|  | 98 | +    })); | 
|  | 99 | +  }; | 
|  | 100 | + | 
|  | 101 | +  return ( | 
|  | 102 | +    <div className="space-y-6"> | 
|  | 103 | +      <div> | 
|  | 104 | +        <h2 className="mb-4 text-xl font-semibold">Configuration</h2> | 
|  | 105 | +        <div className="space-y-4"> | 
|  | 106 | +          {/* Chain selection */} | 
|  | 107 | +          <div className="flex flex-col gap-2"> | 
|  | 108 | +            <Label htmlFor={chainId}>Chain</Label> | 
|  | 109 | +            <BridgeNetworkSelector | 
|  | 110 | +              chainId={selectedChain} | 
|  | 111 | +              onChange={handleChainChange} | 
|  | 112 | +              placeholder="Select a chain" | 
|  | 113 | +              className="bg-card" | 
|  | 114 | +            /> | 
|  | 115 | +          </div> | 
|  | 116 | + | 
|  | 117 | +          {/* Token selection - only show if chain is selected */} | 
|  | 118 | +          {selectedChain && ( | 
|  | 119 | +            <div className="flex flex-col gap-2"> | 
|  | 120 | +              <Label htmlFor={tokenId}>Token</Label> | 
|  | 121 | +              <TokenSelector | 
|  | 122 | +                chainId={selectedChain} | 
|  | 123 | +                client={THIRDWEB_CLIENT} | 
|  | 124 | +                enabled={true} | 
|  | 125 | +                onChange={handleTokenChange} | 
|  | 126 | +                placeholder="Select a token" | 
|  | 127 | +                selectedToken={selectedToken} | 
|  | 128 | +                className="bg-card" | 
|  | 129 | +              /> | 
|  | 130 | +            </div> | 
|  | 131 | +          )} | 
|  | 132 | + | 
|  | 133 | +          {/* Amount input */} | 
|  | 134 | +          <div className="flex flex-col gap-2"> | 
|  | 135 | +            <Label htmlFor={amountId}>Amount</Label> | 
|  | 136 | +            <Input | 
|  | 137 | +              id={amountId} | 
|  | 138 | +              type="text" | 
|  | 139 | +              placeholder="0.01" | 
|  | 140 | +              value={options.amount} | 
|  | 141 | +              onChange={handleAmountChange} | 
|  | 142 | +              className="bg-card" | 
|  | 143 | +            /> | 
|  | 144 | +            {options.tokenSymbol && ( | 
|  | 145 | +              <p className="text-sm text-muted-foreground"> | 
|  | 146 | +                Amount in {options.tokenSymbol} | 
|  | 147 | +              </p> | 
|  | 148 | +            )} | 
|  | 149 | +          </div> | 
|  | 150 | + | 
|  | 151 | +          {/* Pay To input */} | 
|  | 152 | +          <div className="flex flex-col gap-2"> | 
|  | 153 | +            <Label htmlFor={payToId}>Pay To Address</Label> | 
|  | 154 | +            <Input | 
|  | 155 | +              id={payToId} | 
|  | 156 | +              type="text" | 
|  | 157 | +              placeholder="0x..." | 
|  | 158 | +              value={options.payTo} | 
|  | 159 | +              onChange={handlePayToChange} | 
|  | 160 | +              className="bg-card" | 
|  | 161 | +            /> | 
|  | 162 | +            <p className="text-sm text-muted-foreground"> | 
|  | 163 | +              The wallet address that will receive the payment | 
|  | 164 | +            </p> | 
|  | 165 | +          </div> | 
|  | 166 | + | 
|  | 167 | +          {/* Wait Until selection */} | 
|  | 168 | +          <div className="flex flex-col gap-2"> | 
|  | 169 | +            <Label htmlFor={waitUntilId}>Wait Until</Label> | 
|  | 170 | +            <Select | 
|  | 171 | +              value={options.waitUntil} | 
|  | 172 | +              onValueChange={handleWaitUntilChange} | 
|  | 173 | +            > | 
|  | 174 | +              <SelectTrigger className="bg-card"> | 
|  | 175 | +                <SelectValue placeholder="Select wait condition" /> | 
|  | 176 | +              </SelectTrigger> | 
|  | 177 | +              <SelectContent> | 
|  | 178 | +                <SelectItem value="simulated">Simulated</SelectItem> | 
|  | 179 | +                <SelectItem value="submitted">Submitted</SelectItem> | 
|  | 180 | +                <SelectItem value="confirmed">Confirmed</SelectItem> | 
|  | 181 | +              </SelectContent> | 
|  | 182 | +            </Select> | 
|  | 183 | +            <p className="text-sm text-muted-foreground"> | 
|  | 184 | +              When to consider the payment settled: simulated (fastest), | 
|  | 185 | +              submitted (medium), or confirmed (most secure) | 
|  | 186 | +            </p> | 
|  | 187 | +          </div> | 
|  | 188 | +        </div> | 
|  | 189 | +      </div> | 
|  | 190 | +    </div> | 
|  | 191 | +  ); | 
|  | 192 | +} | 
0 commit comments