@b3dotfun/sdk 0.1.69-alpha.14 → 0.1.69-alpha.16
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.
- package/dist/cjs/anyspend/react/components/checkout/AnySpendCheckout.js +5 -1
- package/dist/cjs/anyspend/react/components/checkout/CryptoPayPanel.js +43 -23
- package/dist/cjs/anyspend/react/components/common/CryptoPaymentMethod.d.ts +8 -0
- package/dist/cjs/anyspend/react/components/common/CryptoPaymentMethod.js +10 -9
- package/dist/cjs/global-account/react/components/LinkAccount/LinkedAccountItem.js +2 -1
- package/dist/cjs/global-account/react/components/ManageAccount/SettingsProfileCard.js +2 -2
- package/dist/cjs/global-account/react/components/Send/Send.js +5 -2
- package/dist/cjs/global-account/react/components/SingleUserSearchSelector/SingleUserSearchSelector.js +2 -1
- package/dist/esm/anyspend/react/components/checkout/AnySpendCheckout.js +5 -1
- package/dist/esm/anyspend/react/components/checkout/CryptoPayPanel.js +44 -24
- package/dist/esm/anyspend/react/components/common/CryptoPaymentMethod.d.ts +8 -0
- package/dist/esm/anyspend/react/components/common/CryptoPaymentMethod.js +9 -8
- package/dist/esm/global-account/react/components/LinkAccount/LinkedAccountItem.js +2 -1
- package/dist/esm/global-account/react/components/ManageAccount/SettingsProfileCard.js +2 -2
- package/dist/esm/global-account/react/components/Send/Send.js +5 -2
- package/dist/esm/global-account/react/components/SingleUserSearchSelector/SingleUserSearchSelector.js +2 -1
- package/dist/types/anyspend/react/components/common/CryptoPaymentMethod.d.ts +8 -0
- package/package.json +1 -1
- package/src/anyspend/react/components/checkout/AnySpendCheckout.tsx +9 -1
- package/src/anyspend/react/components/checkout/CryptoPayPanel.tsx +45 -27
- package/src/anyspend/react/components/common/CryptoPaymentMethod.tsx +10 -8
- package/src/global-account/react/components/LinkAccount/LinkedAccountItem.tsx +2 -1
- package/src/global-account/react/components/ManageAccount/SettingsProfileCard.tsx +2 -2
- package/src/global-account/react/components/Send/Send.tsx +8 -5
- package/src/global-account/react/components/SingleUserSearchSelector/SingleUserSearchSelector.tsx +2 -1
|
@@ -205,5 +205,9 @@ variablePricing, feeOnTop, kycEnabled = false, callbackMetadata: callbackMetadat
|
|
|
205
205
|
(shippingOptions && shippingOptions.length > 0) ||
|
|
206
206
|
collectShippingAddress ||
|
|
207
207
|
enableDiscountCode;
|
|
208
|
-
return ((0, jsx_runtime_1.jsx)(AnySpendFingerprintWrapper_1.AnySpendFingerprintWrapper, { fingerprint: fingerprint, children: (0, jsx_runtime_1.jsx)(AnySpendCustomizationContext_1.AnySpendCustomizationProvider, { slots: slots, content:
|
|
208
|
+
return ((0, jsx_runtime_1.jsx)(AnySpendFingerprintWrapper_1.AnySpendFingerprintWrapper, { fingerprint: fingerprint, children: (0, jsx_runtime_1.jsx)(AnySpendCustomizationContext_1.AnySpendCustomizationProvider, { slots: slots, content: {
|
|
209
|
+
successTitle: "Payment Successful",
|
|
210
|
+
successDescription: "Your payment has been processed successfully.",
|
|
211
|
+
...content,
|
|
212
|
+
}, theme: theme, children: (0, jsx_runtime_1.jsx)(CheckoutLayout_1.CheckoutLayout, { mode: mode, paymentPanel: (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [isVariablePricingActive && tokenData && variablePricing && ((0, jsx_runtime_1.jsx)(VariablePricingInput_1.VariablePricingInput, { config: variablePricing, tokenDecimals: tokenDecimals, tokenSymbol: tokenSymbol, themeColor: themeColor, onChange: setVariablePricingAmount })), hasFormContent && ((0, jsx_runtime_1.jsxs)("div", { className: "mb-6", children: [(0, jsx_runtime_1.jsx)(CheckoutFormPanel_1.CheckoutFormPanel, { formSchema: formSchema, formComponent: formComponent, shippingOptions: shippingOptions, collectShippingAddress: collectShippingAddress, enableDiscountCode: enableDiscountCode, validateDiscount: validateDiscount, tokenSymbol: tokenSymbol, tokenDecimals: tokenDecimals, classes: classes, formData: formData, onFormDataChange: handleFormDataChange, selectedShipping: selectedShipping, onShippingChange: handleShippingChange, appliedDiscount: appliedDiscount, onDiscountApplied: handleDiscountApplied, onDiscountRemoved: handleDiscountRemoved, shippingAddress: shippingAddress, onShippingAddressChange: setShippingAddress, checkoutFormSlot: slots?.checkoutForm }), (0, jsx_runtime_1.jsx)("div", { className: "mt-6 border-t border-gray-200 dark:border-neutral-700" })] })), (0, jsx_runtime_1.jsx)(CheckoutPaymentPanel_1.CheckoutPaymentPanel, { recipientAddress: recipientAddress, destinationTokenAddress: destinationTokenAddress, destinationTokenChainId: destinationTokenChainId, totalAmount: computedTotal, buttonText: buttonText, themeColor: themeColor, returnUrl: returnUrl, returnLabel: returnLabel, onSuccess: onSuccess, onError: onError, classes: classes, defaultPaymentMethod: defaultPaymentMethod, senderAddress: senderAddress, showPoints: showPoints, showOrderId: showOrderId, callbackMetadata: checkoutFormMetadata, isFormValid: isFormValid, feeOnTop: feeOnTop, kycEnabled: kycEnabled })] }), cartPanel: (0, jsx_runtime_1.jsx)(CheckoutCartPanel_1.CheckoutCartPanel, { items: items, totalAmount: computedTotal, tokenSymbol: tokenSymbol, tokenDecimals: tokenDecimals, organizationName: organizationName, organizationLogo: organizationLogo, classes: classes, footer: footer, shipping: effectiveShipping, tax: typeof tax === "string" ? { amount: tax } : tax, discount: effectiveDiscount, summaryLines: summaryLines, usdEquivalent: usdEquivalent }), classes: classes }) }) }));
|
|
209
213
|
}
|
|
@@ -13,7 +13,6 @@ const chain_1 = require("../../../../anyspend/utils/chain");
|
|
|
13
13
|
const token_1 = require("../../../../anyspend/utils/token");
|
|
14
14
|
const react_1 = require("../../../../global-account/react");
|
|
15
15
|
const react_2 = require("../../../../global-account/react");
|
|
16
|
-
const b3Chain_1 = require("../../../../shared/constants/chains/b3Chain");
|
|
17
16
|
const number_1 = require("../../../../shared/utils/number");
|
|
18
17
|
const cn_1 = require("../../../../shared/utils/cn");
|
|
19
18
|
const lucide_react_1 = require("lucide-react");
|
|
@@ -21,8 +20,10 @@ const qrcode_react_1 = require("qrcode.react");
|
|
|
21
20
|
const viem_1 = require("viem");
|
|
22
21
|
const react_3 = require("motion/react");
|
|
23
22
|
const react_4 = require("react");
|
|
23
|
+
const react_5 = require("thirdweb/react");
|
|
24
24
|
const relay_kit_ui_1 = require("@relayprotocol/relay-kit-ui");
|
|
25
25
|
const ChainTokenIcon_1 = require("../common/ChainTokenIcon");
|
|
26
|
+
const CryptoPaymentMethod_1 = require("../common/CryptoPaymentMethod");
|
|
26
27
|
function CryptoPayPanel({ recipientAddress, destinationTokenAddress, destinationTokenChainId, totalAmount, buttonText = "Pay", themeColor, onSuccess, onOrderCreated, onError, callbackMetadata, classes, senderAddress, }) {
|
|
27
28
|
// Stable refs for callback props to avoid re-triggering effects
|
|
28
29
|
const onErrorRef = (0, react_4.useRef)(onError);
|
|
@@ -35,11 +36,10 @@ function CryptoPayPanel({ recipientAddress, destinationTokenAddress, destination
|
|
|
35
36
|
const [selectedSrcChainId, setSelectedSrcChainId] = (0, react_4.useState)(destinationTokenChainId);
|
|
36
37
|
const [selectedSrcToken, setSelectedSrcToken] = (0, react_4.useState)(null);
|
|
37
38
|
const [copied, setCopied] = (0, react_4.useState)(false);
|
|
38
|
-
const { address: walletAddress } = (0, react_1.useAccountWallet)();
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
-
const
|
|
42
|
-
const setB3ModalContentType = (0, react_1.useModalStore)(state => state.setB3ModalContentType);
|
|
39
|
+
const { address: walletAddress, connectedEOAWallet } = (0, react_1.useAccountWallet)();
|
|
40
|
+
const connectedAddress = walletAddress || connectedEOAWallet?.getAccount()?.address;
|
|
41
|
+
const effectiveAddress = connectedAddress || senderAddress;
|
|
42
|
+
const { connect: openConnectModal } = (0, react_5.useConnectModal)();
|
|
43
43
|
const { data: dstTokenData } = (0, react_1.useTokenData)(destinationTokenChainId, destinationTokenAddress);
|
|
44
44
|
// Default to destination token data once available
|
|
45
45
|
(0, react_4.useEffect)(() => {
|
|
@@ -169,9 +169,8 @@ function CryptoPayPanel({ recipientAddress, destinationTokenAddress, destination
|
|
|
169
169
|
/* ------------------------------------------------------------------ */
|
|
170
170
|
const [walletOrderId, setWalletOrderId] = (0, react_4.useState)();
|
|
171
171
|
const [isSendingDeposit, setIsSendingDeposit] = (0, react_4.useState)(false);
|
|
172
|
-
const [depositRejected, setDepositRejected] = (0, react_4.useState)(false);
|
|
173
172
|
const depositSentRef = (0, react_4.useRef)(false);
|
|
174
|
-
const {
|
|
173
|
+
const { switchChainAndExecuteWithEOA } = (0, react_1.useUnifiedChainSwitchAndExecute)();
|
|
175
174
|
const { createOrder: createSwapOrder, isCreatingOrder: isCreatingSwapOrder } = (0, useAnyspendCreateOrder_1.useAnyspendCreateOrder)({
|
|
176
175
|
onSuccess: data => {
|
|
177
176
|
const id = data.data?.id;
|
|
@@ -186,7 +185,7 @@ function CryptoPayPanel({ recipientAddress, destinationTokenAddress, destination
|
|
|
186
185
|
const { orderAndTransactions: walletOat } = (0, useAnyspendOrderAndTransactions_1.useAnyspendOrderAndTransactions)(walletOrderId);
|
|
187
186
|
// Auto-send deposit tx once swap order is ready
|
|
188
187
|
(0, react_4.useEffect)(() => {
|
|
189
|
-
if (!walletOat?.data?.order || depositSentRef.current
|
|
188
|
+
if (!walletOat?.data?.order || depositSentRef.current)
|
|
190
189
|
return;
|
|
191
190
|
const order = walletOat.data.order;
|
|
192
191
|
if (order.status !== "scanning_deposit_transaction")
|
|
@@ -198,8 +197,9 @@ function CryptoPayPanel({ recipientAddress, destinationTokenAddress, destination
|
|
|
198
197
|
try {
|
|
199
198
|
setIsSendingDeposit(true);
|
|
200
199
|
const amount = BigInt(order.srcAmount);
|
|
200
|
+
let txHash;
|
|
201
201
|
if ((0, token_1.isNativeToken)(order.srcTokenAddress)) {
|
|
202
|
-
await
|
|
202
|
+
txHash = await switchChainAndExecuteWithEOA(order.srcChain, {
|
|
203
203
|
to: order.globalAddress,
|
|
204
204
|
value: amount,
|
|
205
205
|
});
|
|
@@ -210,12 +210,18 @@ function CryptoPayPanel({ recipientAddress, destinationTokenAddress, destination
|
|
|
210
210
|
functionName: "transfer",
|
|
211
211
|
args: [order.globalAddress, amount],
|
|
212
212
|
});
|
|
213
|
-
await
|
|
213
|
+
txHash = await switchChainAndExecuteWithEOA(order.srcChain, {
|
|
214
214
|
to: order.srcTokenAddress,
|
|
215
215
|
data,
|
|
216
216
|
value: BigInt(0),
|
|
217
217
|
});
|
|
218
218
|
}
|
|
219
|
+
if (!txHash) {
|
|
220
|
+
// User cancelled or tx failed — reset so they can retry
|
|
221
|
+
depositSentRef.current = false;
|
|
222
|
+
setWalletOrderId(undefined);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
219
225
|
// Deposit sent — notify parent to transition to order lifecycle tracking
|
|
220
226
|
if (walletOrderId) {
|
|
221
227
|
onOrderCreatedRef.current?.(walletOrderId);
|
|
@@ -223,10 +229,8 @@ function CryptoPayPanel({ recipientAddress, destinationTokenAddress, destination
|
|
|
223
229
|
}
|
|
224
230
|
catch (error) {
|
|
225
231
|
depositSentRef.current = false;
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
setDepositRejected(true);
|
|
229
|
-
}
|
|
232
|
+
// Reset order so user can retry
|
|
233
|
+
setWalletOrderId(undefined);
|
|
230
234
|
onErrorRef.current?.(error instanceof Error ? error : new Error(error?.message || "Transaction rejected"));
|
|
231
235
|
}
|
|
232
236
|
finally {
|
|
@@ -234,7 +238,7 @@ function CryptoPayPanel({ recipientAddress, destinationTokenAddress, destination
|
|
|
234
238
|
}
|
|
235
239
|
};
|
|
236
240
|
sendDeposit();
|
|
237
|
-
}, [walletOat,
|
|
241
|
+
}, [walletOat, switchChainAndExecuteWithEOA, walletOrderId]);
|
|
238
242
|
(0, useOnOrderSuccess_1.useOnOrderSuccess)({
|
|
239
243
|
orderData: walletOat,
|
|
240
244
|
orderId: walletOrderId,
|
|
@@ -242,9 +246,10 @@ function CryptoPayPanel({ recipientAddress, destinationTokenAddress, destination
|
|
|
242
246
|
});
|
|
243
247
|
const isWaitingForExecution = !!walletOrderId && walletOat?.data?.order.status !== "executed";
|
|
244
248
|
const handleWalletPay = (0, react_4.useCallback)(() => {
|
|
245
|
-
if (!selectedSrcToken || !
|
|
249
|
+
if (!selectedSrcToken || !connectedAddress)
|
|
246
250
|
return;
|
|
247
251
|
depositSentRef.current = false;
|
|
252
|
+
setWalletOrderId(undefined);
|
|
248
253
|
createSwapOrder({
|
|
249
254
|
recipientAddress,
|
|
250
255
|
orderType: "swap",
|
|
@@ -259,7 +264,7 @@ function CryptoPayPanel({ recipientAddress, destinationTokenAddress, destination
|
|
|
259
264
|
});
|
|
260
265
|
}, [
|
|
261
266
|
selectedSrcToken,
|
|
262
|
-
|
|
267
|
+
connectedAddress,
|
|
263
268
|
effectiveAddress,
|
|
264
269
|
recipientAddress,
|
|
265
270
|
selectedSrcChainId,
|
|
@@ -291,13 +296,28 @@ function CryptoPayPanel({ recipientAddress, destinationTokenAddress, destination
|
|
|
291
296
|
setTimeout(() => setCopied(false), 2000);
|
|
292
297
|
}
|
|
293
298
|
};
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
|
|
299
|
+
const [shouldAutoPay, setShouldAutoPay] = (0, react_4.useState)(false);
|
|
300
|
+
const handleConnectWallet = async () => {
|
|
301
|
+
try {
|
|
302
|
+
const wallet = await openConnectModal(CryptoPaymentMethod_1.connectModalConfig);
|
|
303
|
+
if (wallet) {
|
|
304
|
+
setShouldAutoPay(true);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
catch (error) {
|
|
308
|
+
console.error("Failed to connect wallet:", error);
|
|
309
|
+
}
|
|
297
310
|
};
|
|
311
|
+
// Auto-trigger payment after wallet connect
|
|
312
|
+
(0, react_4.useEffect)(() => {
|
|
313
|
+
if (shouldAutoPay && connectedAddress && selectedSrcToken && hasEnoughBalance && !isLoadingAnyspendQuote) {
|
|
314
|
+
setShouldAutoPay(false);
|
|
315
|
+
handleWalletPay();
|
|
316
|
+
}
|
|
317
|
+
}, [shouldAutoPay, connectedAddress, selectedSrcToken, hasEnoughBalance, isLoadingAnyspendQuote, handleWalletPay]);
|
|
298
318
|
const isLoading = isLoadingAnyspendQuote;
|
|
299
319
|
const isPending = isCreatingSwapOrder || isSendingDeposit || isWaitingForExecution;
|
|
300
|
-
const canPay =
|
|
320
|
+
const canPay = connectedAddress && selectedSrcToken && hasEnoughBalance && !isLoading && !isPending;
|
|
301
321
|
const chainName = anyspend_1.ALL_CHAINS[selectedSrcChainId]?.name || "the specified chain";
|
|
302
322
|
const chainLogoUrl = anyspend_1.ALL_CHAINS[selectedSrcChainId]?.logoUrl;
|
|
303
323
|
// Collapse QR on mobile when a wallet connector is available
|
|
@@ -316,7 +336,7 @@ function CryptoPayPanel({ recipientAddress, destinationTokenAddress, destination
|
|
|
316
336
|
name: token.name,
|
|
317
337
|
symbol: token.symbol,
|
|
318
338
|
});
|
|
319
|
-
}, supportedWalletVMs: ["evm", "svm"], token: undefined, trigger: (0, jsx_runtime_1.jsxs)("button", { className: (0, cn_1.cn)("flex w-full items-center justify-between rounded-xl border border-gray-200 bg-white px-4 py-3 transition-colors hover:border-gray-300 dark:border-neutral-700 dark:bg-neutral-800 dark:hover:border-neutral-600", classes?.tokenSelector), children: [selectedSrcToken ? ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3", children: [(0, jsx_runtime_1.jsx)(ChainTokenIcon_1.ChainTokenIcon, { chainUrl: anyspend_1.ALL_CHAINS[selectedSrcToken.chainId]?.logoUrl || "", tokenUrl: selectedSrcToken.metadata?.logoURI, className: "h-8 w-8" }), (0, jsx_runtime_1.jsxs)("div", { className: "text-left", children: [(0, jsx_runtime_1.jsx)("p", { className: "text-sm font-medium text-gray-900 dark:text-gray-100", children: selectedSrcToken.symbol }), (0, jsx_runtime_1.jsxs)("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: ["Balance: ", balance.formatted] })] })] })) : ((0, jsx_runtime_1.jsx)("span", { className: "text-sm text-gray-400", children: "Select token" })), (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronsUpDown, { className: "h-4 w-4 text-gray-400" })] }) })] }), (0, jsx_runtime_1.jsx)(react_3.motion.div, { initial: { opacity: 0, y: 6 }, animate: { opacity: 1, y: 0 }, transition: { duration: 0.25, ease: "easeOut" }, className: (0, cn_1.cn)("rounded-xl border border-gray-200 bg-gray-50 px-4 py-3 dark:border-neutral-700 dark:bg-neutral-800/50", classes?.quoteDisplay), children: (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-sm text-gray-500 dark:text-gray-400", children: "You pay" }), (0, jsx_runtime_1.jsx)(react_3.AnimatePresence, { mode: "wait", children: isLoadingAnyspendQuote ? ((0, jsx_runtime_1.jsx)(react_3.motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, transition: { duration: 0.15 }, children: (0, jsx_runtime_1.jsx)(react_2.TextShimmer, { duration: 1, className: "text-sm", children: "Fetching quote..." }) }, "quote-loading")) : ((0, jsx_runtime_1.jsxs)(react_3.motion.span, { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, transition: { duration: 0.15 }, className: "text-sm font-medium text-gray-900 dark:text-gray-100", children: [srcAmountFormatted, " ", selectedSrcToken?.symbol || ""] }, "quote-amount")) })] }) }), (0, jsx_runtime_1.jsx)(react_3.AnimatePresence, { children:
|
|
339
|
+
}, supportedWalletVMs: ["evm", "svm"], token: undefined, trigger: (0, jsx_runtime_1.jsxs)("button", { className: (0, cn_1.cn)("flex w-full items-center justify-between rounded-xl border border-gray-200 bg-white px-4 py-3 transition-colors hover:border-gray-300 dark:border-neutral-700 dark:bg-neutral-800 dark:hover:border-neutral-600", classes?.tokenSelector), children: [selectedSrcToken ? ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3", children: [(0, jsx_runtime_1.jsx)(ChainTokenIcon_1.ChainTokenIcon, { chainUrl: anyspend_1.ALL_CHAINS[selectedSrcToken.chainId]?.logoUrl || "", tokenUrl: selectedSrcToken.metadata?.logoURI, className: "h-8 w-8" }), (0, jsx_runtime_1.jsxs)("div", { className: "text-left", children: [(0, jsx_runtime_1.jsx)("p", { className: "text-sm font-medium text-gray-900 dark:text-gray-100", children: selectedSrcToken.symbol }), (0, jsx_runtime_1.jsxs)("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: ["Balance: ", balance.formatted] })] })] })) : ((0, jsx_runtime_1.jsx)("span", { className: "text-sm text-gray-400", children: "Select token" })), (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronsUpDown, { className: "h-4 w-4 text-gray-400" })] }) })] }), (0, jsx_runtime_1.jsx)(react_3.motion.div, { initial: { opacity: 0, y: 6 }, animate: { opacity: 1, y: 0 }, transition: { duration: 0.25, ease: "easeOut" }, className: (0, cn_1.cn)("rounded-xl border border-gray-200 bg-gray-50 px-4 py-3 dark:border-neutral-700 dark:bg-neutral-800/50", classes?.quoteDisplay), children: (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-sm text-gray-500 dark:text-gray-400", children: "You pay" }), (0, jsx_runtime_1.jsx)(react_3.AnimatePresence, { mode: "wait", children: isLoadingAnyspendQuote ? ((0, jsx_runtime_1.jsx)(react_3.motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, transition: { duration: 0.15 }, children: (0, jsx_runtime_1.jsx)(react_2.TextShimmer, { duration: 1, className: "text-sm", children: "Fetching quote..." }) }, "quote-loading")) : ((0, jsx_runtime_1.jsxs)(react_3.motion.span, { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, transition: { duration: 0.15 }, className: "text-sm font-medium text-gray-900 dark:text-gray-100", children: [srcAmountFormatted, " ", selectedSrcToken?.symbol || ""] }, "quote-amount")) })] }) }), (0, jsx_runtime_1.jsx)(react_3.AnimatePresence, { children: connectedAddress && selectedSrcToken && !hasEnoughBalance && !isLoading && ((0, jsx_runtime_1.jsxs)(react_3.motion.p, { initial: { opacity: 0, height: 0 }, animate: { opacity: 1, height: "auto" }, exit: { opacity: 0, height: 0 }, transition: { duration: 0.2, ease: "easeOut" }, className: "text-center text-sm text-red-500", children: ["Insufficient ", selectedSrcToken.symbol, " balance"] }, "balance-warning")) }), !connectedAddress ? ((0, jsx_runtime_1.jsx)(react_2.ShinyButton, { accentColor: themeColor || "hsl(var(--as-brand))", onClick: handleConnectWallet, className: (0, cn_1.cn)("w-full", classes?.payButton), textClassName: "text-white", children: "Connect Wallet to Pay" })) : ((0, jsx_runtime_1.jsx)(react_2.ShinyButton, { accentColor: themeColor || "hsl(var(--as-brand))", onClick: handleWalletPay, disabled: !canPay, className: (0, cn_1.cn)("w-full", classes?.payButton), textClassName: (0, cn_1.cn)(!canPay ? "text-as-secondary" : "text-white"), children: isPending ? ((0, jsx_runtime_1.jsxs)("span", { className: "flex items-center justify-center gap-2", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Loader2, { className: "h-4 w-4 animate-spin" }), isCreatingSwapOrder
|
|
320
340
|
? "Creating order..."
|
|
321
341
|
: isSendingDeposit
|
|
322
342
|
? "Confirm in wallet..."
|
|
@@ -5,6 +5,14 @@ export declare enum CryptoPaymentMethodType {
|
|
|
5
5
|
GLOBAL_WALLET = "global_wallet",
|
|
6
6
|
TRANSFER_CRYPTO = "transfer_crypto"
|
|
7
7
|
}
|
|
8
|
+
export declare const recommendWallets: (import("thirdweb/wallets").Wallet<"io.metamask"> | import("thirdweb/wallets").Wallet<"com.coinbase.wallet"> | import("thirdweb/wallets").Wallet<"me.rainbow"> | import("thirdweb/wallets").Wallet<"io.rabby">)[];
|
|
9
|
+
export declare const connectModalConfig: {
|
|
10
|
+
client: import("thirdweb").ThirdwebClient;
|
|
11
|
+
setActive: false;
|
|
12
|
+
size: "compact";
|
|
13
|
+
showThirdwebBranding: boolean;
|
|
14
|
+
wallets: (import("thirdweb/wallets").Wallet<"io.metamask"> | import("thirdweb/wallets").Wallet<"com.coinbase.wallet"> | import("thirdweb/wallets").Wallet<"me.rainbow"> | import("thirdweb/wallets").Wallet<"io.rabby">)[];
|
|
15
|
+
};
|
|
8
16
|
interface CryptoPaymentMethodProps {
|
|
9
17
|
selectedPaymentMethod: CryptoPaymentMethodType;
|
|
10
18
|
setSelectedPaymentMethod: (method: CryptoPaymentMethodType) => void;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
"use client";
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.CryptoPaymentMethodType = void 0;
|
|
4
|
+
exports.connectModalConfig = exports.recommendWallets = exports.CryptoPaymentMethodType = void 0;
|
|
5
5
|
exports.CryptoPaymentMethod = CryptoPaymentMethod;
|
|
6
6
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
7
7
|
const react_1 = require("../../../../global-account/react");
|
|
@@ -19,12 +19,19 @@ var CryptoPaymentMethodType;
|
|
|
19
19
|
CryptoPaymentMethodType["GLOBAL_WALLET"] = "global_wallet";
|
|
20
20
|
CryptoPaymentMethodType["TRANSFER_CRYPTO"] = "transfer_crypto";
|
|
21
21
|
})(CryptoPaymentMethodType || (exports.CryptoPaymentMethodType = CryptoPaymentMethodType = {}));
|
|
22
|
-
|
|
22
|
+
exports.recommendWallets = [
|
|
23
23
|
(0, wallets_1.createWallet)("io.metamask"),
|
|
24
24
|
(0, wallets_1.createWallet)("com.coinbase.wallet"),
|
|
25
25
|
(0, wallets_1.createWallet)("me.rainbow"),
|
|
26
26
|
(0, wallets_1.createWallet)("io.rabby"),
|
|
27
27
|
];
|
|
28
|
+
exports.connectModalConfig = {
|
|
29
|
+
client: thirdweb_1.client,
|
|
30
|
+
setActive: false,
|
|
31
|
+
size: "compact",
|
|
32
|
+
showThirdwebBranding: false,
|
|
33
|
+
wallets: exports.recommendWallets,
|
|
34
|
+
};
|
|
28
35
|
function CryptoPaymentMethod({ selectedPaymentMethod, setSelectedPaymentMethod, isCreatingOrder, onBack, onSelectPaymentMethod, classes, }) {
|
|
29
36
|
const { connectedEOAWallet, connectedSmartWallet } = (0, react_1.useAccountWallet)();
|
|
30
37
|
const { disconnect } = (0, react_2.useDisconnect)();
|
|
@@ -44,13 +51,7 @@ function CryptoPaymentMethod({ selectedPaymentMethod, setSelectedPaymentMethod,
|
|
|
44
51
|
if (connectedEOAWallet) {
|
|
45
52
|
disconnect(connectedEOAWallet);
|
|
46
53
|
}
|
|
47
|
-
const wallet = await openConnectModal(
|
|
48
|
-
client: thirdweb_1.client,
|
|
49
|
-
setActive: false,
|
|
50
|
-
size: "compact",
|
|
51
|
-
showThirdwebBranding: false,
|
|
52
|
-
wallets: recommendWallets,
|
|
53
|
-
});
|
|
54
|
+
const wallet = await openConnectModal(exports.connectModalConfig);
|
|
54
55
|
if (wallet) {
|
|
55
56
|
setSelectedPaymentMethod(CryptoPaymentMethodType.CONNECT_WALLET);
|
|
56
57
|
onSelectPaymentMethod(CryptoPaymentMethodType.CONNECT_WALLET);
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
4
4
|
const react_1 = require("../../../../global-account/react");
|
|
5
|
+
const profileDisplay_1 = require("../../../../global-account/react/utils/profileDisplay");
|
|
5
6
|
const truncateAddress_1 = require("../../../../shared/utils/truncateAddress");
|
|
6
7
|
const lucide_react_1 = require("lucide-react");
|
|
7
8
|
// Helper function to check if a string is a wallet address and format it
|
|
@@ -32,7 +33,7 @@ const LinkedAccountItem = ({ profile, profileToUnlink, unlinkingAccountId, isUnl
|
|
|
32
33
|
react_1.toast.error("Failed to copy address");
|
|
33
34
|
}
|
|
34
35
|
};
|
|
35
|
-
const displayImageUrl = profileData?.avatar || profile.imageUrl;
|
|
36
|
+
const displayImageUrl = (0, profileDisplay_1.validateImageUrl)(profileData?.avatar) || (0, profileDisplay_1.validateImageUrl)(profile.imageUrl);
|
|
36
37
|
const displayName = profileData?.name || displayTitle;
|
|
37
38
|
return ((0, jsx_runtime_1.jsxs)("div", { className: "linked-account-item hover:bg-b3-line group flex cursor-pointer items-center justify-between rounded-xl p-4 transition-colors", children: [(0, jsx_runtime_1.jsxs)("div", { className: "linked-account-info flex items-center gap-3", children: [displayImageUrl ? ((0, jsx_runtime_1.jsx)("img", { src: displayImageUrl, alt: profile.title, className: "linked-account-avatar linked-account-avatar-image size-10 rounded-full" })) : ((0, jsx_runtime_1.jsx)("div", { className: "linked-account-avatar linked-account-avatar-placeholder bg-b3-primary-wash flex h-10 w-10 items-center justify-center rounded-full", children: (0, jsx_runtime_1.jsx)("span", { className: "linked-account-initial text-b3-grey font-neue-montreal-semibold text-sm uppercase", children: profile.initial }) })), (0, jsx_runtime_1.jsxs)("div", { className: "linked-account-details", children: [(0, jsx_runtime_1.jsxs)("div", { className: "linked-account-title-row flex items-center gap-2", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-1", children: [(0, jsx_runtime_1.jsx)("span", { className: `linked-account-title text-b3-grey font-neue-montreal-semibold ${isAddress
|
|
38
39
|
? "font-mono text-sm" // Use monospace font for addresses
|
|
@@ -7,6 +7,7 @@ const jsx_runtime_1 = require("react/jsx-runtime");
|
|
|
7
7
|
const ens_normalize_1 = require("@adraffy/ens-normalize");
|
|
8
8
|
const app_1 = __importDefault(require("../../../../global-account/app"));
|
|
9
9
|
const react_1 = require("../../../../global-account/react");
|
|
10
|
+
const profileDisplay_1 = require("../../../../global-account/react/utils/profileDisplay");
|
|
10
11
|
const utils_1 = require("../../../../shared/utils");
|
|
11
12
|
const lucide_react_1 = require("lucide-react");
|
|
12
13
|
const react_2 = require("react");
|
|
@@ -30,8 +31,7 @@ const SettingsProfileCard = () => {
|
|
|
30
31
|
const [editedUsername, setEditedUsername] = (0, react_2.useState)("");
|
|
31
32
|
const [isSaving, setIsSaving] = (0, react_2.useState)(false);
|
|
32
33
|
const inputRef = (0, react_2.useRef)(null);
|
|
33
|
-
|
|
34
|
-
const avatarSrc = user?.avatar || profile?.avatar;
|
|
34
|
+
const avatarSrc = (0, profileDisplay_1.validateImageUrl)(user?.avatar) || (0, profileDisplay_1.validateImageUrl)(profile?.avatar);
|
|
35
35
|
// Get current username - prioritize user.username, fallback to profile data
|
|
36
36
|
const currentUsername = user?.username || profile?.displayName || (0, utils_1.formatUsername)(profile?.name || "");
|
|
37
37
|
// Focus input when entering edit mode
|
|
@@ -8,6 +8,7 @@ const jsx_runtime_1 = require("react/jsx-runtime");
|
|
|
8
8
|
const anyspend_1 = require("../../../../anyspend");
|
|
9
9
|
const ChainTokenIcon_1 = require("../../../../anyspend/react/components/common/ChainTokenIcon");
|
|
10
10
|
const react_1 = require("../../../../global-account/react");
|
|
11
|
+
const profileDisplay_1 = require("../../../../global-account/react/utils/profileDisplay");
|
|
11
12
|
const number_1 = require("../../../../shared/utils/number");
|
|
12
13
|
const invariant_1 = __importDefault(require("invariant"));
|
|
13
14
|
const lucide_react_1 = require("lucide-react");
|
|
@@ -20,7 +21,8 @@ const button_1 = require("../ui/button");
|
|
|
20
21
|
// Component for displaying a recent address with profile data
|
|
21
22
|
function RecentAddressItem({ address, onClick }) {
|
|
22
23
|
const { data: profileData } = (0, react_1.useProfile)({ address });
|
|
23
|
-
|
|
24
|
+
const validatedAvatar = (0, profileDisplay_1.validateImageUrl)(profileData?.avatar);
|
|
25
|
+
return ((0, jsx_runtime_1.jsxs)("button", { onClick: onClick, className: "flex items-center gap-2 rounded-xl px-3 py-2 transition-colors hover:bg-[#fafafa]", children: [validatedAvatar ? ((0, jsx_runtime_1.jsx)("img", { src: validatedAvatar, alt: profileData?.name || address, className: "h-10 w-10 rounded-full" })) : ((0, jsx_runtime_1.jsx)("div", { className: "flex h-10 w-10 items-center justify-center rounded-full border border-[#e4e4e7] bg-[#f4f4f5]", children: (0, jsx_runtime_1.jsx)(lucide_react_1.Wallet, { className: "h-5 w-5 text-[#a0a0ab]" }) })), (0, jsx_runtime_1.jsx)("div", { className: "flex flex-col items-start", children: (0, jsx_runtime_1.jsxs)("span", { className: "font-neue-montreal-medium text-base tracking-[-0.32px] text-[#70707b]", children: [address.slice(0, 6), "...", address.slice(-4), profileData?.name && ` (${profileData.name})`] }) })] }));
|
|
24
26
|
}
|
|
25
27
|
function Send({ recipientAddress: initialRecipient, onSuccess }) {
|
|
26
28
|
const { address } = (0, react_1.useAccountWallet)();
|
|
@@ -45,6 +47,7 @@ function Send({ recipientAddress: initialRecipient, onSuccess }) {
|
|
|
45
47
|
const { data: validatedProfileData } = (0, react_1.useProfile)({
|
|
46
48
|
address: showValidatedResult && recipientAddress && (0, viem_1.isAddress)(recipientAddress) ? recipientAddress : undefined,
|
|
47
49
|
});
|
|
50
|
+
const validatedRecipientAvatar = (0, profileDisplay_1.validateImageUrl)(validatedProfileData?.avatar);
|
|
48
51
|
// Address validation
|
|
49
52
|
const handleRecipientAddressChange = (value) => {
|
|
50
53
|
setRecipientAddress(value);
|
|
@@ -185,7 +188,7 @@ function Send({ recipientAddress: initialRecipient, onSuccess }) {
|
|
|
185
188
|
return "Send";
|
|
186
189
|
}
|
|
187
190
|
};
|
|
188
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: "dark:bg-b3-background flex h-[600px] w-full flex-col bg-white", children: [(0, jsx_runtime_1.jsx)(ModalHeader_1.default, { handleBack: handleBack, title: getStepTitle() }), (0, jsx_runtime_1.jsxs)("div", { className: "flex-1 overflow-y-auto", children: [step === "recipient" && ((0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col gap-6 p-5", children: [(0, jsx_runtime_1.jsxs)("div", { className: "dark:border-b3-line dark:bg-b3-background flex h-12 w-full items-stretch overflow-hidden rounded-lg border border-[#d1d1d6] bg-white", children: [(0, jsx_runtime_1.jsx)("div", { className: "flex w-12 items-center justify-center bg-transparent px-3 py-2", children: (0, jsx_runtime_1.jsx)("span", { className: "font-neue-montreal-medium text-base text-[#3f3f46] dark:text-white", children: "To" }) }), (0, jsx_runtime_1.jsxs)("div", { className: "flex flex-1 items-center border-l border-[#d1d1d6] px-3 py-2", children: [(0, jsx_runtime_1.jsx)("input", { type: "text", value: recipientAddress, onChange: e => handleRecipientAddressChange(e.target.value), placeholder: "ENS or Address", className: "font-neue-montreal-medium dark:bg-b3-background flex-1 text-base text-[#70707b] outline-none placeholder:text-[#70707b] dark:text-white dark:placeholder:text-white" }), (0, jsx_runtime_1.jsx)("button", { onClick: handlePaste, className: "font-inter ml-2 rounded-md border border-[#e4e4e7] bg-[#fafafa] px-2.5 py-0.5 text-sm font-medium text-[#3f3f46] transition-colors hover:bg-[#f4f4f5]", children: "Paste" })] })] }), addressError && (0, jsx_runtime_1.jsx)("p", { className: "font-neue-montreal-medium -mt-4 text-xs text-red-500", children: addressError }), showValidatedResult && recipientAddress && (0, viem_1.isAddress)(recipientAddress) && ((0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col gap-2", children: [(0, jsx_runtime_1.jsx)("div", { className: "flex items-center gap-1", children: (0, jsx_runtime_1.jsx)("span", { className: "font-sf-pro-text text-sm font-semibold leading-[1.3] tracking-[-0.41px] text-[#0b57c2]", children: "Result" }) }), (0, jsx_runtime_1.jsxs)("button", { onClick: handleSelectValidatedAddress, className: "dark:bg-b3-background dark:border-b3-line flex items-center gap-2 rounded-xl bg-[#f4f4f5] px-3 py-2 transition-colors hover:bg-[#e4e4e7]", children: [
|
|
191
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "dark:bg-b3-background flex h-[600px] w-full flex-col bg-white", children: [(0, jsx_runtime_1.jsx)(ModalHeader_1.default, { handleBack: handleBack, title: getStepTitle() }), (0, jsx_runtime_1.jsxs)("div", { className: "flex-1 overflow-y-auto", children: [step === "recipient" && ((0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col gap-6 p-5", children: [(0, jsx_runtime_1.jsxs)("div", { className: "dark:border-b3-line dark:bg-b3-background flex h-12 w-full items-stretch overflow-hidden rounded-lg border border-[#d1d1d6] bg-white", children: [(0, jsx_runtime_1.jsx)("div", { className: "flex w-12 items-center justify-center bg-transparent px-3 py-2", children: (0, jsx_runtime_1.jsx)("span", { className: "font-neue-montreal-medium text-base text-[#3f3f46] dark:text-white", children: "To" }) }), (0, jsx_runtime_1.jsxs)("div", { className: "flex flex-1 items-center border-l border-[#d1d1d6] px-3 py-2", children: [(0, jsx_runtime_1.jsx)("input", { type: "text", value: recipientAddress, onChange: e => handleRecipientAddressChange(e.target.value), placeholder: "ENS or Address", className: "font-neue-montreal-medium dark:bg-b3-background flex-1 text-base text-[#70707b] outline-none placeholder:text-[#70707b] dark:text-white dark:placeholder:text-white" }), (0, jsx_runtime_1.jsx)("button", { onClick: handlePaste, className: "font-inter ml-2 rounded-md border border-[#e4e4e7] bg-[#fafafa] px-2.5 py-0.5 text-sm font-medium text-[#3f3f46] transition-colors hover:bg-[#f4f4f5]", children: "Paste" })] })] }), addressError && (0, jsx_runtime_1.jsx)("p", { className: "font-neue-montreal-medium -mt-4 text-xs text-red-500", children: addressError }), showValidatedResult && recipientAddress && (0, viem_1.isAddress)(recipientAddress) && ((0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col gap-2", children: [(0, jsx_runtime_1.jsx)("div", { className: "flex items-center gap-1", children: (0, jsx_runtime_1.jsx)("span", { className: "font-sf-pro-text text-sm font-semibold leading-[1.3] tracking-[-0.41px] text-[#0b57c2]", children: "Result" }) }), (0, jsx_runtime_1.jsxs)("button", { onClick: handleSelectValidatedAddress, className: "dark:bg-b3-background dark:border-b3-line flex items-center gap-2 rounded-xl bg-[#f4f4f5] px-3 py-2 transition-colors hover:bg-[#e4e4e7]", children: [validatedRecipientAvatar ? ((0, jsx_runtime_1.jsx)("img", { src: validatedRecipientAvatar, alt: validatedProfileData?.name || recipientAddress, className: "h-10 w-10 rounded-full" })) : ((0, jsx_runtime_1.jsx)("div", { className: "dark:border-b3-line dark:bg-b3-background flex h-10 w-10 items-center justify-center rounded-full border border-[#e4e4e7] bg-[#f4f4f5]", children: (0, jsx_runtime_1.jsx)(lucide_react_1.Wallet, { className: "h-5 w-5 text-[#a0a0ab] dark:text-white" }) })), (0, jsx_runtime_1.jsxs)("span", { className: "font-neue-montreal-medium text-base tracking-[-0.32px] text-[#70707b] dark:text-white", children: [recipientAddress.slice(0, 6), "...", recipientAddress.slice(-4), validatedProfileData?.name && ` (${validatedProfileData.name})`] })] })] })), recentAddresses.length > 0 && ((0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col gap-2", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-1", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Clock, { className: "h-3.5 w-3.5 text-[#3f3f46]" }), (0, jsx_runtime_1.jsx)("span", { className: "font-sf-pro-text text-sm font-semibold leading-[1.3] tracking-[-0.41px] text-[#3f3f46]", children: "Recents" })] }), (0, jsx_runtime_1.jsx)("div", { className: "flex flex-col", children: recentAddresses.map((recent, index) => ((0, jsx_runtime_1.jsx)(RecentAddressItem, { address: recent.address, onClick: () => {
|
|
189
192
|
// Just fill the input and show validation - don't auto-proceed
|
|
190
193
|
handleRecipientAddressChange(recent.address);
|
|
191
194
|
} }, index))) })] }))] })), step === "token" && ((0, jsx_runtime_1.jsx)("div", { className: "flex flex-col p-5", children: isLoadingBalance ? ((0, jsx_runtime_1.jsx)("div", { className: "space-y-4", children: (0, jsx_runtime_1.jsx)("div", { className: "space-y-1", children: [...Array(3)].map((_, index) => ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between rounded-xl p-3", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3", children: [(0, jsx_runtime_1.jsx)("div", { className: "bg-b3-line h-10 w-10 animate-pulse rounded-full" }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("div", { className: "bg-b3-line mb-1 h-4 w-16 animate-pulse rounded" }), (0, jsx_runtime_1.jsx)("div", { className: "bg-b3-line h-3 w-24 animate-pulse rounded" })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "text-right", children: [(0, jsx_runtime_1.jsx)("div", { className: "bg-b3-line mb-1 h-4 w-20 animate-pulse rounded" }), (0, jsx_runtime_1.jsx)("div", { className: "bg-b3-line h-3 w-16 animate-pulse rounded" })] })] }, index))) }) })) : simBalance?.balances && simBalance.balances.length > 0 ? ((0, jsx_runtime_1.jsx)("div", { className: "space-y-4", children: (0, jsx_runtime_1.jsx)("div", { className: "space-y-1", children: simBalance.balances.map(token => ((0, jsx_runtime_1.jsxs)("div", { className: "hover:bg-b3-line/60 dark:hover:bg-b3-primary-wash/40 group flex cursor-pointer items-center justify-between rounded-xl p-3 transition-all duration-200", onClick: () => {
|
|
@@ -7,6 +7,7 @@ const utils_1 = require("../../../../shared/utils");
|
|
|
7
7
|
const lucide_react_1 = require("lucide-react");
|
|
8
8
|
const react_1 = require("react");
|
|
9
9
|
const profileApi_1 = require("../../utils/profileApi");
|
|
10
|
+
const profileDisplay_1 = require("../../utils/profileDisplay");
|
|
10
11
|
const IPFSMediaRenderer_1 = require("../IPFSMediaRenderer/IPFSMediaRenderer");
|
|
11
12
|
const input_1 = require("../ui/input");
|
|
12
13
|
/**
|
|
@@ -159,5 +160,5 @@ function SingleUserSearchSelector({ onSelectUser, profileTypeFilter, placeholder
|
|
|
159
160
|
const getProfileTypeBadges = (profiles) => {
|
|
160
161
|
return profiles.map(p => p.type);
|
|
161
162
|
};
|
|
162
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: (0, utils_1.cn)("single-user-search-selector b3-root relative w-full", className), ref: dropdownRef, children: [(0, jsx_runtime_1.jsxs)("div", { className: "single-user-search-input-wrapper relative flex items-center", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Search, { className: "single-user-search-icon pointer-events-none absolute text-gray-400", style: { left: "12px", width: "16px", height: "16px" } }), (0, jsx_runtime_1.jsx)(input_1.Input, { ref: inputRef, type: "text", value: searchQuery, onChange: e => setSearchQuery(e.target.value), placeholder: placeholder, className: (0, utils_1.cn)("single-user-search-input w-full border-gray-300 focus:border-blue-500 focus:ring-blue-500"), style: { paddingLeft: "44px", paddingRight: "44px" } }), showClearButton && searchQuery && ((0, jsx_runtime_1.jsx)("button", { onClick: handleClear, className: "single-user-search-clear-button absolute text-gray-400 transition-colors hover:text-gray-600", style: { right: "12px" }, type: "button", children: (0, jsx_runtime_1.jsx)(lucide_react_1.X, { style: { width: "16px", height: "16px" } }) }))] }), isSearching && (0, jsx_runtime_1.jsx)("div", { className: "single-user-search-loading mt-2 text-sm text-gray-500", children: "Searching..." }), error && !isSearching && (0, jsx_runtime_1.jsx)("div", { className: "single-user-search-error mt-2 text-sm text-red-500", children: error }), showDropdown && searchResult && !isSearching && ((0, jsx_runtime_1.jsx)("div", { className: "single-user-search-dropdown absolute z-50 mt-2 w-full rounded-lg border border-gray-200 bg-white shadow-lg", children: (0, jsx_runtime_1.jsx)("button", { onClick: () => handleSelectUser(searchResult), className: "single-user-search-result-button w-full px-4 py-3 text-left transition-colors hover:bg-gray-50", type: "button", children: (0, jsx_runtime_1.jsxs)("div", { className: "flex items-start gap-3", children: [(0, jsx_runtime_1.jsx)("div", { className: "single-user-search-result-avatar h-11 w-11 shrink-0", children: (0, jsx_runtime_1.jsx)(IPFSMediaRenderer_1.IPFSMediaRenderer, { src: searchResult.avatar, alt: getDisplayName(searchResult), className: "h-full w-full rounded-full object-cover" }) }), (0, jsx_runtime_1.jsxs)("div", { className: "single-user-search-result-info min-w-0 flex-1 pt-0.5", children: [(0, jsx_runtime_1.jsx)("div", { className: "single-user-search-result-name text-base font-semibold text-gray-900", children: getDisplayName(searchResult) }), searchResult.address && ((0, jsx_runtime_1.jsxs)("div", { className: "single-user-search-result-address mt-1 font-mono text-xs text-gray-500", children: [searchResult.address.slice(0, 6), "...", searchResult.address.slice(-4)] })), searchResult.bio && ((0, jsx_runtime_1.jsx)("div", { className: "single-user-search-result-bio mt-1.5 line-clamp-2 text-sm text-gray-600", children: searchResult.bio })), showBadges && ((0, jsx_runtime_1.jsx)("div", { className: "single-user-search-result-badges mt-2 flex flex-wrap gap-1.5", children: getProfileTypeBadges(searchResult.profiles).map((type, index) => ((0, jsx_runtime_1.jsx)("span", { className: "single-user-search-result-badge inline-flex items-center rounded-full bg-blue-100 px-2.5 py-0.5 text-xs font-medium text-blue-800", children: type }, `${type}-${index}`))) }))] })] }) }) }))] }));
|
|
163
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: (0, utils_1.cn)("single-user-search-selector b3-root relative w-full", className), ref: dropdownRef, children: [(0, jsx_runtime_1.jsxs)("div", { className: "single-user-search-input-wrapper relative flex items-center", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Search, { className: "single-user-search-icon pointer-events-none absolute text-gray-400", style: { left: "12px", width: "16px", height: "16px" } }), (0, jsx_runtime_1.jsx)(input_1.Input, { ref: inputRef, type: "text", value: searchQuery, onChange: e => setSearchQuery(e.target.value), placeholder: placeholder, className: (0, utils_1.cn)("single-user-search-input w-full border-gray-300 focus:border-blue-500 focus:ring-blue-500"), style: { paddingLeft: "44px", paddingRight: "44px" } }), showClearButton && searchQuery && ((0, jsx_runtime_1.jsx)("button", { onClick: handleClear, className: "single-user-search-clear-button absolute text-gray-400 transition-colors hover:text-gray-600", style: { right: "12px" }, type: "button", children: (0, jsx_runtime_1.jsx)(lucide_react_1.X, { style: { width: "16px", height: "16px" } }) }))] }), isSearching && (0, jsx_runtime_1.jsx)("div", { className: "single-user-search-loading mt-2 text-sm text-gray-500", children: "Searching..." }), error && !isSearching && (0, jsx_runtime_1.jsx)("div", { className: "single-user-search-error mt-2 text-sm text-red-500", children: error }), showDropdown && searchResult && !isSearching && ((0, jsx_runtime_1.jsx)("div", { className: "single-user-search-dropdown absolute z-50 mt-2 w-full rounded-lg border border-gray-200 bg-white shadow-lg", children: (0, jsx_runtime_1.jsx)("button", { onClick: () => handleSelectUser(searchResult), className: "single-user-search-result-button w-full px-4 py-3 text-left transition-colors hover:bg-gray-50", type: "button", children: (0, jsx_runtime_1.jsxs)("div", { className: "flex items-start gap-3", children: [(0, jsx_runtime_1.jsx)("div", { className: "single-user-search-result-avatar h-11 w-11 shrink-0", children: (0, jsx_runtime_1.jsx)(IPFSMediaRenderer_1.IPFSMediaRenderer, { src: (0, profileDisplay_1.validateImageUrl)(searchResult.avatar) ?? undefined, alt: getDisplayName(searchResult), className: "h-full w-full rounded-full object-cover" }) }), (0, jsx_runtime_1.jsxs)("div", { className: "single-user-search-result-info min-w-0 flex-1 pt-0.5", children: [(0, jsx_runtime_1.jsx)("div", { className: "single-user-search-result-name text-base font-semibold text-gray-900", children: getDisplayName(searchResult) }), searchResult.address && ((0, jsx_runtime_1.jsxs)("div", { className: "single-user-search-result-address mt-1 font-mono text-xs text-gray-500", children: [searchResult.address.slice(0, 6), "...", searchResult.address.slice(-4)] })), searchResult.bio && ((0, jsx_runtime_1.jsx)("div", { className: "single-user-search-result-bio mt-1.5 line-clamp-2 text-sm text-gray-600", children: searchResult.bio })), showBadges && ((0, jsx_runtime_1.jsx)("div", { className: "single-user-search-result-badges mt-2 flex flex-wrap gap-1.5", children: getProfileTypeBadges(searchResult.profiles).map((type, index) => ((0, jsx_runtime_1.jsx)("span", { className: "single-user-search-result-badge inline-flex items-center rounded-full bg-blue-100 px-2.5 py-0.5 text-xs font-medium text-blue-800", children: type }, `${type}-${index}`))) }))] })] }) }) }))] }));
|
|
163
164
|
}
|
|
@@ -202,5 +202,9 @@ variablePricing, feeOnTop, kycEnabled = false, callbackMetadata: callbackMetadat
|
|
|
202
202
|
(shippingOptions && shippingOptions.length > 0) ||
|
|
203
203
|
collectShippingAddress ||
|
|
204
204
|
enableDiscountCode;
|
|
205
|
-
return (_jsx(AnySpendFingerprintWrapper, { fingerprint: fingerprint, children: _jsx(AnySpendCustomizationProvider, { slots: slots, content:
|
|
205
|
+
return (_jsx(AnySpendFingerprintWrapper, { fingerprint: fingerprint, children: _jsx(AnySpendCustomizationProvider, { slots: slots, content: {
|
|
206
|
+
successTitle: "Payment Successful",
|
|
207
|
+
successDescription: "Your payment has been processed successfully.",
|
|
208
|
+
...content,
|
|
209
|
+
}, theme: theme, children: _jsx(CheckoutLayout, { mode: mode, paymentPanel: _jsxs(_Fragment, { children: [isVariablePricingActive && tokenData && variablePricing && (_jsx(VariablePricingInput, { config: variablePricing, tokenDecimals: tokenDecimals, tokenSymbol: tokenSymbol, themeColor: themeColor, onChange: setVariablePricingAmount })), hasFormContent && (_jsxs("div", { className: "mb-6", children: [_jsx(CheckoutFormPanel, { formSchema: formSchema, formComponent: formComponent, shippingOptions: shippingOptions, collectShippingAddress: collectShippingAddress, enableDiscountCode: enableDiscountCode, validateDiscount: validateDiscount, tokenSymbol: tokenSymbol, tokenDecimals: tokenDecimals, classes: classes, formData: formData, onFormDataChange: handleFormDataChange, selectedShipping: selectedShipping, onShippingChange: handleShippingChange, appliedDiscount: appliedDiscount, onDiscountApplied: handleDiscountApplied, onDiscountRemoved: handleDiscountRemoved, shippingAddress: shippingAddress, onShippingAddressChange: setShippingAddress, checkoutFormSlot: slots?.checkoutForm }), _jsx("div", { className: "mt-6 border-t border-gray-200 dark:border-neutral-700" })] })), _jsx(CheckoutPaymentPanel, { recipientAddress: recipientAddress, destinationTokenAddress: destinationTokenAddress, destinationTokenChainId: destinationTokenChainId, totalAmount: computedTotal, buttonText: buttonText, themeColor: themeColor, returnUrl: returnUrl, returnLabel: returnLabel, onSuccess: onSuccess, onError: onError, classes: classes, defaultPaymentMethod: defaultPaymentMethod, senderAddress: senderAddress, showPoints: showPoints, showOrderId: showOrderId, callbackMetadata: checkoutFormMetadata, isFormValid: isFormValid, feeOnTop: feeOnTop, kycEnabled: kycEnabled })] }), cartPanel: _jsx(CheckoutCartPanel, { items: items, totalAmount: computedTotal, tokenSymbol: tokenSymbol, tokenDecimals: tokenDecimals, organizationName: organizationName, organizationLogo: organizationLogo, classes: classes, footer: footer, shipping: effectiveShipping, tax: typeof tax === "string" ? { amount: tax } : tax, discount: effectiveDiscount, summaryLines: summaryLines, usdEquivalent: usdEquivalent }), classes: classes }) }) }));
|
|
206
210
|
}
|
|
@@ -8,9 +8,8 @@ import { useOnOrderSuccess } from "../../../../anyspend/react/hooks/useOnOrderSu
|
|
|
8
8
|
import { ALL_CHAINS, RELAY_SOLANA_MAINNET_CHAIN_ID, getAvailableChainIds } from "../../../../anyspend/index.js";
|
|
9
9
|
import { getPaymentUrl } from "../../../../anyspend/utils/chain.js";
|
|
10
10
|
import { isNativeToken } from "../../../../anyspend/utils/token.js";
|
|
11
|
-
import { useAccountWallet,
|
|
11
|
+
import { useAccountWallet, useIsMobile, useSimTokenBalance, useTokenData, useUnifiedChainSwitchAndExecute, } from "../../../../global-account/react/index.js";
|
|
12
12
|
import { ShinyButton, TextShimmer } from "../../../../global-account/react/index.js";
|
|
13
|
-
import { thirdwebB3Chain } from "../../../../shared/constants/chains/b3Chain.js";
|
|
14
13
|
import { formatTokenAmount } from "../../../../shared/utils/number.js";
|
|
15
14
|
import { cn } from "../../../../shared/utils/cn.js";
|
|
16
15
|
import { Check, ChevronDown, ChevronsUpDown, Copy, Loader2, QrCode } from "lucide-react";
|
|
@@ -18,8 +17,10 @@ import { QRCodeSVG } from "qrcode.react";
|
|
|
18
17
|
import { encodeFunctionData, erc20Abi } from "viem";
|
|
19
18
|
import { AnimatePresence, motion } from "motion/react";
|
|
20
19
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
20
|
+
import { useConnectModal } from "thirdweb/react";
|
|
21
21
|
import { TokenSelector } from "@relayprotocol/relay-kit-ui";
|
|
22
22
|
import { ChainTokenIcon } from "../common/ChainTokenIcon.js";
|
|
23
|
+
import { connectModalConfig } from "../common/CryptoPaymentMethod.js";
|
|
23
24
|
export function CryptoPayPanel({ recipientAddress, destinationTokenAddress, destinationTokenChainId, totalAmount, buttonText = "Pay", themeColor, onSuccess, onOrderCreated, onError, callbackMetadata, classes, senderAddress, }) {
|
|
24
25
|
// Stable refs for callback props to avoid re-triggering effects
|
|
25
26
|
const onErrorRef = useRef(onError);
|
|
@@ -32,11 +33,10 @@ export function CryptoPayPanel({ recipientAddress, destinationTokenAddress, dest
|
|
|
32
33
|
const [selectedSrcChainId, setSelectedSrcChainId] = useState(destinationTokenChainId);
|
|
33
34
|
const [selectedSrcToken, setSelectedSrcToken] = useState(null);
|
|
34
35
|
const [copied, setCopied] = useState(false);
|
|
35
|
-
const { address: walletAddress } = useAccountWallet();
|
|
36
|
-
const
|
|
37
|
-
const
|
|
38
|
-
const
|
|
39
|
-
const setB3ModalContentType = useModalStore(state => state.setB3ModalContentType);
|
|
36
|
+
const { address: walletAddress, connectedEOAWallet } = useAccountWallet();
|
|
37
|
+
const connectedAddress = walletAddress || connectedEOAWallet?.getAccount()?.address;
|
|
38
|
+
const effectiveAddress = connectedAddress || senderAddress;
|
|
39
|
+
const { connect: openConnectModal } = useConnectModal();
|
|
40
40
|
const { data: dstTokenData } = useTokenData(destinationTokenChainId, destinationTokenAddress);
|
|
41
41
|
// Default to destination token data once available
|
|
42
42
|
useEffect(() => {
|
|
@@ -166,9 +166,8 @@ export function CryptoPayPanel({ recipientAddress, destinationTokenAddress, dest
|
|
|
166
166
|
/* ------------------------------------------------------------------ */
|
|
167
167
|
const [walletOrderId, setWalletOrderId] = useState();
|
|
168
168
|
const [isSendingDeposit, setIsSendingDeposit] = useState(false);
|
|
169
|
-
const [depositRejected, setDepositRejected] = useState(false);
|
|
170
169
|
const depositSentRef = useRef(false);
|
|
171
|
-
const {
|
|
170
|
+
const { switchChainAndExecuteWithEOA } = useUnifiedChainSwitchAndExecute();
|
|
172
171
|
const { createOrder: createSwapOrder, isCreatingOrder: isCreatingSwapOrder } = useAnyspendCreateOrder({
|
|
173
172
|
onSuccess: data => {
|
|
174
173
|
const id = data.data?.id;
|
|
@@ -183,7 +182,7 @@ export function CryptoPayPanel({ recipientAddress, destinationTokenAddress, dest
|
|
|
183
182
|
const { orderAndTransactions: walletOat } = useAnyspendOrderAndTransactions(walletOrderId);
|
|
184
183
|
// Auto-send deposit tx once swap order is ready
|
|
185
184
|
useEffect(() => {
|
|
186
|
-
if (!walletOat?.data?.order || depositSentRef.current
|
|
185
|
+
if (!walletOat?.data?.order || depositSentRef.current)
|
|
187
186
|
return;
|
|
188
187
|
const order = walletOat.data.order;
|
|
189
188
|
if (order.status !== "scanning_deposit_transaction")
|
|
@@ -195,8 +194,9 @@ export function CryptoPayPanel({ recipientAddress, destinationTokenAddress, dest
|
|
|
195
194
|
try {
|
|
196
195
|
setIsSendingDeposit(true);
|
|
197
196
|
const amount = BigInt(order.srcAmount);
|
|
197
|
+
let txHash;
|
|
198
198
|
if (isNativeToken(order.srcTokenAddress)) {
|
|
199
|
-
await
|
|
199
|
+
txHash = await switchChainAndExecuteWithEOA(order.srcChain, {
|
|
200
200
|
to: order.globalAddress,
|
|
201
201
|
value: amount,
|
|
202
202
|
});
|
|
@@ -207,12 +207,18 @@ export function CryptoPayPanel({ recipientAddress, destinationTokenAddress, dest
|
|
|
207
207
|
functionName: "transfer",
|
|
208
208
|
args: [order.globalAddress, amount],
|
|
209
209
|
});
|
|
210
|
-
await
|
|
210
|
+
txHash = await switchChainAndExecuteWithEOA(order.srcChain, {
|
|
211
211
|
to: order.srcTokenAddress,
|
|
212
212
|
data,
|
|
213
213
|
value: BigInt(0),
|
|
214
214
|
});
|
|
215
215
|
}
|
|
216
|
+
if (!txHash) {
|
|
217
|
+
// User cancelled or tx failed — reset so they can retry
|
|
218
|
+
depositSentRef.current = false;
|
|
219
|
+
setWalletOrderId(undefined);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
216
222
|
// Deposit sent — notify parent to transition to order lifecycle tracking
|
|
217
223
|
if (walletOrderId) {
|
|
218
224
|
onOrderCreatedRef.current?.(walletOrderId);
|
|
@@ -220,10 +226,8 @@ export function CryptoPayPanel({ recipientAddress, destinationTokenAddress, dest
|
|
|
220
226
|
}
|
|
221
227
|
catch (error) {
|
|
222
228
|
depositSentRef.current = false;
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
setDepositRejected(true);
|
|
226
|
-
}
|
|
229
|
+
// Reset order so user can retry
|
|
230
|
+
setWalletOrderId(undefined);
|
|
227
231
|
onErrorRef.current?.(error instanceof Error ? error : new Error(error?.message || "Transaction rejected"));
|
|
228
232
|
}
|
|
229
233
|
finally {
|
|
@@ -231,7 +235,7 @@ export function CryptoPayPanel({ recipientAddress, destinationTokenAddress, dest
|
|
|
231
235
|
}
|
|
232
236
|
};
|
|
233
237
|
sendDeposit();
|
|
234
|
-
}, [walletOat,
|
|
238
|
+
}, [walletOat, switchChainAndExecuteWithEOA, walletOrderId]);
|
|
235
239
|
useOnOrderSuccess({
|
|
236
240
|
orderData: walletOat,
|
|
237
241
|
orderId: walletOrderId,
|
|
@@ -239,9 +243,10 @@ export function CryptoPayPanel({ recipientAddress, destinationTokenAddress, dest
|
|
|
239
243
|
});
|
|
240
244
|
const isWaitingForExecution = !!walletOrderId && walletOat?.data?.order.status !== "executed";
|
|
241
245
|
const handleWalletPay = useCallback(() => {
|
|
242
|
-
if (!selectedSrcToken || !
|
|
246
|
+
if (!selectedSrcToken || !connectedAddress)
|
|
243
247
|
return;
|
|
244
248
|
depositSentRef.current = false;
|
|
249
|
+
setWalletOrderId(undefined);
|
|
245
250
|
createSwapOrder({
|
|
246
251
|
recipientAddress,
|
|
247
252
|
orderType: "swap",
|
|
@@ -256,7 +261,7 @@ export function CryptoPayPanel({ recipientAddress, destinationTokenAddress, dest
|
|
|
256
261
|
});
|
|
257
262
|
}, [
|
|
258
263
|
selectedSrcToken,
|
|
259
|
-
|
|
264
|
+
connectedAddress,
|
|
260
265
|
effectiveAddress,
|
|
261
266
|
recipientAddress,
|
|
262
267
|
selectedSrcChainId,
|
|
@@ -288,13 +293,28 @@ export function CryptoPayPanel({ recipientAddress, destinationTokenAddress, dest
|
|
|
288
293
|
setTimeout(() => setCopied(false), 2000);
|
|
289
294
|
}
|
|
290
295
|
};
|
|
291
|
-
const
|
|
292
|
-
|
|
293
|
-
|
|
296
|
+
const [shouldAutoPay, setShouldAutoPay] = useState(false);
|
|
297
|
+
const handleConnectWallet = async () => {
|
|
298
|
+
try {
|
|
299
|
+
const wallet = await openConnectModal(connectModalConfig);
|
|
300
|
+
if (wallet) {
|
|
301
|
+
setShouldAutoPay(true);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
catch (error) {
|
|
305
|
+
console.error("Failed to connect wallet:", error);
|
|
306
|
+
}
|
|
294
307
|
};
|
|
308
|
+
// Auto-trigger payment after wallet connect
|
|
309
|
+
useEffect(() => {
|
|
310
|
+
if (shouldAutoPay && connectedAddress && selectedSrcToken && hasEnoughBalance && !isLoadingAnyspendQuote) {
|
|
311
|
+
setShouldAutoPay(false);
|
|
312
|
+
handleWalletPay();
|
|
313
|
+
}
|
|
314
|
+
}, [shouldAutoPay, connectedAddress, selectedSrcToken, hasEnoughBalance, isLoadingAnyspendQuote, handleWalletPay]);
|
|
295
315
|
const isLoading = isLoadingAnyspendQuote;
|
|
296
316
|
const isPending = isCreatingSwapOrder || isSendingDeposit || isWaitingForExecution;
|
|
297
|
-
const canPay =
|
|
317
|
+
const canPay = connectedAddress && selectedSrcToken && hasEnoughBalance && !isLoading && !isPending;
|
|
298
318
|
const chainName = ALL_CHAINS[selectedSrcChainId]?.name || "the specified chain";
|
|
299
319
|
const chainLogoUrl = ALL_CHAINS[selectedSrcChainId]?.logoUrl;
|
|
300
320
|
// Collapse QR on mobile when a wallet connector is available
|
|
@@ -313,7 +333,7 @@ export function CryptoPayPanel({ recipientAddress, destinationTokenAddress, dest
|
|
|
313
333
|
name: token.name,
|
|
314
334
|
symbol: token.symbol,
|
|
315
335
|
});
|
|
316
|
-
}, supportedWalletVMs: ["evm", "svm"], token: undefined, trigger: _jsxs("button", { className: cn("flex w-full items-center justify-between rounded-xl border border-gray-200 bg-white px-4 py-3 transition-colors hover:border-gray-300 dark:border-neutral-700 dark:bg-neutral-800 dark:hover:border-neutral-600", classes?.tokenSelector), children: [selectedSrcToken ? (_jsxs("div", { className: "flex items-center gap-3", children: [_jsx(ChainTokenIcon, { chainUrl: ALL_CHAINS[selectedSrcToken.chainId]?.logoUrl || "", tokenUrl: selectedSrcToken.metadata?.logoURI, className: "h-8 w-8" }), _jsxs("div", { className: "text-left", children: [_jsx("p", { className: "text-sm font-medium text-gray-900 dark:text-gray-100", children: selectedSrcToken.symbol }), _jsxs("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: ["Balance: ", balance.formatted] })] })] })) : (_jsx("span", { className: "text-sm text-gray-400", children: "Select token" })), _jsx(ChevronsUpDown, { className: "h-4 w-4 text-gray-400" })] }) })] }), _jsx(motion.div, { initial: { opacity: 0, y: 6 }, animate: { opacity: 1, y: 0 }, transition: { duration: 0.25, ease: "easeOut" }, className: cn("rounded-xl border border-gray-200 bg-gray-50 px-4 py-3 dark:border-neutral-700 dark:bg-neutral-800/50", classes?.quoteDisplay), children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: "text-sm text-gray-500 dark:text-gray-400", children: "You pay" }), _jsx(AnimatePresence, { mode: "wait", children: isLoadingAnyspendQuote ? (_jsx(motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, transition: { duration: 0.15 }, children: _jsx(TextShimmer, { duration: 1, className: "text-sm", children: "Fetching quote..." }) }, "quote-loading")) : (_jsxs(motion.span, { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, transition: { duration: 0.15 }, className: "text-sm font-medium text-gray-900 dark:text-gray-100", children: [srcAmountFormatted, " ", selectedSrcToken?.symbol || ""] }, "quote-amount")) })] }) }), _jsx(AnimatePresence, { children:
|
|
336
|
+
}, supportedWalletVMs: ["evm", "svm"], token: undefined, trigger: _jsxs("button", { className: cn("flex w-full items-center justify-between rounded-xl border border-gray-200 bg-white px-4 py-3 transition-colors hover:border-gray-300 dark:border-neutral-700 dark:bg-neutral-800 dark:hover:border-neutral-600", classes?.tokenSelector), children: [selectedSrcToken ? (_jsxs("div", { className: "flex items-center gap-3", children: [_jsx(ChainTokenIcon, { chainUrl: ALL_CHAINS[selectedSrcToken.chainId]?.logoUrl || "", tokenUrl: selectedSrcToken.metadata?.logoURI, className: "h-8 w-8" }), _jsxs("div", { className: "text-left", children: [_jsx("p", { className: "text-sm font-medium text-gray-900 dark:text-gray-100", children: selectedSrcToken.symbol }), _jsxs("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: ["Balance: ", balance.formatted] })] })] })) : (_jsx("span", { className: "text-sm text-gray-400", children: "Select token" })), _jsx(ChevronsUpDown, { className: "h-4 w-4 text-gray-400" })] }) })] }), _jsx(motion.div, { initial: { opacity: 0, y: 6 }, animate: { opacity: 1, y: 0 }, transition: { duration: 0.25, ease: "easeOut" }, className: cn("rounded-xl border border-gray-200 bg-gray-50 px-4 py-3 dark:border-neutral-700 dark:bg-neutral-800/50", classes?.quoteDisplay), children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: "text-sm text-gray-500 dark:text-gray-400", children: "You pay" }), _jsx(AnimatePresence, { mode: "wait", children: isLoadingAnyspendQuote ? (_jsx(motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, transition: { duration: 0.15 }, children: _jsx(TextShimmer, { duration: 1, className: "text-sm", children: "Fetching quote..." }) }, "quote-loading")) : (_jsxs(motion.span, { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, transition: { duration: 0.15 }, className: "text-sm font-medium text-gray-900 dark:text-gray-100", children: [srcAmountFormatted, " ", selectedSrcToken?.symbol || ""] }, "quote-amount")) })] }) }), _jsx(AnimatePresence, { children: connectedAddress && selectedSrcToken && !hasEnoughBalance && !isLoading && (_jsxs(motion.p, { initial: { opacity: 0, height: 0 }, animate: { opacity: 1, height: "auto" }, exit: { opacity: 0, height: 0 }, transition: { duration: 0.2, ease: "easeOut" }, className: "text-center text-sm text-red-500", children: ["Insufficient ", selectedSrcToken.symbol, " balance"] }, "balance-warning")) }), !connectedAddress ? (_jsx(ShinyButton, { accentColor: themeColor || "hsl(var(--as-brand))", onClick: handleConnectWallet, className: cn("w-full", classes?.payButton), textClassName: "text-white", children: "Connect Wallet to Pay" })) : (_jsx(ShinyButton, { accentColor: themeColor || "hsl(var(--as-brand))", onClick: handleWalletPay, disabled: !canPay, className: cn("w-full", classes?.payButton), textClassName: cn(!canPay ? "text-as-secondary" : "text-white"), children: isPending ? (_jsxs("span", { className: "flex items-center justify-center gap-2", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), isCreatingSwapOrder
|
|
317
337
|
? "Creating order..."
|
|
318
338
|
: isSendingDeposit
|
|
319
339
|
? "Confirm in wallet..."
|
|
@@ -5,6 +5,14 @@ export declare enum CryptoPaymentMethodType {
|
|
|
5
5
|
GLOBAL_WALLET = "global_wallet",
|
|
6
6
|
TRANSFER_CRYPTO = "transfer_crypto"
|
|
7
7
|
}
|
|
8
|
+
export declare const recommendWallets: (import("thirdweb/wallets").Wallet<"io.metamask"> | import("thirdweb/wallets").Wallet<"com.coinbase.wallet"> | import("thirdweb/wallets").Wallet<"me.rainbow"> | import("thirdweb/wallets").Wallet<"io.rabby">)[];
|
|
9
|
+
export declare const connectModalConfig: {
|
|
10
|
+
client: import("thirdweb").ThirdwebClient;
|
|
11
|
+
setActive: false;
|
|
12
|
+
size: "compact";
|
|
13
|
+
showThirdwebBranding: boolean;
|
|
14
|
+
wallets: (import("thirdweb/wallets").Wallet<"io.metamask"> | import("thirdweb/wallets").Wallet<"com.coinbase.wallet"> | import("thirdweb/wallets").Wallet<"me.rainbow"> | import("thirdweb/wallets").Wallet<"io.rabby">)[];
|
|
15
|
+
};
|
|
8
16
|
interface CryptoPaymentMethodProps {
|
|
9
17
|
selectedPaymentMethod: CryptoPaymentMethodType;
|
|
10
18
|
setSelectedPaymentMethod: (method: CryptoPaymentMethodType) => void;
|
|
@@ -15,12 +15,19 @@ export var CryptoPaymentMethodType;
|
|
|
15
15
|
CryptoPaymentMethodType["GLOBAL_WALLET"] = "global_wallet";
|
|
16
16
|
CryptoPaymentMethodType["TRANSFER_CRYPTO"] = "transfer_crypto";
|
|
17
17
|
})(CryptoPaymentMethodType || (CryptoPaymentMethodType = {}));
|
|
18
|
-
const recommendWallets = [
|
|
18
|
+
export const recommendWallets = [
|
|
19
19
|
createWallet("io.metamask"),
|
|
20
20
|
createWallet("com.coinbase.wallet"),
|
|
21
21
|
createWallet("me.rainbow"),
|
|
22
22
|
createWallet("io.rabby"),
|
|
23
23
|
];
|
|
24
|
+
export const connectModalConfig = {
|
|
25
|
+
client,
|
|
26
|
+
setActive: false,
|
|
27
|
+
size: "compact",
|
|
28
|
+
showThirdwebBranding: false,
|
|
29
|
+
wallets: recommendWallets,
|
|
30
|
+
};
|
|
24
31
|
export function CryptoPaymentMethod({ selectedPaymentMethod, setSelectedPaymentMethod, isCreatingOrder, onBack, onSelectPaymentMethod, classes, }) {
|
|
25
32
|
const { connectedEOAWallet, connectedSmartWallet } = useAccountWallet();
|
|
26
33
|
const { disconnect } = useDisconnect();
|
|
@@ -40,13 +47,7 @@ export function CryptoPaymentMethod({ selectedPaymentMethod, setSelectedPaymentM
|
|
|
40
47
|
if (connectedEOAWallet) {
|
|
41
48
|
disconnect(connectedEOAWallet);
|
|
42
49
|
}
|
|
43
|
-
const wallet = await openConnectModal(
|
|
44
|
-
client,
|
|
45
|
-
setActive: false,
|
|
46
|
-
size: "compact",
|
|
47
|
-
showThirdwebBranding: false,
|
|
48
|
-
wallets: recommendWallets,
|
|
49
|
-
});
|
|
50
|
+
const wallet = await openConnectModal(connectModalConfig);
|
|
50
51
|
if (wallet) {
|
|
51
52
|
setSelectedPaymentMethod(CryptoPaymentMethodType.CONNECT_WALLET);
|
|
52
53
|
onSelectPaymentMethod(CryptoPaymentMethodType.CONNECT_WALLET);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Button, toast, useProfile } from "../../../../global-account/react/index.js";
|
|
3
|
+
import { validateImageUrl } from "../../../../global-account/react/utils/profileDisplay.js";
|
|
3
4
|
import { truncateAddress } from "../../../../shared/utils/truncateAddress.js";
|
|
4
5
|
import { Copy, Loader2, UnlinkIcon } from "lucide-react";
|
|
5
6
|
// Helper function to check if a string is a wallet address and format it
|
|
@@ -30,7 +31,7 @@ const LinkedAccountItem = ({ profile, profileToUnlink, unlinkingAccountId, isUnl
|
|
|
30
31
|
toast.error("Failed to copy address");
|
|
31
32
|
}
|
|
32
33
|
};
|
|
33
|
-
const displayImageUrl = profileData?.avatar || profile.imageUrl;
|
|
34
|
+
const displayImageUrl = validateImageUrl(profileData?.avatar) || validateImageUrl(profile.imageUrl);
|
|
34
35
|
const displayName = profileData?.name || displayTitle;
|
|
35
36
|
return (_jsxs("div", { className: "linked-account-item hover:bg-b3-line group flex cursor-pointer items-center justify-between rounded-xl p-4 transition-colors", children: [_jsxs("div", { className: "linked-account-info flex items-center gap-3", children: [displayImageUrl ? (_jsx("img", { src: displayImageUrl, alt: profile.title, className: "linked-account-avatar linked-account-avatar-image size-10 rounded-full" })) : (_jsx("div", { className: "linked-account-avatar linked-account-avatar-placeholder bg-b3-primary-wash flex h-10 w-10 items-center justify-center rounded-full", children: _jsx("span", { className: "linked-account-initial text-b3-grey font-neue-montreal-semibold text-sm uppercase", children: profile.initial }) })), _jsxs("div", { className: "linked-account-details", children: [_jsxs("div", { className: "linked-account-title-row flex items-center gap-2", children: [_jsxs("div", { className: "flex items-center gap-1", children: [_jsx("span", { className: `linked-account-title text-b3-grey font-neue-montreal-semibold ${isAddress
|
|
36
37
|
? "font-mono text-sm" // Use monospace font for addresses
|
|
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import { ens_normalize } from "@adraffy/ens-normalize";
|
|
3
3
|
import app from "../../../../global-account/app.js";
|
|
4
4
|
import { toast, useAuthentication, useB3Config, useModalStore, useProfile } from "../../../../global-account/react/index.js";
|
|
5
|
+
import { validateImageUrl } from "../../../../global-account/react/utils/profileDisplay.js";
|
|
5
6
|
import { formatUsername } from "../../../../shared/utils/index.js";
|
|
6
7
|
import { Check, Loader2, Pencil, X } from "lucide-react";
|
|
7
8
|
import { useEffect, useRef, useState } from "react";
|
|
@@ -25,8 +26,7 @@ const SettingsProfileCard = () => {
|
|
|
25
26
|
const [editedUsername, setEditedUsername] = useState("");
|
|
26
27
|
const [isSaving, setIsSaving] = useState(false);
|
|
27
28
|
const inputRef = useRef(null);
|
|
28
|
-
|
|
29
|
-
const avatarSrc = user?.avatar || profile?.avatar;
|
|
29
|
+
const avatarSrc = validateImageUrl(user?.avatar) || validateImageUrl(profile?.avatar);
|
|
30
30
|
// Get current username - prioritize user.username, fallback to profile data
|
|
31
31
|
const currentUsername = user?.username || profile?.displayName || formatUsername(profile?.name || "");
|
|
32
32
|
// Focus input when entering edit mode
|
|
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import { ALL_CHAINS, getExplorerTxUrl } from "../../../../anyspend/index.js";
|
|
3
3
|
import { ChainTokenIcon } from "../../../../anyspend/react/components/common/ChainTokenIcon.js";
|
|
4
4
|
import { toast, useAccountWallet, useAnalytics, useModalStore, useProfile, useSimBalance, useUnifiedChainSwitchAndExecute, } from "../../../../global-account/react/index.js";
|
|
5
|
+
import { validateImageUrl } from "../../../../global-account/react/utils/profileDisplay.js";
|
|
5
6
|
import { formatDisplayNumber, formatTokenAmount } from "../../../../shared/utils/number.js";
|
|
6
7
|
import invariant from "invariant";
|
|
7
8
|
import { CircleHelp, Clock, Loader2, Send as SendIcon, Wallet } from "lucide-react";
|
|
@@ -14,7 +15,8 @@ import { Button } from "../ui/button.js";
|
|
|
14
15
|
// Component for displaying a recent address with profile data
|
|
15
16
|
function RecentAddressItem({ address, onClick }) {
|
|
16
17
|
const { data: profileData } = useProfile({ address });
|
|
17
|
-
|
|
18
|
+
const validatedAvatar = validateImageUrl(profileData?.avatar);
|
|
19
|
+
return (_jsxs("button", { onClick: onClick, className: "flex items-center gap-2 rounded-xl px-3 py-2 transition-colors hover:bg-[#fafafa]", children: [validatedAvatar ? (_jsx("img", { src: validatedAvatar, alt: profileData?.name || address, className: "h-10 w-10 rounded-full" })) : (_jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-full border border-[#e4e4e7] bg-[#f4f4f5]", children: _jsx(Wallet, { className: "h-5 w-5 text-[#a0a0ab]" }) })), _jsx("div", { className: "flex flex-col items-start", children: _jsxs("span", { className: "font-neue-montreal-medium text-base tracking-[-0.32px] text-[#70707b]", children: [address.slice(0, 6), "...", address.slice(-4), profileData?.name && ` (${profileData.name})`] }) })] }));
|
|
18
20
|
}
|
|
19
21
|
export function Send({ recipientAddress: initialRecipient, onSuccess }) {
|
|
20
22
|
const { address } = useAccountWallet();
|
|
@@ -39,6 +41,7 @@ export function Send({ recipientAddress: initialRecipient, onSuccess }) {
|
|
|
39
41
|
const { data: validatedProfileData } = useProfile({
|
|
40
42
|
address: showValidatedResult && recipientAddress && isAddress(recipientAddress) ? recipientAddress : undefined,
|
|
41
43
|
});
|
|
44
|
+
const validatedRecipientAvatar = validateImageUrl(validatedProfileData?.avatar);
|
|
42
45
|
// Address validation
|
|
43
46
|
const handleRecipientAddressChange = (value) => {
|
|
44
47
|
setRecipientAddress(value);
|
|
@@ -179,7 +182,7 @@ export function Send({ recipientAddress: initialRecipient, onSuccess }) {
|
|
|
179
182
|
return "Send";
|
|
180
183
|
}
|
|
181
184
|
};
|
|
182
|
-
return (_jsxs("div", { className: "dark:bg-b3-background flex h-[600px] w-full flex-col bg-white", children: [_jsx(ModalHeader, { handleBack: handleBack, title: getStepTitle() }), _jsxs("div", { className: "flex-1 overflow-y-auto", children: [step === "recipient" && (_jsxs("div", { className: "flex flex-col gap-6 p-5", children: [_jsxs("div", { className: "dark:border-b3-line dark:bg-b3-background flex h-12 w-full items-stretch overflow-hidden rounded-lg border border-[#d1d1d6] bg-white", children: [_jsx("div", { className: "flex w-12 items-center justify-center bg-transparent px-3 py-2", children: _jsx("span", { className: "font-neue-montreal-medium text-base text-[#3f3f46] dark:text-white", children: "To" }) }), _jsxs("div", { className: "flex flex-1 items-center border-l border-[#d1d1d6] px-3 py-2", children: [_jsx("input", { type: "text", value: recipientAddress, onChange: e => handleRecipientAddressChange(e.target.value), placeholder: "ENS or Address", className: "font-neue-montreal-medium dark:bg-b3-background flex-1 text-base text-[#70707b] outline-none placeholder:text-[#70707b] dark:text-white dark:placeholder:text-white" }), _jsx("button", { onClick: handlePaste, className: "font-inter ml-2 rounded-md border border-[#e4e4e7] bg-[#fafafa] px-2.5 py-0.5 text-sm font-medium text-[#3f3f46] transition-colors hover:bg-[#f4f4f5]", children: "Paste" })] })] }), addressError && _jsx("p", { className: "font-neue-montreal-medium -mt-4 text-xs text-red-500", children: addressError }), showValidatedResult && recipientAddress && isAddress(recipientAddress) && (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx("div", { className: "flex items-center gap-1", children: _jsx("span", { className: "font-sf-pro-text text-sm font-semibold leading-[1.3] tracking-[-0.41px] text-[#0b57c2]", children: "Result" }) }), _jsxs("button", { onClick: handleSelectValidatedAddress, className: "dark:bg-b3-background dark:border-b3-line flex items-center gap-2 rounded-xl bg-[#f4f4f5] px-3 py-2 transition-colors hover:bg-[#e4e4e7]", children: [
|
|
185
|
+
return (_jsxs("div", { className: "dark:bg-b3-background flex h-[600px] w-full flex-col bg-white", children: [_jsx(ModalHeader, { handleBack: handleBack, title: getStepTitle() }), _jsxs("div", { className: "flex-1 overflow-y-auto", children: [step === "recipient" && (_jsxs("div", { className: "flex flex-col gap-6 p-5", children: [_jsxs("div", { className: "dark:border-b3-line dark:bg-b3-background flex h-12 w-full items-stretch overflow-hidden rounded-lg border border-[#d1d1d6] bg-white", children: [_jsx("div", { className: "flex w-12 items-center justify-center bg-transparent px-3 py-2", children: _jsx("span", { className: "font-neue-montreal-medium text-base text-[#3f3f46] dark:text-white", children: "To" }) }), _jsxs("div", { className: "flex flex-1 items-center border-l border-[#d1d1d6] px-3 py-2", children: [_jsx("input", { type: "text", value: recipientAddress, onChange: e => handleRecipientAddressChange(e.target.value), placeholder: "ENS or Address", className: "font-neue-montreal-medium dark:bg-b3-background flex-1 text-base text-[#70707b] outline-none placeholder:text-[#70707b] dark:text-white dark:placeholder:text-white" }), _jsx("button", { onClick: handlePaste, className: "font-inter ml-2 rounded-md border border-[#e4e4e7] bg-[#fafafa] px-2.5 py-0.5 text-sm font-medium text-[#3f3f46] transition-colors hover:bg-[#f4f4f5]", children: "Paste" })] })] }), addressError && _jsx("p", { className: "font-neue-montreal-medium -mt-4 text-xs text-red-500", children: addressError }), showValidatedResult && recipientAddress && isAddress(recipientAddress) && (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx("div", { className: "flex items-center gap-1", children: _jsx("span", { className: "font-sf-pro-text text-sm font-semibold leading-[1.3] tracking-[-0.41px] text-[#0b57c2]", children: "Result" }) }), _jsxs("button", { onClick: handleSelectValidatedAddress, className: "dark:bg-b3-background dark:border-b3-line flex items-center gap-2 rounded-xl bg-[#f4f4f5] px-3 py-2 transition-colors hover:bg-[#e4e4e7]", children: [validatedRecipientAvatar ? (_jsx("img", { src: validatedRecipientAvatar, alt: validatedProfileData?.name || recipientAddress, className: "h-10 w-10 rounded-full" })) : (_jsx("div", { className: "dark:border-b3-line dark:bg-b3-background flex h-10 w-10 items-center justify-center rounded-full border border-[#e4e4e7] bg-[#f4f4f5]", children: _jsx(Wallet, { className: "h-5 w-5 text-[#a0a0ab] dark:text-white" }) })), _jsxs("span", { className: "font-neue-montreal-medium text-base tracking-[-0.32px] text-[#70707b] dark:text-white", children: [recipientAddress.slice(0, 6), "...", recipientAddress.slice(-4), validatedProfileData?.name && ` (${validatedProfileData.name})`] })] })] })), recentAddresses.length > 0 && (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("div", { className: "flex items-center gap-1", children: [_jsx(Clock, { className: "h-3.5 w-3.5 text-[#3f3f46]" }), _jsx("span", { className: "font-sf-pro-text text-sm font-semibold leading-[1.3] tracking-[-0.41px] text-[#3f3f46]", children: "Recents" })] }), _jsx("div", { className: "flex flex-col", children: recentAddresses.map((recent, index) => (_jsx(RecentAddressItem, { address: recent.address, onClick: () => {
|
|
183
186
|
// Just fill the input and show validation - don't auto-proceed
|
|
184
187
|
handleRecipientAddressChange(recent.address);
|
|
185
188
|
} }, index))) })] }))] })), step === "token" && (_jsx("div", { className: "flex flex-col p-5", children: isLoadingBalance ? (_jsx("div", { className: "space-y-4", children: _jsx("div", { className: "space-y-1", children: [...Array(3)].map((_, index) => (_jsxs("div", { className: "flex items-center justify-between rounded-xl p-3", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "bg-b3-line h-10 w-10 animate-pulse rounded-full" }), _jsxs("div", { children: [_jsx("div", { className: "bg-b3-line mb-1 h-4 w-16 animate-pulse rounded" }), _jsx("div", { className: "bg-b3-line h-3 w-24 animate-pulse rounded" })] })] }), _jsxs("div", { className: "text-right", children: [_jsx("div", { className: "bg-b3-line mb-1 h-4 w-20 animate-pulse rounded" }), _jsx("div", { className: "bg-b3-line h-3 w-16 animate-pulse rounded" })] })] }, index))) }) })) : simBalance?.balances && simBalance.balances.length > 0 ? (_jsx("div", { className: "space-y-4", children: _jsx("div", { className: "space-y-1", children: simBalance.balances.map(token => (_jsxs("div", { className: "hover:bg-b3-line/60 dark:hover:bg-b3-primary-wash/40 group flex cursor-pointer items-center justify-between rounded-xl p-3 transition-all duration-200", onClick: () => {
|
|
@@ -4,6 +4,7 @@ import { cn } from "../../../../shared/utils/index.js";
|
|
|
4
4
|
import { Search, X } from "lucide-react";
|
|
5
5
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
6
6
|
import { fetchProfile as fetchProfileApi } from "../../utils/profileApi.js";
|
|
7
|
+
import { validateImageUrl } from "../../utils/profileDisplay.js";
|
|
7
8
|
import { IPFSMediaRenderer } from "../IPFSMediaRenderer/IPFSMediaRenderer.js";
|
|
8
9
|
import { Input } from "../ui/input.js";
|
|
9
10
|
/**
|
|
@@ -156,5 +157,5 @@ export function SingleUserSearchSelector({ onSelectUser, profileTypeFilter, plac
|
|
|
156
157
|
const getProfileTypeBadges = (profiles) => {
|
|
157
158
|
return profiles.map(p => p.type);
|
|
158
159
|
};
|
|
159
|
-
return (_jsxs("div", { className: cn("single-user-search-selector b3-root relative w-full", className), ref: dropdownRef, children: [_jsxs("div", { className: "single-user-search-input-wrapper relative flex items-center", children: [_jsx(Search, { className: "single-user-search-icon pointer-events-none absolute text-gray-400", style: { left: "12px", width: "16px", height: "16px" } }), _jsx(Input, { ref: inputRef, type: "text", value: searchQuery, onChange: e => setSearchQuery(e.target.value), placeholder: placeholder, className: cn("single-user-search-input w-full border-gray-300 focus:border-blue-500 focus:ring-blue-500"), style: { paddingLeft: "44px", paddingRight: "44px" } }), showClearButton && searchQuery && (_jsx("button", { onClick: handleClear, className: "single-user-search-clear-button absolute text-gray-400 transition-colors hover:text-gray-600", style: { right: "12px" }, type: "button", children: _jsx(X, { style: { width: "16px", height: "16px" } }) }))] }), isSearching && _jsx("div", { className: "single-user-search-loading mt-2 text-sm text-gray-500", children: "Searching..." }), error && !isSearching && _jsx("div", { className: "single-user-search-error mt-2 text-sm text-red-500", children: error }), showDropdown && searchResult && !isSearching && (_jsx("div", { className: "single-user-search-dropdown absolute z-50 mt-2 w-full rounded-lg border border-gray-200 bg-white shadow-lg", children: _jsx("button", { onClick: () => handleSelectUser(searchResult), className: "single-user-search-result-button w-full px-4 py-3 text-left transition-colors hover:bg-gray-50", type: "button", children: _jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: "single-user-search-result-avatar h-11 w-11 shrink-0", children: _jsx(IPFSMediaRenderer, { src: searchResult.avatar, alt: getDisplayName(searchResult), className: "h-full w-full rounded-full object-cover" }) }), _jsxs("div", { className: "single-user-search-result-info min-w-0 flex-1 pt-0.5", children: [_jsx("div", { className: "single-user-search-result-name text-base font-semibold text-gray-900", children: getDisplayName(searchResult) }), searchResult.address && (_jsxs("div", { className: "single-user-search-result-address mt-1 font-mono text-xs text-gray-500", children: [searchResult.address.slice(0, 6), "...", searchResult.address.slice(-4)] })), searchResult.bio && (_jsx("div", { className: "single-user-search-result-bio mt-1.5 line-clamp-2 text-sm text-gray-600", children: searchResult.bio })), showBadges && (_jsx("div", { className: "single-user-search-result-badges mt-2 flex flex-wrap gap-1.5", children: getProfileTypeBadges(searchResult.profiles).map((type, index) => (_jsx("span", { className: "single-user-search-result-badge inline-flex items-center rounded-full bg-blue-100 px-2.5 py-0.5 text-xs font-medium text-blue-800", children: type }, `${type}-${index}`))) }))] })] }) }) }))] }));
|
|
160
|
+
return (_jsxs("div", { className: cn("single-user-search-selector b3-root relative w-full", className), ref: dropdownRef, children: [_jsxs("div", { className: "single-user-search-input-wrapper relative flex items-center", children: [_jsx(Search, { className: "single-user-search-icon pointer-events-none absolute text-gray-400", style: { left: "12px", width: "16px", height: "16px" } }), _jsx(Input, { ref: inputRef, type: "text", value: searchQuery, onChange: e => setSearchQuery(e.target.value), placeholder: placeholder, className: cn("single-user-search-input w-full border-gray-300 focus:border-blue-500 focus:ring-blue-500"), style: { paddingLeft: "44px", paddingRight: "44px" } }), showClearButton && searchQuery && (_jsx("button", { onClick: handleClear, className: "single-user-search-clear-button absolute text-gray-400 transition-colors hover:text-gray-600", style: { right: "12px" }, type: "button", children: _jsx(X, { style: { width: "16px", height: "16px" } }) }))] }), isSearching && _jsx("div", { className: "single-user-search-loading mt-2 text-sm text-gray-500", children: "Searching..." }), error && !isSearching && _jsx("div", { className: "single-user-search-error mt-2 text-sm text-red-500", children: error }), showDropdown && searchResult && !isSearching && (_jsx("div", { className: "single-user-search-dropdown absolute z-50 mt-2 w-full rounded-lg border border-gray-200 bg-white shadow-lg", children: _jsx("button", { onClick: () => handleSelectUser(searchResult), className: "single-user-search-result-button w-full px-4 py-3 text-left transition-colors hover:bg-gray-50", type: "button", children: _jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: "single-user-search-result-avatar h-11 w-11 shrink-0", children: _jsx(IPFSMediaRenderer, { src: validateImageUrl(searchResult.avatar) ?? undefined, alt: getDisplayName(searchResult), className: "h-full w-full rounded-full object-cover" }) }), _jsxs("div", { className: "single-user-search-result-info min-w-0 flex-1 pt-0.5", children: [_jsx("div", { className: "single-user-search-result-name text-base font-semibold text-gray-900", children: getDisplayName(searchResult) }), searchResult.address && (_jsxs("div", { className: "single-user-search-result-address mt-1 font-mono text-xs text-gray-500", children: [searchResult.address.slice(0, 6), "...", searchResult.address.slice(-4)] })), searchResult.bio && (_jsx("div", { className: "single-user-search-result-bio mt-1.5 line-clamp-2 text-sm text-gray-600", children: searchResult.bio })), showBadges && (_jsx("div", { className: "single-user-search-result-badges mt-2 flex flex-wrap gap-1.5", children: getProfileTypeBadges(searchResult.profiles).map((type, index) => (_jsx("span", { className: "single-user-search-result-badge inline-flex items-center rounded-full bg-blue-100 px-2.5 py-0.5 text-xs font-medium text-blue-800", children: type }, `${type}-${index}`))) }))] })] }) }) }))] }));
|
|
160
161
|
}
|
|
@@ -5,6 +5,14 @@ export declare enum CryptoPaymentMethodType {
|
|
|
5
5
|
GLOBAL_WALLET = "global_wallet",
|
|
6
6
|
TRANSFER_CRYPTO = "transfer_crypto"
|
|
7
7
|
}
|
|
8
|
+
export declare const recommendWallets: (import("thirdweb/wallets").Wallet<"io.metamask"> | import("thirdweb/wallets").Wallet<"com.coinbase.wallet"> | import("thirdweb/wallets").Wallet<"me.rainbow"> | import("thirdweb/wallets").Wallet<"io.rabby">)[];
|
|
9
|
+
export declare const connectModalConfig: {
|
|
10
|
+
client: import("thirdweb").ThirdwebClient;
|
|
11
|
+
setActive: false;
|
|
12
|
+
size: "compact";
|
|
13
|
+
showThirdwebBranding: boolean;
|
|
14
|
+
wallets: (import("thirdweb/wallets").Wallet<"io.metamask"> | import("thirdweb/wallets").Wallet<"com.coinbase.wallet"> | import("thirdweb/wallets").Wallet<"me.rainbow"> | import("thirdweb/wallets").Wallet<"io.rabby">)[];
|
|
15
|
+
};
|
|
8
16
|
interface CryptoPaymentMethodProps {
|
|
9
17
|
selectedPaymentMethod: CryptoPaymentMethodType;
|
|
10
18
|
setSelectedPaymentMethod: (method: CryptoPaymentMethodType) => void;
|
package/package.json
CHANGED
|
@@ -377,7 +377,15 @@ export function AnySpendCheckout({
|
|
|
377
377
|
|
|
378
378
|
return (
|
|
379
379
|
<AnySpendFingerprintWrapper fingerprint={fingerprint}>
|
|
380
|
-
<AnySpendCustomizationProvider
|
|
380
|
+
<AnySpendCustomizationProvider
|
|
381
|
+
slots={slots}
|
|
382
|
+
content={{
|
|
383
|
+
successTitle: "Payment Successful",
|
|
384
|
+
successDescription: "Your payment has been processed successfully.",
|
|
385
|
+
...content,
|
|
386
|
+
}}
|
|
387
|
+
theme={theme}
|
|
388
|
+
>
|
|
381
389
|
<CheckoutLayout
|
|
382
390
|
mode={mode}
|
|
383
391
|
paymentPanel={
|
|
@@ -11,15 +11,12 @@ import { getPaymentUrl } from "@b3dotfun/sdk/anyspend/utils/chain";
|
|
|
11
11
|
import { isNativeToken } from "@b3dotfun/sdk/anyspend/utils/token";
|
|
12
12
|
import {
|
|
13
13
|
useAccountWallet,
|
|
14
|
-
useB3Config,
|
|
15
14
|
useIsMobile,
|
|
16
|
-
useModalStore,
|
|
17
15
|
useSimTokenBalance,
|
|
18
16
|
useTokenData,
|
|
19
17
|
useUnifiedChainSwitchAndExecute,
|
|
20
18
|
} from "@b3dotfun/sdk/global-account/react";
|
|
21
19
|
import { ShinyButton, TextShimmer } from "@b3dotfun/sdk/global-account/react";
|
|
22
|
-
import { thirdwebB3Chain } from "@b3dotfun/sdk/shared/constants/chains/b3Chain";
|
|
23
20
|
import { formatTokenAmount } from "@b3dotfun/sdk/shared/utils/number";
|
|
24
21
|
import { cn } from "@b3dotfun/sdk/shared/utils/cn";
|
|
25
22
|
import { Check, ChevronDown, ChevronsUpDown, Copy, Loader2, QrCode } from "lucide-react";
|
|
@@ -27,8 +24,10 @@ import { QRCodeSVG } from "qrcode.react";
|
|
|
27
24
|
import { encodeFunctionData, erc20Abi } from "viem";
|
|
28
25
|
import { AnimatePresence, motion } from "motion/react";
|
|
29
26
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
27
|
+
import { useConnectModal } from "thirdweb/react";
|
|
30
28
|
import { TokenSelector } from "@relayprotocol/relay-kit-ui";
|
|
31
29
|
import { ChainTokenIcon } from "../common/ChainTokenIcon";
|
|
30
|
+
import { connectModalConfig } from "../common/CryptoPaymentMethod";
|
|
32
31
|
import type { AnySpendCheckoutClasses } from "./AnySpendCheckout";
|
|
33
32
|
|
|
34
33
|
interface CryptoPayPanelProps {
|
|
@@ -76,11 +75,10 @@ export function CryptoPayPanel({
|
|
|
76
75
|
const [selectedSrcToken, setSelectedSrcToken] = useState<components["schemas"]["Token"] | null>(null);
|
|
77
76
|
const [copied, setCopied] = useState(false);
|
|
78
77
|
|
|
79
|
-
const { address: walletAddress } = useAccountWallet();
|
|
80
|
-
const
|
|
81
|
-
const
|
|
82
|
-
const
|
|
83
|
-
const setB3ModalContentType = useModalStore(state => state.setB3ModalContentType);
|
|
78
|
+
const { address: walletAddress, connectedEOAWallet } = useAccountWallet();
|
|
79
|
+
const connectedAddress = walletAddress || connectedEOAWallet?.getAccount()?.address;
|
|
80
|
+
const effectiveAddress = connectedAddress || senderAddress;
|
|
81
|
+
const { connect: openConnectModal } = useConnectModal();
|
|
84
82
|
|
|
85
83
|
const { data: dstTokenData } = useTokenData(destinationTokenChainId, destinationTokenAddress);
|
|
86
84
|
|
|
@@ -235,9 +233,8 @@ export function CryptoPayPanel({
|
|
|
235
233
|
/* ------------------------------------------------------------------ */
|
|
236
234
|
const [walletOrderId, setWalletOrderId] = useState<string | undefined>();
|
|
237
235
|
const [isSendingDeposit, setIsSendingDeposit] = useState(false);
|
|
238
|
-
const [depositRejected, setDepositRejected] = useState(false);
|
|
239
236
|
const depositSentRef = useRef(false);
|
|
240
|
-
const {
|
|
237
|
+
const { switchChainAndExecuteWithEOA } = useUnifiedChainSwitchAndExecute();
|
|
241
238
|
|
|
242
239
|
const { createOrder: createSwapOrder, isCreatingOrder: isCreatingSwapOrder } = useAnyspendCreateOrder({
|
|
243
240
|
onSuccess: data => {
|
|
@@ -254,7 +251,7 @@ export function CryptoPayPanel({
|
|
|
254
251
|
|
|
255
252
|
// Auto-send deposit tx once swap order is ready
|
|
256
253
|
useEffect(() => {
|
|
257
|
-
if (!walletOat?.data?.order || depositSentRef.current
|
|
254
|
+
if (!walletOat?.data?.order || depositSentRef.current) return;
|
|
258
255
|
const order = walletOat.data.order;
|
|
259
256
|
if (order.status !== "scanning_deposit_transaction") return;
|
|
260
257
|
if (walletOat.data.depositTxs?.length) return;
|
|
@@ -264,8 +261,9 @@ export function CryptoPayPanel({
|
|
|
264
261
|
try {
|
|
265
262
|
setIsSendingDeposit(true);
|
|
266
263
|
const amount = BigInt(order.srcAmount);
|
|
264
|
+
let txHash: string | undefined;
|
|
267
265
|
if (isNativeToken(order.srcTokenAddress)) {
|
|
268
|
-
await
|
|
266
|
+
txHash = await switchChainAndExecuteWithEOA(order.srcChain, {
|
|
269
267
|
to: order.globalAddress as `0x${string}`,
|
|
270
268
|
value: amount,
|
|
271
269
|
});
|
|
@@ -275,30 +273,33 @@ export function CryptoPayPanel({
|
|
|
275
273
|
functionName: "transfer",
|
|
276
274
|
args: [order.globalAddress as `0x${string}`, amount],
|
|
277
275
|
});
|
|
278
|
-
await
|
|
276
|
+
txHash = await switchChainAndExecuteWithEOA(order.srcChain, {
|
|
279
277
|
to: order.srcTokenAddress as `0x${string}`,
|
|
280
278
|
data,
|
|
281
279
|
value: BigInt(0),
|
|
282
280
|
});
|
|
283
281
|
}
|
|
282
|
+
if (!txHash) {
|
|
283
|
+
// User cancelled or tx failed — reset so they can retry
|
|
284
|
+
depositSentRef.current = false;
|
|
285
|
+
setWalletOrderId(undefined);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
284
288
|
// Deposit sent — notify parent to transition to order lifecycle tracking
|
|
285
289
|
if (walletOrderId) {
|
|
286
290
|
onOrderCreatedRef.current?.(walletOrderId);
|
|
287
291
|
}
|
|
288
292
|
} catch (error: any) {
|
|
289
293
|
depositSentRef.current = false;
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
if (isUserRejection) {
|
|
293
|
-
setDepositRejected(true);
|
|
294
|
-
}
|
|
294
|
+
// Reset order so user can retry
|
|
295
|
+
setWalletOrderId(undefined);
|
|
295
296
|
onErrorRef.current?.(error instanceof Error ? error : new Error(error?.message || "Transaction rejected"));
|
|
296
297
|
} finally {
|
|
297
298
|
setIsSendingDeposit(false);
|
|
298
299
|
}
|
|
299
300
|
};
|
|
300
301
|
sendDeposit();
|
|
301
|
-
}, [walletOat,
|
|
302
|
+
}, [walletOat, switchChainAndExecuteWithEOA, walletOrderId]);
|
|
302
303
|
|
|
303
304
|
useOnOrderSuccess({
|
|
304
305
|
orderData: walletOat,
|
|
@@ -309,8 +310,9 @@ export function CryptoPayPanel({
|
|
|
309
310
|
const isWaitingForExecution = !!walletOrderId && walletOat?.data?.order.status !== "executed";
|
|
310
311
|
|
|
311
312
|
const handleWalletPay = useCallback(() => {
|
|
312
|
-
if (!selectedSrcToken || !
|
|
313
|
+
if (!selectedSrcToken || !connectedAddress) return;
|
|
313
314
|
depositSentRef.current = false;
|
|
315
|
+
setWalletOrderId(undefined);
|
|
314
316
|
createSwapOrder({
|
|
315
317
|
recipientAddress,
|
|
316
318
|
orderType: "swap",
|
|
@@ -325,7 +327,7 @@ export function CryptoPayPanel({
|
|
|
325
327
|
});
|
|
326
328
|
}, [
|
|
327
329
|
selectedSrcToken,
|
|
328
|
-
|
|
330
|
+
connectedAddress,
|
|
329
331
|
effectiveAddress,
|
|
330
332
|
recipientAddress,
|
|
331
333
|
selectedSrcChainId,
|
|
@@ -360,14 +362,30 @@ export function CryptoPayPanel({
|
|
|
360
362
|
}
|
|
361
363
|
};
|
|
362
364
|
|
|
363
|
-
const
|
|
364
|
-
|
|
365
|
-
|
|
365
|
+
const [shouldAutoPay, setShouldAutoPay] = useState(false);
|
|
366
|
+
|
|
367
|
+
const handleConnectWallet = async () => {
|
|
368
|
+
try {
|
|
369
|
+
const wallet = await openConnectModal(connectModalConfig);
|
|
370
|
+
if (wallet) {
|
|
371
|
+
setShouldAutoPay(true);
|
|
372
|
+
}
|
|
373
|
+
} catch (error) {
|
|
374
|
+
console.error("Failed to connect wallet:", error);
|
|
375
|
+
}
|
|
366
376
|
};
|
|
367
377
|
|
|
378
|
+
// Auto-trigger payment after wallet connect
|
|
379
|
+
useEffect(() => {
|
|
380
|
+
if (shouldAutoPay && connectedAddress && selectedSrcToken && hasEnoughBalance && !isLoadingAnyspendQuote) {
|
|
381
|
+
setShouldAutoPay(false);
|
|
382
|
+
handleWalletPay();
|
|
383
|
+
}
|
|
384
|
+
}, [shouldAutoPay, connectedAddress, selectedSrcToken, hasEnoughBalance, isLoadingAnyspendQuote, handleWalletPay]);
|
|
385
|
+
|
|
368
386
|
const isLoading = isLoadingAnyspendQuote;
|
|
369
387
|
const isPending = isCreatingSwapOrder || isSendingDeposit || isWaitingForExecution;
|
|
370
|
-
const canPay =
|
|
388
|
+
const canPay = connectedAddress && selectedSrcToken && hasEnoughBalance && !isLoading && !isPending;
|
|
371
389
|
|
|
372
390
|
const chainName = ALL_CHAINS[selectedSrcChainId]?.name || "the specified chain";
|
|
373
391
|
const chainLogoUrl = ALL_CHAINS[selectedSrcChainId]?.logoUrl;
|
|
@@ -478,7 +496,7 @@ export function CryptoPayPanel({
|
|
|
478
496
|
|
|
479
497
|
{/* ---- Insufficient balance warning ---- */}
|
|
480
498
|
<AnimatePresence>
|
|
481
|
-
{
|
|
499
|
+
{connectedAddress && selectedSrcToken && !hasEnoughBalance && !isLoading && (
|
|
482
500
|
<motion.p
|
|
483
501
|
key="balance-warning"
|
|
484
502
|
initial={{ opacity: 0, height: 0 }}
|
|
@@ -493,7 +511,7 @@ export function CryptoPayPanel({
|
|
|
493
511
|
</AnimatePresence>
|
|
494
512
|
|
|
495
513
|
{/* ---- Wallet Pay Button ---- */}
|
|
496
|
-
{!
|
|
514
|
+
{!connectedAddress ? (
|
|
497
515
|
<ShinyButton
|
|
498
516
|
accentColor={themeColor || "hsl(var(--as-brand))"}
|
|
499
517
|
onClick={handleConnectWallet}
|
|
@@ -17,13 +17,21 @@ export enum CryptoPaymentMethodType {
|
|
|
17
17
|
TRANSFER_CRYPTO = "transfer_crypto",
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
const recommendWallets = [
|
|
20
|
+
export const recommendWallets = [
|
|
21
21
|
createWallet("io.metamask"),
|
|
22
22
|
createWallet("com.coinbase.wallet"),
|
|
23
23
|
createWallet("me.rainbow"),
|
|
24
24
|
createWallet("io.rabby"),
|
|
25
25
|
];
|
|
26
26
|
|
|
27
|
+
export const connectModalConfig = {
|
|
28
|
+
client,
|
|
29
|
+
setActive: false as const,
|
|
30
|
+
size: "compact" as const,
|
|
31
|
+
showThirdwebBranding: false,
|
|
32
|
+
wallets: recommendWallets,
|
|
33
|
+
};
|
|
34
|
+
|
|
27
35
|
interface CryptoPaymentMethodProps {
|
|
28
36
|
selectedPaymentMethod: CryptoPaymentMethodType;
|
|
29
37
|
setSelectedPaymentMethod: (method: CryptoPaymentMethodType) => void;
|
|
@@ -65,13 +73,7 @@ export function CryptoPaymentMethod({
|
|
|
65
73
|
disconnect(connectedEOAWallet);
|
|
66
74
|
}
|
|
67
75
|
|
|
68
|
-
const wallet = await openConnectModal(
|
|
69
|
-
client,
|
|
70
|
-
setActive: false,
|
|
71
|
-
size: "compact",
|
|
72
|
-
showThirdwebBranding: false,
|
|
73
|
-
wallets: recommendWallets,
|
|
74
|
-
});
|
|
76
|
+
const wallet = await openConnectModal(connectModalConfig);
|
|
75
77
|
|
|
76
78
|
if (wallet) {
|
|
77
79
|
setSelectedPaymentMethod(CryptoPaymentMethodType.CONNECT_WALLET);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Button, toast, useProfile } from "@b3dotfun/sdk/global-account/react";
|
|
2
|
+
import { validateImageUrl } from "@b3dotfun/sdk/global-account/react/utils/profileDisplay";
|
|
2
3
|
import { truncateAddress } from "@b3dotfun/sdk/shared/utils/truncateAddress";
|
|
3
4
|
import { Copy, Loader2, UnlinkIcon } from "lucide-react";
|
|
4
5
|
|
|
@@ -55,7 +56,7 @@ const LinkedAccountItem = ({
|
|
|
55
56
|
}
|
|
56
57
|
};
|
|
57
58
|
|
|
58
|
-
const displayImageUrl = profileData?.avatar || profile.imageUrl;
|
|
59
|
+
const displayImageUrl = validateImageUrl(profileData?.avatar) || validateImageUrl(profile.imageUrl);
|
|
59
60
|
const displayName = profileData?.name || displayTitle;
|
|
60
61
|
|
|
61
62
|
return (
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ens_normalize } from "@adraffy/ens-normalize";
|
|
2
2
|
import app from "@b3dotfun/sdk/global-account/app";
|
|
3
3
|
import { toast, useAuthentication, useB3Config, useModalStore, useProfile } from "@b3dotfun/sdk/global-account/react";
|
|
4
|
+
import { validateImageUrl } from "@b3dotfun/sdk/global-account/react/utils/profileDisplay";
|
|
4
5
|
import { formatUsername } from "@b3dotfun/sdk/shared/utils";
|
|
5
6
|
import { Check, Loader2, Pencil, X } from "lucide-react";
|
|
6
7
|
import { useEffect, useRef, useState } from "react";
|
|
@@ -29,8 +30,7 @@ const SettingsProfileCard = () => {
|
|
|
29
30
|
const [isSaving, setIsSaving] = useState(false);
|
|
30
31
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
const avatarSrc = user?.avatar || profile?.avatar;
|
|
33
|
+
const avatarSrc = validateImageUrl(user?.avatar) || validateImageUrl(profile?.avatar);
|
|
34
34
|
|
|
35
35
|
// Get current username - prioritize user.username, fallback to profile data
|
|
36
36
|
const currentUsername = user?.username || profile?.displayName || formatUsername(profile?.name || "");
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
useSimBalance,
|
|
10
10
|
useUnifiedChainSwitchAndExecute,
|
|
11
11
|
} from "@b3dotfun/sdk/global-account/react";
|
|
12
|
+
import { validateImageUrl } from "@b3dotfun/sdk/global-account/react/utils/profileDisplay";
|
|
12
13
|
import { formatDisplayNumber, formatTokenAmount } from "@b3dotfun/sdk/shared/utils/number";
|
|
13
14
|
import invariant from "invariant";
|
|
14
15
|
import { CircleHelp, Clock, Loader2, Send as SendIcon, Wallet } from "lucide-react";
|
|
@@ -32,6 +33,7 @@ type SendStep = "recipient" | "token" | "amount" | "confirm" | "success";
|
|
|
32
33
|
// Component for displaying a recent address with profile data
|
|
33
34
|
function RecentAddressItem({ address, onClick }: { address: string; onClick: () => void }) {
|
|
34
35
|
const { data: profileData } = useProfile({ address });
|
|
36
|
+
const validatedAvatar = validateImageUrl(profileData?.avatar);
|
|
35
37
|
|
|
36
38
|
return (
|
|
37
39
|
<button
|
|
@@ -39,8 +41,8 @@ function RecentAddressItem({ address, onClick }: { address: string; onClick: ()
|
|
|
39
41
|
className="flex items-center gap-2 rounded-xl px-3 py-2 transition-colors hover:bg-[#fafafa]"
|
|
40
42
|
>
|
|
41
43
|
{/* Avatar */}
|
|
42
|
-
{
|
|
43
|
-
<img src={
|
|
44
|
+
{validatedAvatar ? (
|
|
45
|
+
<img src={validatedAvatar} alt={profileData?.name || address} className="h-10 w-10 rounded-full" />
|
|
44
46
|
) : (
|
|
45
47
|
<div className="flex h-10 w-10 items-center justify-center rounded-full border border-[#e4e4e7] bg-[#f4f4f5]">
|
|
46
48
|
<Wallet className="h-5 w-5 text-[#a0a0ab]" />
|
|
@@ -84,6 +86,7 @@ export function Send({ recipientAddress: initialRecipient, onSuccess }: SendModa
|
|
|
84
86
|
const { data: validatedProfileData } = useProfile({
|
|
85
87
|
address: showValidatedResult && recipientAddress && isAddress(recipientAddress) ? recipientAddress : undefined,
|
|
86
88
|
});
|
|
89
|
+
const validatedRecipientAvatar = validateImageUrl(validatedProfileData?.avatar);
|
|
87
90
|
|
|
88
91
|
// Address validation
|
|
89
92
|
const handleRecipientAddressChange = (value: string) => {
|
|
@@ -290,10 +293,10 @@ export function Send({ recipientAddress: initialRecipient, onSuccess }: SendModa
|
|
|
290
293
|
className="dark:bg-b3-background dark:border-b3-line flex items-center gap-2 rounded-xl bg-[#f4f4f5] px-3 py-2 transition-colors hover:bg-[#e4e4e7]"
|
|
291
294
|
>
|
|
292
295
|
{/* Avatar */}
|
|
293
|
-
{
|
|
296
|
+
{validatedRecipientAvatar ? (
|
|
294
297
|
<img
|
|
295
|
-
src={
|
|
296
|
-
alt={validatedProfileData
|
|
298
|
+
src={validatedRecipientAvatar}
|
|
299
|
+
alt={validatedProfileData?.name || recipientAddress}
|
|
297
300
|
className="h-10 w-10 rounded-full"
|
|
298
301
|
/>
|
|
299
302
|
) : (
|
package/src/global-account/react/components/SingleUserSearchSelector/SingleUserSearchSelector.tsx
CHANGED
|
@@ -5,6 +5,7 @@ import { Search, X } from "lucide-react";
|
|
|
5
5
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
6
6
|
import type { CombinedProfile, Profile } from "../../hooks/useProfile";
|
|
7
7
|
import { fetchProfile as fetchProfileApi } from "../../utils/profileApi";
|
|
8
|
+
import { validateImageUrl } from "../../utils/profileDisplay";
|
|
8
9
|
import { IPFSMediaRenderer } from "../IPFSMediaRenderer/IPFSMediaRenderer";
|
|
9
10
|
import { Input } from "../ui/input";
|
|
10
11
|
|
|
@@ -283,7 +284,7 @@ export function SingleUserSearchSelector({
|
|
|
283
284
|
{/* Avatar */}
|
|
284
285
|
<div className="single-user-search-result-avatar h-11 w-11 shrink-0">
|
|
285
286
|
<IPFSMediaRenderer
|
|
286
|
-
src={searchResult.avatar}
|
|
287
|
+
src={validateImageUrl(searchResult.avatar) ?? undefined}
|
|
287
288
|
alt={getDisplayName(searchResult)}
|
|
288
289
|
className="h-full w-full rounded-full object-cover"
|
|
289
290
|
/>
|