@ensofinance/checkout-widget 0.1.8 → 1.0.1
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/checkout-widget.es.js +7287 -7299
- package/dist/checkout-widget.es.js.map +1 -1
- package/dist/checkout-widget.umd.js +48 -48
- package/dist/checkout-widget.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/components/steps/CardBuyFlow/CardBuyFlow.tsx +11 -3
- package/src/components/steps/CardBuyFlow/ChooseAmountStep.tsx +170 -179
- package/src/components/steps/CardBuyFlow/OpenWidgetStep.tsx +1 -5
- package/src/components/steps/ExchangeFlow.tsx +6 -6
- package/src/components/steps/FlowSelector.tsx +1 -1
- package/src/components/steps/WalletFlow/WalletConfirmStep.tsx +1 -1
- package/src/components/steps/shared/TrackUserOpStep.tsx +40 -15
- package/src/enso-api/custom-instance.ts +5 -1
- package/src/util/meld-hooks.tsx +0 -39
package/package.json
CHANGED
|
@@ -80,9 +80,17 @@ const CardBuyFlow = ({
|
|
|
80
80
|
const [currentStep, setCurrentStep] = useState<CardBuyStep>(initialStep);
|
|
81
81
|
const [fiatCurrency, setFiatCurrency] = useState<"USD" | "EUR">("USD");
|
|
82
82
|
const [fiatAmount, setFiatAmount] = useState<string>("100");
|
|
83
|
+
const [debouncedFiatAmount, setDebouncedFiatAmount] =
|
|
84
|
+
useState<string>(fiatAmount);
|
|
83
85
|
const [selectedQuote, setSelectedQuote] = useState<MeldQuote | null>(null);
|
|
84
86
|
const [error, setError] = useState<string | null>(null);
|
|
85
87
|
|
|
88
|
+
// Debounce fiat amount for quote requests
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
const timer = setTimeout(() => setDebouncedFiatAmount(fiatAmount), 500);
|
|
91
|
+
return () => clearTimeout(timer);
|
|
92
|
+
}, [fiatAmount]);
|
|
93
|
+
|
|
86
94
|
// Smart account address - use recipient override if provided
|
|
87
95
|
const { smartAccountAddress } = useSmartAccountAddress(chainIdOut);
|
|
88
96
|
|
|
@@ -109,7 +117,7 @@ const CardBuyFlow = ({
|
|
|
109
117
|
!!chainIdOut &&
|
|
110
118
|
isMeldCardBuySupportedChain(chainIdOut, supportedCryptos);
|
|
111
119
|
|
|
112
|
-
// Fetch quotes
|
|
120
|
+
// Fetch quotes (uses debounced amount to avoid excessive requests)
|
|
113
121
|
const {
|
|
114
122
|
data: quotes,
|
|
115
123
|
isLoading: quotesLoading,
|
|
@@ -117,13 +125,13 @@ const CardBuyFlow = ({
|
|
|
117
125
|
} = useMeldQuotes({
|
|
118
126
|
sourceCurrency: fiatCurrency,
|
|
119
127
|
destinationCurrency: destinationCryptoCode || "",
|
|
120
|
-
amount: parseFloat(
|
|
128
|
+
amount: parseFloat(debouncedFiatAmount) || 0,
|
|
121
129
|
countryCode: countryCode || "",
|
|
122
130
|
walletAddress: smartAccountAddress || "",
|
|
123
131
|
enabled:
|
|
124
132
|
!!countryCode &&
|
|
125
133
|
!!destinationCryptoCode &&
|
|
126
|
-
parseFloat(
|
|
134
|
+
parseFloat(debouncedFiatAmount) > 0 &&
|
|
127
135
|
!!smartAccountAddress,
|
|
128
136
|
});
|
|
129
137
|
|
|
@@ -11,7 +11,8 @@ import {
|
|
|
11
11
|
import { ChevronRight, Check } from "lucide-react";
|
|
12
12
|
import { useState } from "react";
|
|
13
13
|
import { BodyWrapper } from "../../ui/styled";
|
|
14
|
-
import { Button, Input } from "../../ui";
|
|
14
|
+
import { Button, Card, Input, Tag } from "../../ui";
|
|
15
|
+
import { AnimatedStep } from "../../ui/transitions";
|
|
15
16
|
import { formatProviderName, getProviderIcon } from "@/util/meld-hooks";
|
|
16
17
|
import type { MeldQuote } from "@/types";
|
|
17
18
|
|
|
@@ -70,106 +71,93 @@ const ChooseAmountStep = ({
|
|
|
70
71
|
// Provider Selection View
|
|
71
72
|
if (showProviderSelect) {
|
|
72
73
|
return (
|
|
74
|
+
<AnimatedStep key="provider-select">
|
|
73
75
|
<BodyWrapper>
|
|
74
76
|
{quotesLoading ? (
|
|
75
|
-
<Flex direction="column" gap={
|
|
76
|
-
<Skeleton height="
|
|
77
|
-
<Skeleton height="
|
|
78
|
-
<Skeleton height="
|
|
77
|
+
<Flex direction="column" gap={2}>
|
|
78
|
+
<Skeleton height="48px" borderRadius="card" />
|
|
79
|
+
<Skeleton height="48px" borderRadius="card" />
|
|
80
|
+
<Skeleton height="48px" borderRadius="card" />
|
|
79
81
|
</Flex>
|
|
80
82
|
) : quotes && quotes.length > 0 ? (
|
|
81
83
|
<Flex direction="column" gap={2}>
|
|
82
84
|
{quotes.map((quote, idx) => {
|
|
83
|
-
const
|
|
85
|
+
const isSelected =
|
|
84
86
|
selectedQuote?.serviceProvider ===
|
|
85
87
|
quote.serviceProvider;
|
|
86
88
|
return (
|
|
87
|
-
<
|
|
89
|
+
<Card
|
|
88
90
|
key={`${quote.serviceProvider}-${idx}`}
|
|
89
|
-
p={3}
|
|
90
|
-
h="56px"
|
|
91
|
-
borderRadius="card"
|
|
92
|
-
border="1px solid"
|
|
93
|
-
borderColor={
|
|
94
|
-
selected ? "primary" : "border"
|
|
95
|
-
}
|
|
96
|
-
bg={selected ? "bg.subtle" : "transparent"}
|
|
97
|
-
cursor="pointer"
|
|
98
91
|
onClick={() => {
|
|
99
92
|
setSelectedQuote(quote);
|
|
100
93
|
setShowProviderSelect(false);
|
|
101
94
|
}}
|
|
102
|
-
|
|
103
|
-
gap={3}
|
|
95
|
+
selected={isSelected}
|
|
104
96
|
>
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
overflow="hidden"
|
|
111
|
-
flexShrink={0}
|
|
97
|
+
<Box
|
|
98
|
+
display="flex"
|
|
99
|
+
alignItems="center"
|
|
100
|
+
gap="1"
|
|
101
|
+
minWidth="320px"
|
|
112
102
|
>
|
|
113
|
-
<
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
/>
|
|
126
|
-
</Center>
|
|
127
|
-
{/* Provider Info */}
|
|
128
|
-
<Flex direction="column" flex={1}>
|
|
129
|
-
<Flex align="center" gap={2}>
|
|
130
|
-
<Text
|
|
131
|
-
fontSize="sm"
|
|
132
|
-
fontWeight="semibold"
|
|
133
|
-
color="fg"
|
|
103
|
+
<Box
|
|
104
|
+
display="flex"
|
|
105
|
+
alignItems="center"
|
|
106
|
+
gap="1"
|
|
107
|
+
marginRight="auto"
|
|
108
|
+
>
|
|
109
|
+
<Center
|
|
110
|
+
boxSize="32px"
|
|
111
|
+
borderRadius="card"
|
|
112
|
+
bg="bg.subtle"
|
|
113
|
+
overflow="hidden"
|
|
114
|
+
flexShrink={0}
|
|
134
115
|
>
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
</
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
116
|
+
<Image
|
|
117
|
+
src={getProviderIcon(
|
|
118
|
+
quote.serviceProvider,
|
|
119
|
+
)}
|
|
120
|
+
alt={quote.serviceProvider}
|
|
121
|
+
boxSize="32px"
|
|
122
|
+
objectFit="contain"
|
|
123
|
+
onError={(e) => {
|
|
124
|
+
(
|
|
125
|
+
e.target as HTMLImageElement
|
|
126
|
+
).style.display =
|
|
127
|
+
"none";
|
|
128
|
+
}}
|
|
129
|
+
/>
|
|
130
|
+
</Center>
|
|
131
|
+
<Box>
|
|
132
|
+
<Card.Title>
|
|
133
|
+
{formatProviderName(
|
|
134
|
+
quote.serviceProvider,
|
|
135
|
+
)}
|
|
136
|
+
</Card.Title>
|
|
137
|
+
<Card.Description>
|
|
138
|
+
{quote.destinationAmount.toFixed(
|
|
139
|
+
4,
|
|
140
|
+
)}{" "}
|
|
141
|
+
{destinationSymbol} • Fee:{" "}
|
|
142
|
+
{fiatPrefix}
|
|
143
|
+
{quote.totalFee.toFixed(2)}
|
|
144
|
+
</Card.Description>
|
|
145
|
+
</Box>
|
|
146
|
+
</Box>
|
|
147
|
+
{idx === 0 && <Tag>Best price</Tag>}
|
|
148
|
+
{quote.lowKyc && <Tag>Low KYC</Tag>}
|
|
149
|
+
<Icon
|
|
150
|
+
as={Check}
|
|
151
|
+
color={
|
|
152
|
+
isSelected
|
|
153
|
+
? "primary"
|
|
154
|
+
: "transparent"
|
|
155
|
+
}
|
|
156
|
+
boxSize={5}
|
|
157
|
+
flexShrink={0}
|
|
158
|
+
/>
|
|
159
|
+
</Box>
|
|
160
|
+
</Card>
|
|
173
161
|
);
|
|
174
162
|
})}
|
|
175
163
|
</Flex>
|
|
@@ -179,11 +167,13 @@ const ChooseAmountStep = ({
|
|
|
179
167
|
</Text>
|
|
180
168
|
)}
|
|
181
169
|
</BodyWrapper>
|
|
170
|
+
</AnimatedStep>
|
|
182
171
|
);
|
|
183
172
|
}
|
|
184
173
|
|
|
185
174
|
// Main View
|
|
186
175
|
return (
|
|
176
|
+
<AnimatedStep key="main-view">
|
|
187
177
|
<BodyWrapper>
|
|
188
178
|
{/* Currency Toggle */}
|
|
189
179
|
<Flex gap={2} width="100%">
|
|
@@ -219,17 +209,31 @@ const ChooseAmountStep = ({
|
|
|
219
209
|
value={displayValue}
|
|
220
210
|
onChange={handleAmountChange}
|
|
221
211
|
marginY="8px"
|
|
212
|
+
w={"full"}
|
|
222
213
|
/>
|
|
223
214
|
{/* Crypto equivalent */}
|
|
224
|
-
<
|
|
215
|
+
<Flex
|
|
216
|
+
fontSize="md"
|
|
217
|
+
color="fg.muted"
|
|
218
|
+
mt={2}
|
|
219
|
+
gap={1}
|
|
220
|
+
justifyContent="space-between"
|
|
221
|
+
w={"full"}
|
|
222
|
+
>
|
|
225
223
|
{quotesLoading ? (
|
|
226
224
|
<Skeleton height="16px" width="120px" />
|
|
227
225
|
) : selectedQuote ? (
|
|
228
|
-
|
|
226
|
+
<>
|
|
227
|
+
<Text fontSize="sm">Estimated amount:</Text>
|
|
228
|
+
<Text fontSize="sm" fontWeight="semibold">
|
|
229
|
+
{selectedQuote.destinationAmount?.toString()}{" "}
|
|
230
|
+
{destinationSymbol}
|
|
231
|
+
</Text>
|
|
232
|
+
</>
|
|
229
233
|
) : (
|
|
230
|
-
|
|
234
|
+
<Text>Enter amount to see quote</Text>
|
|
231
235
|
)}
|
|
232
|
-
</
|
|
236
|
+
</Flex>
|
|
233
237
|
</Box>
|
|
234
238
|
|
|
235
239
|
{/* Error */}
|
|
@@ -241,100 +245,86 @@ const ChooseAmountStep = ({
|
|
|
241
245
|
|
|
242
246
|
{/* Provider Row */}
|
|
243
247
|
{isInitialLoading ? (
|
|
244
|
-
<
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
</
|
|
257
|
-
</
|
|
248
|
+
<Card>
|
|
249
|
+
<Box
|
|
250
|
+
display="flex"
|
|
251
|
+
alignItems="center"
|
|
252
|
+
justifyContent="center"
|
|
253
|
+
gap="1"
|
|
254
|
+
minWidth="320px"
|
|
255
|
+
>
|
|
256
|
+
<Spinner size="sm" color="fg.muted" />
|
|
257
|
+
<Text fontSize="sm" color="fg.muted">
|
|
258
|
+
Finding best rates...
|
|
259
|
+
</Text>
|
|
260
|
+
</Box>
|
|
261
|
+
</Card>
|
|
258
262
|
) : (
|
|
259
|
-
<
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
) : selectedQuote ? (
|
|
278
|
-
<>
|
|
279
|
-
<Center
|
|
280
|
-
boxSize="32px"
|
|
281
|
-
borderRadius="card"
|
|
282
|
-
bg="bg.subtle"
|
|
283
|
-
overflow="hidden"
|
|
284
|
-
flexShrink={0}
|
|
285
|
-
>
|
|
286
|
-
<Image
|
|
287
|
-
src={getProviderIcon(
|
|
288
|
-
selectedQuote.serviceProvider,
|
|
289
|
-
)}
|
|
290
|
-
alt={selectedQuote.serviceProvider}
|
|
263
|
+
<Card onClick={() => setShowProviderSelect(true)}>
|
|
264
|
+
<Box
|
|
265
|
+
display="flex"
|
|
266
|
+
alignItems="center"
|
|
267
|
+
gap="1"
|
|
268
|
+
minWidth="320px"
|
|
269
|
+
>
|
|
270
|
+
<Box
|
|
271
|
+
display="flex"
|
|
272
|
+
alignItems="center"
|
|
273
|
+
gap="1"
|
|
274
|
+
marginRight="auto"
|
|
275
|
+
>
|
|
276
|
+
{quotesLoading ? (
|
|
277
|
+
<Skeleton height="20px" width="100px" />
|
|
278
|
+
) : selectedQuote ? (
|
|
279
|
+
<>
|
|
280
|
+
<Center
|
|
291
281
|
boxSize="32px"
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
).style.display = "none";
|
|
297
|
-
}}
|
|
298
|
-
/>
|
|
299
|
-
</Center>
|
|
300
|
-
<Flex direction="column" align="end">
|
|
301
|
-
<Text
|
|
302
|
-
fontSize="sm"
|
|
303
|
-
fontWeight="semibold"
|
|
304
|
-
color="fg"
|
|
282
|
+
borderRadius="card"
|
|
283
|
+
bg="bg.subtle"
|
|
284
|
+
overflow="hidden"
|
|
285
|
+
flexShrink={0}
|
|
305
286
|
>
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
287
|
+
<Image
|
|
288
|
+
src={getProviderIcon(
|
|
289
|
+
selectedQuote.serviceProvider,
|
|
290
|
+
)}
|
|
291
|
+
alt={selectedQuote.serviceProvider}
|
|
292
|
+
boxSize="32px"
|
|
293
|
+
objectFit="contain"
|
|
294
|
+
onError={(e) => {
|
|
295
|
+
(
|
|
296
|
+
e.target as HTMLImageElement
|
|
297
|
+
).style.display = "none";
|
|
298
|
+
}}
|
|
299
|
+
/>
|
|
300
|
+
</Center>
|
|
301
|
+
<Box>
|
|
302
|
+
<Card.Title>
|
|
303
|
+
{formatProviderName(
|
|
304
|
+
selectedQuote.serviceProvider,
|
|
305
|
+
)}
|
|
306
|
+
</Card.Title>
|
|
307
|
+
<Card.Description>
|
|
308
|
+
{isBestRate && "Best price"}
|
|
309
|
+
{isBestRate &&
|
|
310
|
+
selectedQuote.lowKyc &&
|
|
311
|
+
" • "}
|
|
312
|
+
{selectedQuote.lowKyc && "Low KYC"}
|
|
313
|
+
</Card.Description>
|
|
314
|
+
</Box>
|
|
315
|
+
</>
|
|
316
|
+
) : (
|
|
317
|
+
<Card.Title>Select provider</Card.Title>
|
|
318
|
+
)}
|
|
319
|
+
</Box>
|
|
320
|
+
<Icon
|
|
321
|
+
as={ChevronRight}
|
|
322
|
+
color="fg.muted"
|
|
323
|
+
width="16px"
|
|
324
|
+
height="16px"
|
|
325
|
+
/>
|
|
326
|
+
</Box>
|
|
327
|
+
</Card>
|
|
338
328
|
)}
|
|
339
329
|
|
|
340
330
|
{/* Continue Button */}
|
|
@@ -346,6 +336,7 @@ const ChooseAmountStep = ({
|
|
|
346
336
|
Continue
|
|
347
337
|
</Button>
|
|
348
338
|
</BodyWrapper>
|
|
339
|
+
</AnimatedStep>
|
|
349
340
|
);
|
|
350
341
|
};
|
|
351
342
|
|
|
@@ -2,7 +2,7 @@ import { Spinner, Text, Flex, Image, Box, Center } from "@chakra-ui/react";
|
|
|
2
2
|
import { useEffect, useRef, useState } from "react";
|
|
3
3
|
import { BodyWrapper } from "../../ui/styled";
|
|
4
4
|
import { Button } from "../../ui";
|
|
5
|
-
import { formatProviderName
|
|
5
|
+
import { formatProviderName } from "@/util/meld-hooks";
|
|
6
6
|
import { CircleTimer } from "@/components/CircleTimer";
|
|
7
7
|
import type { MeldQuote, MeldTransactionStatus } from "@/types";
|
|
8
8
|
import FailIcon from "@/assets/fail.svg";
|
|
@@ -70,10 +70,6 @@ const OpenWidgetStep = ({
|
|
|
70
70
|
const [isTimerFinished, setIsTimerFinished] = useState(false);
|
|
71
71
|
const { smartAccountAddress } = useSmartAccountAddress();
|
|
72
72
|
|
|
73
|
-
// Track payment status via MELD for informational UI only.
|
|
74
|
-
const { data: transaction } = useMeldTransaction(sessionId);
|
|
75
|
-
const txStatus = transaction?.status;
|
|
76
|
-
|
|
77
73
|
// Auto-create session on mount
|
|
78
74
|
useEffect(() => {
|
|
79
75
|
if (!widgetUrl && !isCreatingSession && !error) {
|
|
@@ -249,7 +249,7 @@ const ChooseExchangeStep = ({
|
|
|
249
249
|
if (error)
|
|
250
250
|
return (
|
|
251
251
|
<BodyWrapper>
|
|
252
|
-
<Box p={5} color="
|
|
252
|
+
<Box p={5} color="error">
|
|
253
253
|
Failed to load exchanges
|
|
254
254
|
</Box>
|
|
255
255
|
</BodyWrapper>
|
|
@@ -391,7 +391,7 @@ const CheckSessionKeyStep = ({
|
|
|
391
391
|
<Text
|
|
392
392
|
fontSize="lg"
|
|
393
393
|
fontWeight="bold"
|
|
394
|
-
color="
|
|
394
|
+
color="error"
|
|
395
395
|
mb={4}
|
|
396
396
|
>
|
|
397
397
|
Unsupported Network
|
|
@@ -568,7 +568,7 @@ const ChooseAssetStep = ({
|
|
|
568
568
|
);
|
|
569
569
|
if (error)
|
|
570
570
|
return (
|
|
571
|
-
<Box p={5} color="
|
|
571
|
+
<Box p={5} color="error">
|
|
572
572
|
Error:{" "}
|
|
573
573
|
{error instanceof Error
|
|
574
574
|
? error.message
|
|
@@ -783,15 +783,15 @@ const InitiateWithdrawalStep = ({
|
|
|
783
783
|
w={12}
|
|
784
784
|
h={12}
|
|
785
785
|
borderRadius="full"
|
|
786
|
-
bg="
|
|
786
|
+
bg="bg.emphasized"
|
|
787
787
|
>
|
|
788
788
|
<Icon
|
|
789
789
|
as={TriangleAlert}
|
|
790
790
|
boxSize={6}
|
|
791
|
-
color="
|
|
791
|
+
color="warning"
|
|
792
792
|
/>
|
|
793
793
|
</Flex>
|
|
794
|
-
<Text fontSize="md" textAlign="center" color="
|
|
794
|
+
<Text fontSize="md" textAlign="center" color="fg.muted">
|
|
795
795
|
{error}
|
|
796
796
|
</Text>
|
|
797
797
|
</Flex>
|