@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ensofinance/checkout-widget",
3
- "version": "0.1.8",
3
+ "version": "1.0.1",
4
4
  "type": "module",
5
5
  "homepage": "https://www.enso.build/",
6
6
  "repository": {
@@ -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(fiatAmount) || 0,
128
+ amount: parseFloat(debouncedFiatAmount) || 0,
121
129
  countryCode: countryCode || "",
122
130
  walletAddress: smartAccountAddress || "",
123
131
  enabled:
124
132
  !!countryCode &&
125
133
  !!destinationCryptoCode &&
126
- parseFloat(fiatAmount) > 0 &&
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={3}>
76
- <Skeleton height="56px" borderRadius="card" />
77
- <Skeleton height="56px" borderRadius="card" />
78
- <Skeleton height="56px" borderRadius="card" />
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 selected =
85
+ const isSelected =
84
86
  selectedQuote?.serviceProvider ===
85
87
  quote.serviceProvider;
86
88
  return (
87
- <Flex
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
- align="center"
103
- gap={3}
95
+ selected={isSelected}
104
96
  >
105
- {/* Provider Icon */}
106
- <Center
107
- boxSize="40px"
108
- borderRadius="card"
109
- bg="bg.subtle"
110
- overflow="hidden"
111
- flexShrink={0}
97
+ <Box
98
+ display="flex"
99
+ alignItems="center"
100
+ gap="1"
101
+ minWidth="320px"
112
102
  >
113
- <Image
114
- src={getProviderIcon(
115
- quote.serviceProvider,
116
- )}
117
- alt={quote.serviceProvider}
118
- boxSize="40px"
119
- objectFit="contain"
120
- onError={(e) => {
121
- (
122
- e.target as HTMLImageElement
123
- ).style.display = "none";
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
- {formatProviderName(
136
- quote.serviceProvider,
137
- )}
138
- </Text>
139
- {idx === 0 && (
140
- <Text
141
- fontSize="xs"
142
- color="success"
143
- fontWeight="medium"
144
- >
145
- Best price
146
- </Text>
147
- )}
148
- {quote.lowKyc && (
149
- <Text
150
- fontSize="xs"
151
- color="primary.muted"
152
- fontWeight="medium"
153
- >
154
- Low KYC
155
- </Text>
156
- )}
157
- </Flex>
158
- <Text fontSize="xs" color="fg.subtle">
159
- {quote.destinationAmount.toFixed(4)}{" "}
160
- {destinationSymbol} • Fee:{" "}
161
- {fiatPrefix}
162
- {quote.totalFee.toFixed(2)}
163
- </Text>
164
- </Flex>
165
- {/* Selected Check */}
166
- <Icon
167
- as={Check}
168
- color={selected ? "primary" : "transparent"}
169
- boxSize={5}
170
- flexShrink={0}
171
- />
172
- </Flex>
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
- <Text fontSize="md" color="fg.muted" mt={2}>
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
- `Estimated amount: ${selectedQuote.destinationAmount?.toString()} ${destinationSymbol}`
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
- "Enter amount to see quote"
234
+ <Text>Enter amount to see quote</Text>
231
235
  )}
232
- </Text>
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
- <Flex
245
- p={3}
246
- borderRadius="card"
247
- border="1px solid"
248
- borderColor="border"
249
- align="center"
250
- justify="center"
251
- gap={2}
252
- >
253
- <Spinner size="sm" color="fg.muted" />
254
- <Text fontSize="sm" color="fg.muted">
255
- Finding best rates...
256
- </Text>
257
- </Flex>
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
- <Flex
260
- p={3}
261
- borderRadius="card"
262
- border="1px solid"
263
- borderColor="border"
264
- cursor="pointer"
265
- onClick={() => setShowProviderSelect(true)}
266
- align="center"
267
- justify="space-between"
268
- gap={2}
269
- >
270
- <Text fontSize="sm" color="fg.muted">
271
- Provider
272
- </Text>
273
-
274
- <Flex align="center" gap={2}>
275
- {quotesLoading ? (
276
- <Skeleton height="20px" width="100px" />
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
- objectFit="contain"
293
- onError={(e) => {
294
- (
295
- e.target as HTMLImageElement
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
- {formatProviderName(
307
- selectedQuote.serviceProvider,
308
- )}
309
- </Text>
310
- {isBestRate && (
311
- <Text
312
- fontSize="xs"
313
- color="green.600"
314
- fontWeight="medium"
315
- >
316
- Best price
317
- </Text>
318
- )}
319
- {selectedQuote.lowKyc && (
320
- <Text
321
- fontSize="xs"
322
- color="blue.600"
323
- fontWeight="medium"
324
- >
325
- Low KYC
326
- </Text>
327
- )}
328
- </Flex>
329
- </>
330
- ) : (
331
- <Text fontSize="sm" color="fg.subtle">
332
- Select provider
333
- </Text>
334
- )}
335
- <Icon as={ChevronRight} color="fg.muted" boxSize={5} />
336
- </Flex>
337
- </Flex>
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, useMeldTransaction } from "@/util/meld-hooks";
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="red.500">
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="red.500"
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="red.500">
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="orange.100"
786
+ bg="bg.emphasized"
787
787
  >
788
788
  <Icon
789
789
  as={TriangleAlert}
790
790
  boxSize={6}
791
- color="orange.500"
791
+ color="warning"
792
792
  />
793
793
  </Flex>
794
- <Text fontSize="md" textAlign="center" color="gray.700">
794
+ <Text fontSize="md" textAlign="center" color="fg.muted">
795
795
  {error}
796
796
  </Text>
797
797
  </Flex>
@@ -245,7 +245,7 @@ const FlowSelector = () => {
245
245
  >
246
246
  <Icon
247
247
  as={AlertCircle}
248
- color="red.500"
248
+ color="error"
249
249
  width="48px"
250
250
  height="48px"
251
251
  />
@@ -350,7 +350,7 @@ const WalletConfirmStep = ({
350
350
  target="_blank"
351
351
  rel="noreferrer"
352
352
  >
353
- <Text color="blue.500">
353
+ <Text color="primary">
354
354
  View on{" "}
355
355
  {isCrosschain
356
356
  ? "LayerZero"