@b3dotfun/sdk 0.0.16 → 0.0.17-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/dist/cjs/anyspend/react/components/AnySpend.d.ts +5 -2
  2. package/dist/cjs/anyspend/react/components/AnySpend.js +264 -55
  3. package/dist/cjs/anyspend/react/components/AnySpendCustom.js +25 -29
  4. package/dist/cjs/anyspend/react/components/common/ConnectWalletPayment.d.ts +15 -0
  5. package/dist/cjs/anyspend/react/components/common/ConnectWalletPayment.js +49 -0
  6. package/dist/cjs/anyspend/react/components/common/CryptoPaymentMethod.d.ts +20 -0
  7. package/dist/cjs/anyspend/react/components/common/CryptoPaymentMethod.js +118 -0
  8. package/dist/cjs/anyspend/react/components/common/FiatPaymentMethod.d.ts +15 -0
  9. package/dist/cjs/anyspend/react/components/common/FiatPaymentMethod.js +71 -0
  10. package/dist/cjs/anyspend/react/components/common/OrderDetails.d.ts +2 -0
  11. package/dist/cjs/anyspend/react/components/common/OrderDetails.js +41 -60
  12. package/dist/cjs/anyspend/react/components/common/OrderDetailsCollapsible.d.ts +18 -0
  13. package/dist/cjs/anyspend/react/components/common/OrderDetailsCollapsible.js +42 -0
  14. package/dist/cjs/anyspend/react/components/common/OrderStatus.js +28 -5
  15. package/dist/cjs/anyspend/react/components/common/OrderTokenAmountFiat.d.ts +13 -0
  16. package/dist/cjs/anyspend/react/components/common/OrderTokenAmountFiat.js +50 -0
  17. package/dist/cjs/anyspend/react/components/common/OrderTokenAmountNew.d.ts +16 -0
  18. package/dist/cjs/anyspend/react/components/common/OrderTokenAmountNew.js +63 -0
  19. package/dist/cjs/anyspend/react/components/common/PanelOnramp.d.ts +11 -1
  20. package/dist/cjs/anyspend/react/components/common/PanelOnramp.js +53 -15
  21. package/dist/cjs/anyspend/react/components/common/PanelOnrampPayment.js +1 -12
  22. package/dist/cjs/anyspend/react/components/common/PaymentStripeWeb2.js +4 -12
  23. package/dist/cjs/anyspend/react/components/common/StepProgress.d.ts +11 -0
  24. package/dist/cjs/anyspend/react/components/common/StepProgress.js +22 -0
  25. package/dist/cjs/anyspend/react/components/common/TransferCryptoDetails.d.ts +16 -0
  26. package/dist/cjs/anyspend/react/components/common/TransferCryptoDetails.js +73 -0
  27. package/dist/cjs/anyspend/react/components/index.d.ts +3 -0
  28. package/dist/cjs/anyspend/react/components/index.js +7 -1
  29. package/dist/cjs/anyspend/react/components/webview/WebviewOnrampPayment.js +1 -10
  30. package/dist/cjs/anyspend/utils/format.d.ts +1 -0
  31. package/dist/cjs/anyspend/utils/format.js +23 -10
  32. package/dist/cjs/anyspend/utils/number.d.ts +7 -0
  33. package/dist/cjs/anyspend/utils/number.js +18 -0
  34. package/dist/esm/anyspend/react/components/AnySpend.d.ts +5 -2
  35. package/dist/esm/anyspend/react/components/AnySpend.js +266 -57
  36. package/dist/esm/anyspend/react/components/AnySpendCustom.js +27 -31
  37. package/dist/esm/anyspend/react/components/common/ConnectWalletPayment.d.ts +15 -0
  38. package/dist/esm/anyspend/react/components/common/ConnectWalletPayment.js +46 -0
  39. package/dist/esm/anyspend/react/components/common/CryptoPaymentMethod.d.ts +20 -0
  40. package/dist/esm/anyspend/react/components/common/CryptoPaymentMethod.js +114 -0
  41. package/dist/esm/anyspend/react/components/common/FiatPaymentMethod.d.ts +15 -0
  42. package/dist/esm/anyspend/react/components/common/FiatPaymentMethod.js +67 -0
  43. package/dist/esm/anyspend/react/components/common/OrderDetails.d.ts +2 -0
  44. package/dist/esm/anyspend/react/components/common/OrderDetails.js +43 -62
  45. package/dist/esm/anyspend/react/components/common/OrderDetailsCollapsible.d.ts +18 -0
  46. package/dist/esm/anyspend/react/components/common/OrderDetailsCollapsible.js +36 -0
  47. package/dist/esm/anyspend/react/components/common/OrderStatus.js +27 -4
  48. package/dist/esm/anyspend/react/components/common/OrderTokenAmountFiat.d.ts +13 -0
  49. package/dist/esm/anyspend/react/components/common/OrderTokenAmountFiat.js +47 -0
  50. package/dist/esm/anyspend/react/components/common/OrderTokenAmountNew.d.ts +16 -0
  51. package/dist/esm/anyspend/react/components/common/OrderTokenAmountNew.js +60 -0
  52. package/dist/esm/anyspend/react/components/common/PanelOnramp.d.ts +11 -1
  53. package/dist/esm/anyspend/react/components/common/PanelOnramp.js +54 -16
  54. package/dist/esm/anyspend/react/components/common/PanelOnrampPayment.js +1 -12
  55. package/dist/esm/anyspend/react/components/common/PaymentStripeWeb2.js +7 -15
  56. package/dist/esm/anyspend/react/components/common/StepProgress.d.ts +11 -0
  57. package/dist/esm/anyspend/react/components/common/StepProgress.js +19 -0
  58. package/dist/esm/anyspend/react/components/common/TransferCryptoDetails.d.ts +16 -0
  59. package/dist/esm/anyspend/react/components/common/TransferCryptoDetails.js +70 -0
  60. package/dist/esm/anyspend/react/components/index.d.ts +3 -0
  61. package/dist/esm/anyspend/react/components/index.js +3 -0
  62. package/dist/esm/anyspend/react/components/webview/WebviewOnrampPayment.js +1 -10
  63. package/dist/esm/anyspend/utils/format.d.ts +1 -0
  64. package/dist/esm/anyspend/utils/format.js +23 -10
  65. package/dist/esm/anyspend/utils/number.d.ts +7 -0
  66. package/dist/esm/anyspend/utils/number.js +17 -0
  67. package/dist/styles/index.css +1 -1
  68. package/dist/types/anyspend/react/components/AnySpend.d.ts +5 -2
  69. package/dist/types/anyspend/react/components/common/ConnectWalletPayment.d.ts +15 -0
  70. package/dist/types/anyspend/react/components/common/CryptoPaymentMethod.d.ts +20 -0
  71. package/dist/types/anyspend/react/components/common/FiatPaymentMethod.d.ts +15 -0
  72. package/dist/types/anyspend/react/components/common/OrderDetails.d.ts +2 -0
  73. package/dist/types/anyspend/react/components/common/OrderDetailsCollapsible.d.ts +18 -0
  74. package/dist/types/anyspend/react/components/common/OrderTokenAmountFiat.d.ts +13 -0
  75. package/dist/types/anyspend/react/components/common/OrderTokenAmountNew.d.ts +16 -0
  76. package/dist/types/anyspend/react/components/common/PanelOnramp.d.ts +11 -1
  77. package/dist/types/anyspend/react/components/common/StepProgress.d.ts +11 -0
  78. package/dist/types/anyspend/react/components/common/TransferCryptoDetails.d.ts +16 -0
  79. package/dist/types/anyspend/react/components/index.d.ts +3 -0
  80. package/dist/types/anyspend/utils/format.d.ts +1 -0
  81. package/dist/types/anyspend/utils/number.d.ts +7 -0
  82. package/package.json +1 -1
  83. package/src/anyspend/react/components/AnySpend.tsx +535 -177
  84. package/src/anyspend/react/components/AnySpendCustom.tsx +33 -38
  85. package/src/anyspend/react/components/common/ConnectWalletPayment.tsx +124 -0
  86. package/src/anyspend/react/components/common/CryptoPaymentMethod.tsx +227 -0
  87. package/src/anyspend/react/components/common/FiatPaymentMethod.tsx +173 -0
  88. package/src/anyspend/react/components/common/OrderDetails.tsx +123 -250
  89. package/src/anyspend/react/components/common/OrderDetailsCollapsible.tsx +156 -0
  90. package/src/anyspend/react/components/common/OrderStatus.tsx +47 -24
  91. package/src/anyspend/react/components/common/OrderTokenAmountFiat.tsx +147 -0
  92. package/src/anyspend/react/components/common/OrderTokenAmountNew.tsx +215 -0
  93. package/src/anyspend/react/components/common/PanelOnramp.tsx +215 -62
  94. package/src/anyspend/react/components/common/PanelOnrampPayment.tsx +1 -14
  95. package/src/anyspend/react/components/common/PaymentStripeWeb2.tsx +42 -99
  96. package/src/anyspend/react/components/common/StepProgress.tsx +104 -0
  97. package/src/anyspend/react/components/common/TransferCryptoDetails.tsx +261 -0
  98. package/src/anyspend/react/components/index.ts +3 -0
  99. package/src/anyspend/react/components/webview/WebviewOnrampPayment.tsx +1 -11
  100. package/src/anyspend/utils/format.ts +24 -11
  101. package/src/anyspend/utils/number.ts +18 -0
  102. package/src/styles/index.css +30 -11
@@ -0,0 +1,156 @@
1
+ "use client";
2
+
3
+ import { ALL_CHAINS, capitalizeFirstLetter, getChainName } from "@b3dotfun/sdk/anyspend";
4
+ import { components } from "@b3dotfun/sdk/anyspend/types/api";
5
+ import { CopyToClipboard } from "@b3dotfun/sdk/global-account/react";
6
+ import { cn } from "@b3dotfun/sdk/shared/utils";
7
+ import centerTruncate from "@b3dotfun/sdk/shared/utils/centerTruncate";
8
+ import { formatTokenAmount } from "@b3dotfun/sdk/shared/utils/number";
9
+ import { ChevronDown, Copy } from "lucide-react";
10
+ import { motion } from "motion/react";
11
+ import { memo, useState } from "react";
12
+ import { toast } from "sonner";
13
+ import { b3 } from "viem/chains";
14
+
15
+ type Order = components["schemas"]["Order"];
16
+ type Token = components["schemas"]["Token"];
17
+ type Tournament = components["schemas"]["Tournament"];
18
+ type NFT = components["schemas"]["NFT"];
19
+
20
+ interface OrderDetailsCollapsibleProps {
21
+ order: Order;
22
+ dstToken: Token;
23
+ tournament?: Tournament;
24
+ nft?: NFT;
25
+ recipientName?: string;
26
+ formattedExpectedDstAmount?: string;
27
+ className?: string;
28
+ showTotal?: boolean;
29
+ totalAmount?: string;
30
+ }
31
+
32
+ export const OrderDetailsCollapsible = memo(function OrderDetailsCollapsible({
33
+ order,
34
+ dstToken,
35
+ tournament,
36
+ nft,
37
+ recipientName,
38
+ formattedExpectedDstAmount,
39
+ className,
40
+ showTotal = false,
41
+ totalAmount,
42
+ }: OrderDetailsCollapsibleProps) {
43
+ const [showOrderDetails, setShowOrderDetails] = useState(true);
44
+
45
+ // Calculate expected amount if not provided
46
+ const expectedDstAmount =
47
+ order.type === "mint_nft" ||
48
+ order.type === "join_tournament" ||
49
+ order.type === "fund_tournament" ||
50
+ order.type === "custom"
51
+ ? "0"
52
+ : order.payload.expectedDstAmount.toString();
53
+
54
+ const finalFormattedExpectedDstAmount =
55
+ formattedExpectedDstAmount || formatTokenAmount(BigInt(expectedDstAmount), dstToken.decimals);
56
+
57
+ return (
58
+ <div className={cn("bg-as-surface-secondary border-as-border-secondary rounded-xl border px-4 py-2", className)}>
59
+ {showOrderDetails ? (
60
+ <motion.div
61
+ className="w-full"
62
+ initial={{ opacity: 0, y: 20, filter: "blur(10px)" }}
63
+ animate={{ opacity: 1, y: 0, filter: "blur(0px)" }}
64
+ transition={{ duration: 0.3, delay: 0, ease: "easeInOut" }}
65
+ >
66
+ <div className="flex w-full flex-col items-center gap-3 whitespace-nowrap py-2 text-sm">
67
+ {/* Recipient Section */}
68
+ <div className="flex w-full justify-between gap-4">
69
+ <div className="text-as-tertiarry">Recipient</div>
70
+ <div className="flex flex-col items-end gap-1">
71
+ {recipientName && <div className="text-as-primary font-semibold">{recipientName}</div>}
72
+ <CopyToClipboard
73
+ text={order.recipientAddress}
74
+ onCopy={() => {
75
+ toast.success("Copied recipient address to clipboard");
76
+ }}
77
+ >
78
+ <div className="text-as-primary flex items-center gap-2">
79
+ {centerTruncate(order.recipientAddress, 10)}
80
+ <Copy className="text-as-primary/50 hover:text-as-primary h-4 w-4 cursor-pointer transition-all duration-200" />
81
+ </div>
82
+ </CopyToClipboard>
83
+ </div>
84
+ </div>
85
+ <div className="divider w-full" />
86
+
87
+ {/* Expected Amount/Action Section */}
88
+ <div className="flex w-full items-center justify-between gap-2">
89
+ <div className="text-as-tertiarry">
90
+ {order.type === "swap" || order.type === "mint_nft"
91
+ ? "Expected to receive"
92
+ : order.type === "join_tournament"
93
+ ? "Join tournament"
94
+ : order.type === "fund_tournament"
95
+ ? "Fund tournament"
96
+ : order.type === "custom"
97
+ ? order.metadata.action
98
+ ? capitalizeFirstLetter(order.metadata.action)
99
+ : "Contract execution"
100
+ : ""}
101
+ </div>
102
+
103
+ <div className="flex items-end gap-2">
104
+ {order.type === "swap" ? (
105
+ `~${finalFormattedExpectedDstAmount} ${dstToken.symbol}`
106
+ ) : order.type === "mint_nft" ? (
107
+ <div className="flex items-center gap-2">
108
+ <img src={nft?.imageUrl} alt={nft?.name || "NFT"} className="h-5 w-5" />
109
+ <div>{nft?.name || "NFT"}</div>
110
+ </div>
111
+ ) : order.type === "join_tournament" || order.type === "fund_tournament" ? (
112
+ <div className="flex items-center gap-2">
113
+ <img src={tournament?.imageUrl} alt={tournament?.name || "Tournament"} className="h-5 w-5" />
114
+ <div>{tournament?.name || "Tournament"}</div>
115
+ </div>
116
+ ) : null}
117
+
118
+ <div className="text-as-primary/50 flex items-center gap-2">
119
+ <span>on {order.dstChain !== b3.id && getChainName(order.dstChain)}</span>
120
+ <img
121
+ src={ALL_CHAINS[order.dstChain].logoUrl}
122
+ alt={getChainName(order.dstChain)}
123
+ className={cn(
124
+ "h-3",
125
+ order.dstChain !== b3.id && "w-3 rounded-full",
126
+ order.dstChain === b3.id && "h-4",
127
+ )}
128
+ />
129
+ </div>
130
+ </div>
131
+ </div>
132
+
133
+ <div className="divider w-full" />
134
+
135
+ {/* Order ID / Total Section */}
136
+ <div className="flex w-full justify-between gap-4">
137
+ <div className="text-as-tertiarry">{showTotal ? "Total (included fee)" : "Order ID"}</div>
138
+ <div className="text-as-primary overflow-hidden text-ellipsis whitespace-nowrap">
139
+ {showTotal && totalAmount ? totalAmount : order.id}
140
+ </div>
141
+ </div>
142
+ </div>
143
+ </motion.div>
144
+ ) : (
145
+ <div className="flex w-full items-center">
146
+ <div className="divider w-full" />
147
+ <button className="whitespace-nowrap text-sm" onClick={() => setShowOrderDetails(true)}>
148
+ Order Details
149
+ </button>
150
+ <ChevronDown className="text-as-primary mx-1 h-4 min-h-4 w-4 min-w-4" />
151
+ <div className="divider w-full" />
152
+ </div>
153
+ )}
154
+ </div>
155
+ );
156
+ });
@@ -1,38 +1,61 @@
1
1
  import { getStatusDisplay } from "@b3dotfun/sdk/anyspend";
2
2
  import { components } from "@b3dotfun/sdk/anyspend/types/api";
3
- import { Badge, TextShimmer } from "@b3dotfun/sdk/global-account/react";
4
- import { Check, Loader2 } from "lucide-react";
3
+ import { Check, X } from "lucide-react";
5
4
  import { memo } from "react";
5
+ import { Step, StepProgress } from "./StepProgress";
6
6
 
7
7
  export const OrderStatus = memo(function OrderStatus({ order }: { order: components["schemas"]["Order"] }) {
8
8
  const isComplete = order.status === "executed";
9
- const { text, status: displayStatus } = getStatusDisplay(order);
9
+ const { text, status: displayStatus, description } = getStatusDisplay(order);
10
+
11
+ console.log("OrderStatus", displayStatus);
12
+ console.log("OrderStatus", order);
13
+
14
+ const paymentSteps: Step[] = [
15
+ {
16
+ id: 1,
17
+ title: text,
18
+ description: description,
19
+ },
20
+ {
21
+ id: 2,
22
+ title: text,
23
+ description: description,
24
+ },
25
+ ];
26
+
27
+ if (order.status === "waiting_stripe_payment") {
28
+ return <StepProgress steps={paymentSteps} currentStepIndex={0} />;
29
+ }
30
+
31
+ if (order.status === "relay") {
32
+ return <StepProgress steps={paymentSteps} currentStepIndex={1} />;
33
+ }
34
+
35
+ if (!isComplete && displayStatus !== "failure") {
36
+ return null;
37
+ }
10
38
 
11
39
  return (
12
40
  <div className="flex items-center justify-center gap-2">
13
41
  {isComplete ? (
14
- <Badge
15
- variant="outline"
16
- className="flex items-center gap-3 border-green-500/50 bg-green-500/20 px-4 py-1 text-base transition-colors"
17
- >
18
- <Check className="h-6 w-6 text-green-500" />
19
- <span className="font-medium">{text}</span>
20
- </Badge>
21
- ) : displayStatus === "failure" ? (
22
- <Badge variant="destructive" className="border-red-400/50 bg-red-400/20 px-4 py-1 text-base">
23
- <div className="font-sf-rounded text-base font-semibold text-red-400/50">{text}</div>
24
- </Badge>
42
+ <div className="flex flex-col items-center">
43
+ <div className={`bg-as-success-secondary relative flex h-10 w-10 items-center justify-center rounded-full`}>
44
+ <Check className="text-as-content-icon-success h-6 w-6" />
45
+ </div>
46
+ <h2 className="text-as-primary mt-4 text-xl font-semibold">{text}</h2>
47
+ <div className="text-as-tertiarry mt-1 text-center">{description}</div>
48
+ </div>
25
49
  ) : (
26
- <Badge
27
- variant="default"
28
- className="border-as-stroke/20 bg-as-primary/10 flex items-center gap-3 px-4 py-1 text-base transition-colors"
29
- >
30
- {displayStatus === "processing" && <Loader2 className="text-as-primary h-4 w-4 animate-spin" />}
31
-
32
- <TextShimmer duration={1} className="font-sf-rounded text-base font-semibold">
33
- {text}
34
- </TextShimmer>
35
- </Badge>
50
+ <div className="flex flex-col items-center">
51
+ <div className="bg-as-error-secondary flex h-10 w-10 items-center justify-center rounded-full text-base">
52
+ <X className="text-as-content-icon-error h-5 w-5" />
53
+ </div>
54
+ <div className="font-sf-rounded text-as-content-primary mt-4 text-lg font-semibold">{text}</div>
55
+ <div className="text-as-tertiarry text-center" style={{ whiteSpace: "normal" }}>
56
+ {description}
57
+ </div>
58
+ </div>
36
59
  )}
37
60
  </div>
38
61
  );
@@ -0,0 +1,147 @@
1
+ "use client";
2
+
3
+ import { ChevronsUpDown } from "lucide-react";
4
+ import { useEffect, useRef } from "react";
5
+ import { NumericFormat } from "react-number-format";
6
+
7
+ import { ALL_CHAINS, RELAY_SOLANA_MAINNET_CHAIN_ID } from "@b3dotfun/sdk/anyspend";
8
+ import { components } from "@b3dotfun/sdk/anyspend/types/api";
9
+ import { cn } from "@b3dotfun/sdk/shared/utils";
10
+ import { TokenSelector } from "@reservoir0x/relay-kit-ui";
11
+ import { ChainTokenIcon } from "./ChainTokenIcon";
12
+
13
+ export function OrderTokenAmountFiat({
14
+ disabled,
15
+ inputValue,
16
+ onChangeInput,
17
+ context,
18
+ address,
19
+ chainId,
20
+ setChainId,
21
+ token,
22
+ setToken,
23
+ className,
24
+ }: {
25
+ disabled?: boolean;
26
+ inputValue: string;
27
+ onChangeInput: (value: string) => void;
28
+ context: "from" | "to";
29
+ address: string | undefined;
30
+ token: components["schemas"]["Token"];
31
+ setToken: (token: components["schemas"]["Token"]) => void;
32
+ chainId: number;
33
+ setChainId: (chainId: number) => void;
34
+ className?: string;
35
+ }) {
36
+ // Track previous token to detect changes
37
+ const prevTokenRef = useRef<string>(token.address);
38
+
39
+ useEffect(() => {
40
+ // Only trigger when token actually changes
41
+ if (prevTokenRef.current !== token.address) {
42
+ console.log(`Token changed from ${prevTokenRef.current} to ${token.address}`);
43
+
44
+ // For "from" context, reset to default value when token changes
45
+ if (context === "from") {
46
+ // Reset input to default for new token
47
+ onChangeInput("0.01");
48
+ }
49
+
50
+ // Update ref to current token
51
+ prevTokenRef.current = token.address;
52
+ }
53
+ }, [token.address, chainId, context, onChangeInput]);
54
+
55
+ const handleTokenSelect = (newToken: any) => {
56
+ // Mark that we're about to change tokens
57
+ prevTokenRef.current = "changing"; // Temporary value to force effect
58
+
59
+ // Set the chain ID first
60
+ setChainId(newToken.chainId);
61
+
62
+ // Then set the new token
63
+ setToken({
64
+ address: newToken.address,
65
+ chainId: newToken.chainId, // Use the new chain ID
66
+ decimals: newToken.decimals,
67
+ metadata: { logoURI: newToken.logoURI },
68
+ name: newToken.name,
69
+ symbol: newToken.symbol,
70
+ });
71
+
72
+ // If this is the source token, reset the amount immediately
73
+ if (context === "from") {
74
+ onChangeInput("0.01");
75
+ }
76
+ };
77
+
78
+ // Original token amount input design for other contexts
79
+ return (
80
+ <TokenSelector
81
+ address={address}
82
+ chainIdsFilter={Object.values(ALL_CHAINS).map(chain => chain.id)}
83
+ context={context}
84
+ fromChainWalletVMSupported={true}
85
+ isValidAddress={true}
86
+ key={`selector-${context}-${token.address}-${chainId}`}
87
+ lockedChainIds={Object.values(ALL_CHAINS).map(chain => chain.id)}
88
+ multiWalletSupportEnabled={true}
89
+ onAnalyticEvent={undefined}
90
+ popularChainIds={[1, 8453, RELAY_SOLANA_MAINNET_CHAIN_ID]}
91
+ setToken={handleTokenSelect}
92
+ supportedWalletVMs={["evm", "svm"]}
93
+ token={undefined}
94
+ trigger={
95
+ <div
96
+ className={cn(
97
+ "border-as-border-secondary bg-as-surface-primary flex cursor-pointer items-center justify-between rounded-xl border px-3 py-2",
98
+ className,
99
+ )}
100
+ >
101
+ <div className="flex items-center gap-3">
102
+ {token.metadata?.logoURI ? (
103
+ <ChainTokenIcon
104
+ chainUrl={ALL_CHAINS[chainId]?.logoUrl}
105
+ tokenUrl={token.metadata.logoURI}
106
+ className="h-10 w-10"
107
+ />
108
+ ) : (
109
+ <div className="flex h-10 w-10 items-center justify-center rounded-full bg-blue-600">
110
+ <span className="font-bold text-white">{token.symbol?.substring(0, 2) || "??"}</span>
111
+ </div>
112
+ )}
113
+ <div>
114
+ <div className="text-as-primary font-semibold">{token.symbol}</div>
115
+ <div className="text-as-primary/50 text-sm">{ALL_CHAINS[chainId]?.name || "Unknown"}</div>
116
+ </div>
117
+ </div>
118
+ <div className="flex items-center gap-2">
119
+ <span className="text-sm text-gray-600">≈</span>
120
+ <NumericFormat
121
+ key={`input-${token.address}-${chainId}`}
122
+ decimalSeparator="."
123
+ allowedDecimalSeparators={[","]}
124
+ thousandSeparator
125
+ inputMode="decimal"
126
+ autoComplete="off"
127
+ autoCorrect="off"
128
+ type="text"
129
+ placeholder="0.00"
130
+ minLength={1}
131
+ maxLength={20}
132
+ spellCheck="false"
133
+ className="text-as-primary bg-as-surface-primary w-[100px]"
134
+ pattern="^[0-9]*[.,]?[0-9]*$"
135
+ disabled={disabled}
136
+ value={inputValue}
137
+ allowNegative={false}
138
+ aria-disabled
139
+ readOnly
140
+ />
141
+ <ChevronsUpDown className="h-4 w-4 cursor-pointer text-gray-400" />
142
+ </div>
143
+ </div>
144
+ }
145
+ />
146
+ );
147
+ }
@@ -0,0 +1,215 @@
1
+ "use client";
2
+
3
+ import { ChevronsUpDown } from "lucide-react";
4
+ import { useEffect, useRef } from "react";
5
+ import { NumericFormat } from "react-number-format";
6
+
7
+ import { ALL_CHAINS, RELAY_SOLANA_MAINNET_CHAIN_ID } from "@b3dotfun/sdk/anyspend";
8
+ import { Button } from "@b3dotfun/sdk/global-account/react";
9
+ import { cn } from "@b3dotfun/sdk/shared/utils";
10
+ import { TokenSelector } from "@reservoir0x/relay-kit-ui";
11
+ import { ChainTokenIcon } from "./ChainTokenIcon";
12
+ import { components } from "@b3dotfun/sdk/anyspend/types/api";
13
+
14
+ export function OrderTokenAmountFiat({
15
+ disabled,
16
+ inputValue,
17
+ onChangeInput,
18
+ context,
19
+ address,
20
+ chainId,
21
+ setChainId,
22
+ token,
23
+ setToken,
24
+ hideTokenSelect = false,
25
+ canEditAmount = true,
26
+ className,
27
+ showAsReceiveAmount = false,
28
+ }: {
29
+ disabled?: boolean;
30
+ inputValue: string;
31
+ onChangeInput: (value: string) => void;
32
+ context: "from" | "to";
33
+ address: string | undefined;
34
+ token: components["schemas"]["Token"];
35
+ setToken: (token: components["schemas"]["Token"]) => void;
36
+ chainId: number;
37
+ setChainId: (chainId: number) => void;
38
+ hideTokenSelect?: boolean;
39
+ canEditAmount?: boolean;
40
+ className?: string;
41
+ showAsReceiveAmount?: boolean;
42
+ }) {
43
+ // Track previous token to detect changes
44
+ const prevTokenRef = useRef<string>(token.address);
45
+
46
+ useEffect(() => {
47
+ // Only trigger when token actually changes
48
+ if (prevTokenRef.current !== token.address) {
49
+ console.log(`Token changed from ${prevTokenRef.current} to ${token.address}`);
50
+
51
+ // For "from" context, reset to default value when token changes
52
+ if (context === "from") {
53
+ // Reset input to default for new token
54
+ onChangeInput("0.01");
55
+ }
56
+
57
+ // Update ref to current token
58
+ prevTokenRef.current = token.address;
59
+ }
60
+ }, [token.address, chainId, context, onChangeInput]);
61
+
62
+ const handleTokenSelect = (newToken: any) => {
63
+ // Mark that we're about to change tokens
64
+ prevTokenRef.current = "changing"; // Temporary value to force effect
65
+
66
+ // Set the chain ID first
67
+ setChainId(newToken.chainId);
68
+
69
+ // Then set the new token
70
+ setToken({
71
+ address: newToken.address,
72
+ chainId: newToken.chainId, // Use the new chain ID
73
+ decimals: newToken.decimals,
74
+ metadata: { logoURI: newToken.logoURI },
75
+ name: newToken.name,
76
+ symbol: newToken.symbol,
77
+ });
78
+
79
+ // If this is the source token, reset the amount immediately
80
+ if (context === "from") {
81
+ onChangeInput("0.01");
82
+ }
83
+ };
84
+
85
+ // Calculate formatted amount for display
86
+ const formatAmount = (amount: string) => {
87
+ const numAmount = parseFloat(amount) || 0;
88
+ return numAmount.toLocaleString("en-US", {
89
+ minimumFractionDigits: 2,
90
+ maximumFractionDigits: 2,
91
+ });
92
+ };
93
+
94
+ if (showAsReceiveAmount) {
95
+ // Design-matched token display for receive amounts (like in PanelOnramp)
96
+ return (
97
+ <div className={cn("flex items-center justify-between rounded-xl bg-gray-50 p-4", className)}>
98
+ <div className="flex items-center gap-3">
99
+ {token.metadata?.logoURI ? (
100
+ <ChainTokenIcon
101
+ chainUrl={ALL_CHAINS[chainId]?.logoUrl}
102
+ tokenUrl={token.metadata.logoURI}
103
+ className="h-10 w-10"
104
+ />
105
+ ) : (
106
+ <div className="flex h-10 w-10 items-center justify-center rounded-full bg-blue-600">
107
+ <span className="font-bold text-white">{token.symbol?.substring(0, 2) || "??"}</span>
108
+ </div>
109
+ )}
110
+ <div>
111
+ <div className="text-base font-semibold text-gray-900">{token.symbol}</div>
112
+ <div className="text-sm text-gray-600">{ALL_CHAINS[chainId]?.name || "Unknown"}</div>
113
+ </div>
114
+ </div>
115
+ <div className="flex items-center gap-2">
116
+ <span className="text-sm text-gray-600">≈</span>
117
+ <span className="text-lg font-semibold text-gray-900">{formatAmount(inputValue)}</span>
118
+ {!hideTokenSelect && (
119
+ <TokenSelector
120
+ address={address}
121
+ chainIdsFilter={Object.values(ALL_CHAINS).map(chain => chain.id)}
122
+ context={context}
123
+ fromChainWalletVMSupported={true}
124
+ isValidAddress={true}
125
+ key={`selector-${context}-${token.address}-${chainId}`}
126
+ lockedChainIds={Object.values(ALL_CHAINS).map(chain => chain.id)}
127
+ multiWalletSupportEnabled={true}
128
+ onAnalyticEvent={undefined}
129
+ popularChainIds={[1, 8453, RELAY_SOLANA_MAINNET_CHAIN_ID]}
130
+ setToken={handleTokenSelect}
131
+ supportedWalletVMs={["evm", "svm"]}
132
+ token={undefined}
133
+ trigger={<ChevronsUpDown className="h-4 w-4 cursor-pointer text-gray-400" />}
134
+ />
135
+ )}
136
+ </div>
137
+ </div>
138
+ );
139
+ }
140
+
141
+ // Original token amount input design for other contexts
142
+ return (
143
+ <div
144
+ className={cn("border-as-stroke flex w-full flex-col gap-2 rounded-xl", className)}
145
+ key={`${context}-${token.address}-${chainId}`}
146
+ >
147
+ <div className="flex items-center justify-between gap-3">
148
+ {!canEditAmount ? (
149
+ <h2 className="text-3xl font-medium text-white">{inputValue || "--"}</h2>
150
+ ) : (
151
+ <NumericFormat
152
+ key={`input-${token.address}-${chainId}`}
153
+ decimalSeparator="."
154
+ allowedDecimalSeparators={[","]}
155
+ thousandSeparator
156
+ inputMode="decimal"
157
+ autoComplete="off"
158
+ autoCorrect="off"
159
+ type="text"
160
+ placeholder="0.00"
161
+ minLength={1}
162
+ maxLength={30}
163
+ spellCheck="false"
164
+ className="placeholder:text-as-primary/70 disabled:text-as-primary/70 text-as-primary w-full bg-transparent text-4xl font-semibold leading-[42px] outline-none sm:text-[30px]"
165
+ pattern="^[0-9]*[.,]?[0-9]*$"
166
+ disabled={disabled}
167
+ value={inputValue}
168
+ allowNegative={false}
169
+ onChange={e => onChangeInput(e.currentTarget.value)}
170
+ />
171
+ )}
172
+
173
+ {!hideTokenSelect && (
174
+ <TokenSelector
175
+ address={address}
176
+ chainIdsFilter={Object.values(ALL_CHAINS).map(chain => chain.id)}
177
+ context={context}
178
+ fromChainWalletVMSupported={true}
179
+ isValidAddress={true}
180
+ key={`selector-${context}-${token.address}-${chainId}`}
181
+ lockedChainIds={Object.values(ALL_CHAINS).map(chain => chain.id)}
182
+ multiWalletSupportEnabled={true}
183
+ onAnalyticEvent={undefined}
184
+ popularChainIds={[1, 8453, RELAY_SOLANA_MAINNET_CHAIN_ID]}
185
+ setToken={handleTokenSelect}
186
+ supportedWalletVMs={["evm", "svm"]}
187
+ token={undefined}
188
+ trigger={
189
+ <Button
190
+ variant="outline"
191
+ role="combobox"
192
+ className="bg-b3-react-background border-as-stroke flex h-auto w-fit shrink-0 items-center justify-center gap-2 rounded-xl border-2 px-2 py-1 pr-2 text-center"
193
+ >
194
+ {token.metadata?.logoURI ? (
195
+ <ChainTokenIcon
196
+ chainUrl={ALL_CHAINS[chainId]?.logoUrl}
197
+ tokenUrl={token.metadata.logoURI}
198
+ className="h-8 min-h-8 w-8 min-w-8"
199
+ />
200
+ ) : (
201
+ <div className="h-8 w-8 rounded-full bg-gray-700" />
202
+ )}
203
+ <div className="flex flex-col items-start gap-0">
204
+ <div className="text-as-primary font-semibold">{token.symbol}</div>
205
+ <div className="text-as-primary/70 text-xs">{ALL_CHAINS[chainId]?.name}</div>
206
+ </div>
207
+ <ChevronsUpDown className="h-4 w-4 shrink-0 opacity-70" />
208
+ </Button>
209
+ }
210
+ />
211
+ )}
212
+ </div>
213
+ </div>
214
+ );
215
+ }