@ton-pay/ui-react 0.1.2 → 0.2.0-beta.2
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/README.md +126 -98
- package/dist/index.d.mts +257 -28
- package/dist/index.d.ts +257 -28
- package/dist/index.js +3217 -319
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3155 -257
- package/dist/index.mjs.map +1 -1
- package/package.json +25 -6
package/dist/index.mjs
CHANGED
|
@@ -1,28 +1,285 @@
|
|
|
1
|
-
// src/components/ton-pay-
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
// src/components/ton-pay-button/TonPayButton.tsx
|
|
2
|
+
import "react";
|
|
3
|
+
import { useEffect as useEffect3, useState as useState4, useCallback as useCallback4 } from "react";
|
|
4
|
+
import { useTonAddress, useTonConnectUI } from "@tonconnect/ui-react";
|
|
5
5
|
|
|
6
|
-
// src/
|
|
6
|
+
// src/utils/index.ts
|
|
7
|
+
function toCssSize(value) {
|
|
8
|
+
if (value === void 0) return void 0;
|
|
9
|
+
return typeof value === "number" ? `${value}px` : value;
|
|
10
|
+
}
|
|
11
|
+
function classNames(...classes) {
|
|
12
|
+
return classes.filter(Boolean).join(" ");
|
|
13
|
+
}
|
|
14
|
+
async function getUserIp() {
|
|
15
|
+
try {
|
|
16
|
+
const response = await fetch("https://api.ipify.org?format=json");
|
|
17
|
+
const data = await response.json();
|
|
18
|
+
return data.ip;
|
|
19
|
+
} catch (error) {
|
|
20
|
+
console.error("Failed to fetch user IP:", error);
|
|
21
|
+
return "";
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// src/components/icons/index.tsx
|
|
7
26
|
import "react";
|
|
8
27
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
28
|
+
var TonIcon = ({ size = 24, className }) => /* @__PURE__ */ jsxs(
|
|
29
|
+
"svg",
|
|
30
|
+
{
|
|
31
|
+
width: size,
|
|
32
|
+
height: size,
|
|
33
|
+
viewBox: "0 0 24 24",
|
|
34
|
+
fill: "none",
|
|
35
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
36
|
+
className,
|
|
37
|
+
"aria-hidden": true,
|
|
38
|
+
children: [
|
|
39
|
+
/* @__PURE__ */ jsxs("g", { clipPath: "url(#ton-icon-clip)", children: [
|
|
40
|
+
/* @__PURE__ */ jsx(
|
|
41
|
+
"path",
|
|
42
|
+
{
|
|
43
|
+
d: "M12 24C18.6274 24 24 18.6274 24 12C24 5.37257 18.6274 0 12 0C5.37257 0 0 5.37257 0 12C0 18.6274 5.37257 24 12 24Z",
|
|
44
|
+
fill: "#0098EA"
|
|
45
|
+
}
|
|
46
|
+
),
|
|
47
|
+
/* @__PURE__ */ jsx(
|
|
48
|
+
"path",
|
|
49
|
+
{
|
|
50
|
+
d: "M12 24C18.6274 24 24 18.6274 24 12C24 5.37257 18.6274 0 12 0C5.37257 0 0 5.37257 0 12C0 18.6274 5.37257 24 12 24Z",
|
|
51
|
+
fill: "white"
|
|
52
|
+
}
|
|
53
|
+
),
|
|
54
|
+
/* @__PURE__ */ jsx(
|
|
55
|
+
"path",
|
|
56
|
+
{
|
|
57
|
+
d: "M16.0972 6.69763H7.9022C6.39543 6.69763 5.4404 8.32299 6.19846 9.63695L11.2561 18.4033C11.5862 18.9757 12.4133 18.9757 12.7433 18.4033L17.802 9.63695C18.559 8.32509 17.604 6.69763 16.0982 6.69763H16.0972ZM11.252 15.7744L10.1505 13.6426L7.49278 8.88922C7.31746 8.58497 7.53401 8.1951 7.90117 8.1951H11.251V15.7754L11.252 15.7744ZM16.5046 8.88819L13.8479 13.6437L12.7464 15.7744V8.19407H16.0962C16.4633 8.19407 16.6799 8.58395 16.5046 8.88819Z",
|
|
58
|
+
fill: "#0098EA"
|
|
59
|
+
}
|
|
60
|
+
)
|
|
61
|
+
] }),
|
|
62
|
+
/* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx("clipPath", { id: "ton-icon-clip", children: /* @__PURE__ */ jsx("rect", { width: "24", height: "24", fill: "white" }) }) })
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
var TonIconBlue = ({ size = 24, className }) => /* @__PURE__ */ jsxs(
|
|
67
|
+
"svg",
|
|
68
|
+
{
|
|
69
|
+
width: size,
|
|
70
|
+
height: size,
|
|
71
|
+
viewBox: "0 0 24 24",
|
|
72
|
+
fill: "none",
|
|
73
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
74
|
+
className,
|
|
75
|
+
"aria-hidden": true,
|
|
76
|
+
children: [
|
|
77
|
+
/* @__PURE__ */ jsx(
|
|
78
|
+
"path",
|
|
79
|
+
{
|
|
80
|
+
d: "M12 24C18.6274 24 24 18.6274 24 12C24 5.37257 18.6274 0 12 0C5.37257 0 0 5.37257 0 12C0 18.6274 5.37257 24 12 24Z",
|
|
81
|
+
fill: "#0098EA"
|
|
82
|
+
}
|
|
83
|
+
),
|
|
84
|
+
/* @__PURE__ */ jsx(
|
|
85
|
+
"path",
|
|
86
|
+
{
|
|
87
|
+
d: "M16.0972 6.69763H7.9022C6.39543 6.69763 5.4404 8.32299 6.19846 9.63695L11.2561 18.4033C11.5862 18.9757 12.4133 18.9757 12.7433 18.4033L17.802 9.63695C18.559 8.32509 17.604 6.69763 16.0982 6.69763H16.0972ZM11.252 15.7744L10.1505 13.6426L7.49278 8.88922C7.31746 8.58497 7.53401 8.1951 7.90117 8.1951H11.251V15.7754L11.252 15.7744ZM16.5046 8.88819L13.8479 13.6437L12.7464 15.7744V8.19407H16.0962C16.4633 8.19407 16.6799 8.58395 16.5046 8.88819Z",
|
|
88
|
+
fill: "white"
|
|
89
|
+
}
|
|
90
|
+
)
|
|
91
|
+
]
|
|
92
|
+
}
|
|
93
|
+
);
|
|
94
|
+
var CloseIcon = ({
|
|
95
|
+
size = 24,
|
|
96
|
+
color = "currentColor",
|
|
97
|
+
className
|
|
98
|
+
}) => /* @__PURE__ */ jsxs(
|
|
99
|
+
"svg",
|
|
100
|
+
{
|
|
101
|
+
width: size,
|
|
102
|
+
height: size,
|
|
103
|
+
viewBox: "0 0 24 24",
|
|
104
|
+
fill: "none",
|
|
105
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
106
|
+
className,
|
|
107
|
+
"aria-hidden": true,
|
|
108
|
+
children: [
|
|
109
|
+
/* @__PURE__ */ jsx(
|
|
110
|
+
"path",
|
|
111
|
+
{
|
|
112
|
+
d: "M18 6L6 18",
|
|
113
|
+
stroke: color,
|
|
114
|
+
strokeWidth: "2",
|
|
115
|
+
strokeLinecap: "round",
|
|
116
|
+
strokeLinejoin: "round"
|
|
117
|
+
}
|
|
118
|
+
),
|
|
119
|
+
/* @__PURE__ */ jsx(
|
|
120
|
+
"path",
|
|
121
|
+
{
|
|
122
|
+
d: "M6 6L18 18",
|
|
123
|
+
stroke: color,
|
|
124
|
+
strokeWidth: "2",
|
|
125
|
+
strokeLinecap: "round",
|
|
126
|
+
strokeLinejoin: "round"
|
|
127
|
+
}
|
|
128
|
+
)
|
|
129
|
+
]
|
|
130
|
+
}
|
|
131
|
+
);
|
|
132
|
+
var BackIcon = ({
|
|
133
|
+
size = 24,
|
|
134
|
+
color = "currentColor",
|
|
135
|
+
className
|
|
136
|
+
}) => /* @__PURE__ */ jsxs(
|
|
137
|
+
"svg",
|
|
138
|
+
{
|
|
139
|
+
width: size,
|
|
140
|
+
height: size,
|
|
141
|
+
viewBox: "0 0 24 24",
|
|
142
|
+
fill: "none",
|
|
143
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
144
|
+
className,
|
|
145
|
+
"aria-hidden": true,
|
|
146
|
+
children: [
|
|
147
|
+
/* @__PURE__ */ jsx(
|
|
148
|
+
"path",
|
|
149
|
+
{
|
|
150
|
+
d: "M19 12H5",
|
|
151
|
+
stroke: color,
|
|
152
|
+
strokeWidth: "2",
|
|
153
|
+
strokeLinecap: "round",
|
|
154
|
+
strokeLinejoin: "round"
|
|
155
|
+
}
|
|
156
|
+
),
|
|
157
|
+
/* @__PURE__ */ jsx(
|
|
158
|
+
"path",
|
|
159
|
+
{
|
|
160
|
+
d: "M12 19L5 12L12 5",
|
|
161
|
+
stroke: color,
|
|
162
|
+
strokeWidth: "2",
|
|
163
|
+
strokeLinecap: "round",
|
|
164
|
+
strokeLinejoin: "round"
|
|
165
|
+
}
|
|
166
|
+
)
|
|
167
|
+
]
|
|
168
|
+
}
|
|
169
|
+
);
|
|
170
|
+
var CheckIcon = ({
|
|
171
|
+
size = 24,
|
|
172
|
+
color = "currentColor",
|
|
173
|
+
className
|
|
174
|
+
}) => /* @__PURE__ */ jsx(
|
|
175
|
+
"svg",
|
|
176
|
+
{
|
|
177
|
+
width: size,
|
|
178
|
+
height: size,
|
|
179
|
+
viewBox: "0 0 24 24",
|
|
180
|
+
fill: "none",
|
|
181
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
182
|
+
className,
|
|
183
|
+
"aria-hidden": true,
|
|
184
|
+
children: /* @__PURE__ */ jsx(
|
|
185
|
+
"path",
|
|
186
|
+
{
|
|
187
|
+
d: "M20 6L9 17L4 12",
|
|
188
|
+
stroke: color,
|
|
189
|
+
strokeWidth: "2",
|
|
190
|
+
strokeLinecap: "round",
|
|
191
|
+
strokeLinejoin: "round"
|
|
192
|
+
}
|
|
193
|
+
)
|
|
194
|
+
}
|
|
195
|
+
);
|
|
196
|
+
var ErrorIcon = ({
|
|
197
|
+
size = 24,
|
|
198
|
+
color = "currentColor",
|
|
199
|
+
className
|
|
200
|
+
}) => /* @__PURE__ */ jsxs(
|
|
201
|
+
"svg",
|
|
202
|
+
{
|
|
203
|
+
width: size,
|
|
204
|
+
height: size,
|
|
205
|
+
viewBox: "0 0 24 24",
|
|
206
|
+
fill: "none",
|
|
207
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
208
|
+
className,
|
|
209
|
+
"aria-hidden": true,
|
|
210
|
+
children: [
|
|
211
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10", stroke: color, strokeWidth: "2" }),
|
|
212
|
+
/* @__PURE__ */ jsx("path", { d: "M12 8V12", stroke: color, strokeWidth: "2", strokeLinecap: "round" }),
|
|
213
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "16", r: "1", fill: color })
|
|
214
|
+
]
|
|
215
|
+
}
|
|
216
|
+
);
|
|
217
|
+
var MenuIcon = ({
|
|
218
|
+
size = 24,
|
|
219
|
+
color = "currentColor",
|
|
220
|
+
className
|
|
221
|
+
}) => /* @__PURE__ */ jsxs(
|
|
222
|
+
"svg",
|
|
223
|
+
{
|
|
224
|
+
width: size,
|
|
225
|
+
height: size,
|
|
226
|
+
viewBox: "0 0 24 24",
|
|
227
|
+
fill: "none",
|
|
228
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
229
|
+
className,
|
|
230
|
+
"aria-hidden": true,
|
|
231
|
+
children: [
|
|
232
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "2", fill: color }),
|
|
233
|
+
/* @__PURE__ */ jsx("circle", { cx: "19", cy: "12", r: "2", fill: color }),
|
|
234
|
+
/* @__PURE__ */ jsx("circle", { cx: "5", cy: "12", r: "2", fill: color })
|
|
235
|
+
]
|
|
236
|
+
}
|
|
237
|
+
);
|
|
238
|
+
var ErrorDotIcon = ({
|
|
239
|
+
color = "#FF5252"
|
|
240
|
+
}) => /* @__PURE__ */ jsxs(
|
|
241
|
+
"svg",
|
|
242
|
+
{
|
|
243
|
+
width: "24",
|
|
244
|
+
height: "24",
|
|
245
|
+
viewBox: "0 0 24 24",
|
|
246
|
+
fill: "none",
|
|
247
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
248
|
+
"aria-hidden": true,
|
|
249
|
+
children: [
|
|
250
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "11", fill: color }),
|
|
251
|
+
/* @__PURE__ */ jsx(
|
|
252
|
+
"path",
|
|
253
|
+
{
|
|
254
|
+
d: "M7.864 9.136A1.5 1.5 0 0 1 9.136 7.864L12 10.727 14.864 7.864A1.5 1.5 0 0 1 16.136 9.136L13.273 12 16.136 14.864a1.5 1.5 0 0 1-2.272 2.272L12 13.273 9.136 17.136A1.5 1.5 0 1 1 7.864 14.864L10.727 12 7.864 9.136Z",
|
|
255
|
+
fill: "#fff"
|
|
256
|
+
}
|
|
257
|
+
)
|
|
258
|
+
]
|
|
259
|
+
}
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
// src/components/notification/Notification.tsx
|
|
263
|
+
import "react";
|
|
264
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
9
265
|
var notificationStyles = `
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
266
|
+
.tp-noti-root{position:fixed;top:16px;right:16px;z-index:10000;display:flex;flex-direction:column;gap:10px}
|
|
267
|
+
.tp-noti-card{width:256px;padding:12px 16px;display:flex;gap:9px;align-items:flex-start;background:#ffffff;color:#111827;border-radius:16px;box-shadow:0 4px 24px rgba(0,0,0,.16);animation:tp-fade-in .2s ease}
|
|
268
|
+
.tp-noti-content{flex:1;min-width:0}
|
|
269
|
+
.tp-noti-title{font-size:15px;font-weight:700;line-height:20px;margin:0}
|
|
270
|
+
.tp-noti-text{margin-top:4px;color:#6b7280;font-size:13px;line-height:18px;word-break:break-word}
|
|
271
|
+
.tp-noti-icon{width:24px;height:24px;margin-top:2px;flex:0 0 auto}
|
|
16
272
|
`;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
if (
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
273
|
+
var stylesInjected = false;
|
|
274
|
+
function injectNotificationStyles() {
|
|
275
|
+
if (typeof document === "undefined" || stylesInjected) return;
|
|
276
|
+
const style = document.createElement("style");
|
|
277
|
+
style.id = "tonpay-notification-styles";
|
|
278
|
+
style.textContent = notificationStyles;
|
|
279
|
+
document.head.appendChild(style);
|
|
280
|
+
stylesInjected = true;
|
|
25
281
|
}
|
|
282
|
+
injectNotificationStyles();
|
|
26
283
|
var NotificationCard = ({
|
|
27
284
|
title,
|
|
28
285
|
text,
|
|
@@ -30,40 +287,1006 @@ var NotificationCard = ({
|
|
|
30
287
|
className,
|
|
31
288
|
style
|
|
32
289
|
}) => {
|
|
33
|
-
return /* @__PURE__ */
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
290
|
+
return /* @__PURE__ */ jsxs2(
|
|
291
|
+
"div",
|
|
292
|
+
{
|
|
293
|
+
className: ["tp-noti-card", className].filter(Boolean).join(" "),
|
|
294
|
+
style,
|
|
295
|
+
children: [
|
|
296
|
+
/* @__PURE__ */ jsxs2("div", { className: "tp-noti-content", children: [
|
|
297
|
+
/* @__PURE__ */ jsx2("h3", { className: "tp-noti-title", children: title }),
|
|
298
|
+
text && /* @__PURE__ */ jsx2("div", { className: "tp-noti-text", children: text })
|
|
299
|
+
] }),
|
|
300
|
+
icon && /* @__PURE__ */ jsx2("div", { className: "tp-noti-icon", children: icon })
|
|
301
|
+
]
|
|
302
|
+
}
|
|
303
|
+
);
|
|
40
304
|
};
|
|
41
|
-
var NotificationRoot = ({
|
|
42
|
-
|
|
305
|
+
var NotificationRoot = ({
|
|
306
|
+
children
|
|
307
|
+
}) => {
|
|
308
|
+
return /* @__PURE__ */ jsx2("div", { className: "tp-noti-root", children });
|
|
43
309
|
};
|
|
44
|
-
var ErrorDotIcon = ({ color = "#FF5252" }) => /* @__PURE__ */ jsxs("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": true, children: [
|
|
45
|
-
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "11", fill: color }),
|
|
46
|
-
/* @__PURE__ */ jsx("path", { d: "M7.864 9.136A1.5 1.5 0 0 1 9.136 7.864L12 10.727 14.864 7.864A1.5 1.5 0 0 1 16.136 9.136L13.273 12 16.136 14.864a1.5 1.5 0 0 1-2.272 2.272L12 13.273 9.136 17.136A1.5 1.5 0 1 1 7.864 14.864L10.727 12 7.864 9.136Z", fill: "#fff" })
|
|
47
|
-
] });
|
|
48
310
|
|
|
49
311
|
// src/components/notification/ErrorTransactionNotification.tsx
|
|
50
312
|
import "react";
|
|
51
|
-
import { jsx as
|
|
313
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
52
314
|
var ErrorTransactionNotification = ({ text, className, style }) => {
|
|
53
|
-
return /* @__PURE__ */
|
|
315
|
+
return /* @__PURE__ */ jsx3(
|
|
54
316
|
NotificationCard,
|
|
55
317
|
{
|
|
56
318
|
title: "Transaction cancelled",
|
|
57
319
|
text,
|
|
58
|
-
icon: /* @__PURE__ */
|
|
320
|
+
icon: /* @__PURE__ */ jsx3(ErrorDotIcon, {}),
|
|
59
321
|
className,
|
|
60
322
|
style
|
|
61
323
|
}
|
|
62
324
|
);
|
|
63
325
|
};
|
|
64
326
|
|
|
65
|
-
// src/components/
|
|
66
|
-
import
|
|
327
|
+
// src/components/payment-modal/PaymentModal.tsx
|
|
328
|
+
import "react";
|
|
329
|
+
import { useState as useState2, useEffect as useEffect2, useCallback as useCallback2, useRef as useRef2 } from "react";
|
|
330
|
+
|
|
331
|
+
// src/components/bottom-sheet/BottomSheet.tsx
|
|
332
|
+
import "react";
|
|
333
|
+
import { useEffect, useRef, useState, useCallback } from "react";
|
|
334
|
+
|
|
335
|
+
// src/components/bottom-sheet/BottomSheet.css
|
|
336
|
+
import styleInject from "#style-inject";
|
|
337
|
+
styleInject(".bottom-sheet-backdrop {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background-color: rgba(0, 0, 0, 0.5);\n backdrop-filter: blur(8px);\n -webkit-backdrop-filter: blur(8px);\n z-index: 1000;\n display: flex;\n align-items: flex-end;\n justify-content: center;\n animation: fadeIn 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n}\n.bottom-sheet-backdrop.closing {\n animation: fadeOut 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n}\n@keyframes fadeIn {\n from {\n opacity: 0;\n backdrop-filter: blur(0px);\n -webkit-backdrop-filter: blur(0px);\n }\n to {\n opacity: 1;\n backdrop-filter: blur(8px);\n -webkit-backdrop-filter: blur(8px);\n }\n}\n@keyframes fadeOut {\n from {\n opacity: 1;\n backdrop-filter: blur(8px);\n -webkit-backdrop-filter: blur(8px);\n }\n to {\n opacity: 0;\n backdrop-filter: blur(0px);\n -webkit-backdrop-filter: blur(0px);\n }\n}\n.bottom-sheet {\n position: relative;\n width: 100%;\n max-width: 100%;\n background-color: #ffffff;\n border-radius: 20px 20px 0 0;\n box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.15);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n touch-action: none;\n transition: height 0.3s cubic-bezier(0.4, 0, 0.2, 1), transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n will-change: height, transform;\n transform: translateY(0);\n}\n.bottom-sheet.dragging {\n transition: none;\n}\n.bottom-sheet.closing {\n animation: slideDown 0.3s cubic-bezier(0.4, 0, 0.2, 1) forwards;\n transition: height 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n}\n@keyframes slideDown {\n from {\n transform: translateY(0);\n }\n to {\n transform: translateY(100%);\n }\n}\n.bottom-sheet-handle-container {\n width: 100%;\n padding: 12px 0 8px;\n display: flex;\n justify-content: center;\n align-items: center;\n flex-shrink: 0;\n cursor: grab;\n}\n.bottom-sheet-handle-container:active {\n cursor: grabbing;\n}\n.bottom-sheet-handle {\n width: 80px;\n height: 4px;\n background-color: #d1d5db;\n border-radius: 2px;\n cursor: pointer;\n}\n.bottom-sheet-content {\n flex: 1;\n overflow-y: auto;\n overflow-x: hidden;\n -webkit-overflow-scrolling: touch;\n overscroll-behavior: contain;\n padding: 0;\n min-height: 0;\n}\n.bottom-sheet-content.scrolling {\n touch-action: pan-y;\n}\n@media (prefers-color-scheme: dark) {\n .bottom-sheet {\n background-color: #1C2633;\n box-shadow: 0 -4px 24px rgba(0, 0, 0, 0.4);\n }\n .bottom-sheet-handle {\n background-color: #4B5563;\n }\n}\n");
|
|
338
|
+
|
|
339
|
+
// src/components/bottom-sheet/BottomSheet.tsx
|
|
340
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
341
|
+
var BottomSheet = ({
|
|
342
|
+
isOpen,
|
|
343
|
+
onClose,
|
|
344
|
+
detents = [0.5, 0.9],
|
|
345
|
+
initialDetent = 0,
|
|
346
|
+
children,
|
|
347
|
+
className = "",
|
|
348
|
+
backdropClassName = "",
|
|
349
|
+
handleClassName = "",
|
|
350
|
+
contentClassName = "",
|
|
351
|
+
enableBackdropClose = true,
|
|
352
|
+
enableSwipeToClose = true,
|
|
353
|
+
maxHeight = "90vh",
|
|
354
|
+
minHeight = "20vh"
|
|
355
|
+
}) => {
|
|
356
|
+
const sheetRef = useRef(null);
|
|
357
|
+
const contentRef = useRef(null);
|
|
358
|
+
const [currentDetent, setCurrentDetent] = useState(initialDetent);
|
|
359
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
360
|
+
const [startY, setStartY] = useState(0);
|
|
361
|
+
const [currentY, setCurrentY] = useState(0);
|
|
362
|
+
const [isScrolling, setIsScrolling] = useState(false);
|
|
363
|
+
const [sheetHeight, setSheetHeight] = useState(0);
|
|
364
|
+
const [isClosing, setIsClosing] = useState(false);
|
|
365
|
+
const touchStartYRef = useRef(0);
|
|
366
|
+
const touchStartTimeRef = useRef(0);
|
|
367
|
+
const mouseStartTimeRef = useRef(0);
|
|
368
|
+
const scrollTopRef = useRef(0);
|
|
369
|
+
const canDragRef = useRef(true);
|
|
370
|
+
const closeTimeoutRef = useRef(null);
|
|
371
|
+
const openTimeoutRef = useRef(null);
|
|
372
|
+
const isClosingRef = useRef(false);
|
|
373
|
+
const isSnappingRef = useRef(false);
|
|
374
|
+
const targetDetentRef = useRef(null);
|
|
375
|
+
const getDetentValue = useCallback(
|
|
376
|
+
(index) => {
|
|
377
|
+
const clampedIndex = Math.max(0, Math.min(index, detents.length - 1));
|
|
378
|
+
return detents[clampedIndex];
|
|
379
|
+
},
|
|
380
|
+
[detents]
|
|
381
|
+
);
|
|
382
|
+
const calculateSheetHeight = useCallback(() => {
|
|
383
|
+
if (!sheetRef.current) return 0;
|
|
384
|
+
const viewportHeight = window.innerHeight;
|
|
385
|
+
const detentValue = getDetentValue(currentDetent);
|
|
386
|
+
return viewportHeight * detentValue;
|
|
387
|
+
}, [currentDetent, getDetentValue]);
|
|
388
|
+
useEffect(() => {
|
|
389
|
+
if (isOpen) {
|
|
390
|
+
if (isClosingRef.current) return;
|
|
391
|
+
isClosingRef.current = false;
|
|
392
|
+
setIsClosing(false);
|
|
393
|
+
setCurrentDetent(initialDetent);
|
|
394
|
+
if (openTimeoutRef.current) {
|
|
395
|
+
clearTimeout(openTimeoutRef.current);
|
|
396
|
+
}
|
|
397
|
+
setSheetHeight(0);
|
|
398
|
+
openTimeoutRef.current = setTimeout(() => {
|
|
399
|
+
const viewportHeight = window.innerHeight;
|
|
400
|
+
const detentValue = getDetentValue(initialDetent);
|
|
401
|
+
setSheetHeight(viewportHeight * detentValue);
|
|
402
|
+
openTimeoutRef.current = null;
|
|
403
|
+
}, 10);
|
|
404
|
+
} else {
|
|
405
|
+
if (openTimeoutRef.current) {
|
|
406
|
+
clearTimeout(openTimeoutRef.current);
|
|
407
|
+
openTimeoutRef.current = null;
|
|
408
|
+
}
|
|
409
|
+
setSheetHeight(0);
|
|
410
|
+
setCurrentDetent(initialDetent);
|
|
411
|
+
}
|
|
412
|
+
}, [isOpen, initialDetent, getDetentValue]);
|
|
413
|
+
const handleClose = useCallback(() => {
|
|
414
|
+
if (isClosing || isClosingRef.current) return;
|
|
415
|
+
isClosingRef.current = true;
|
|
416
|
+
setIsClosing(true);
|
|
417
|
+
setSheetHeight(0);
|
|
418
|
+
setCurrentDetent(initialDetent);
|
|
419
|
+
if (closeTimeoutRef.current) {
|
|
420
|
+
clearTimeout(closeTimeoutRef.current);
|
|
421
|
+
}
|
|
422
|
+
closeTimeoutRef.current = setTimeout(() => {
|
|
423
|
+
isClosingRef.current = false;
|
|
424
|
+
setIsClosing(false);
|
|
425
|
+
onClose();
|
|
426
|
+
}, 200);
|
|
427
|
+
}, [isClosing, onClose, initialDetent]);
|
|
428
|
+
useEffect(() => {
|
|
429
|
+
if (isOpen && !isDragging && !isSnappingRef.current && targetDetentRef.current === null) {
|
|
430
|
+
const expectedHeight = calculateSheetHeight();
|
|
431
|
+
const heightDiff = Math.abs(sheetHeight - expectedHeight);
|
|
432
|
+
if (heightDiff > 1) {
|
|
433
|
+
setSheetHeight(expectedHeight);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}, [isOpen, isDragging, currentDetent, sheetHeight, calculateSheetHeight]);
|
|
437
|
+
const findNearestDetent = useCallback(
|
|
438
|
+
(position) => {
|
|
439
|
+
const viewportHeight = window.innerHeight;
|
|
440
|
+
const normalizedPosition = position / viewportHeight;
|
|
441
|
+
let nearestIndex = 0;
|
|
442
|
+
let minDistance = Math.abs(normalizedPosition - detents[0]);
|
|
443
|
+
detents.forEach((detent, index) => {
|
|
444
|
+
const distance = Math.abs(normalizedPosition - detent);
|
|
445
|
+
if (distance < minDistance) {
|
|
446
|
+
minDistance = distance;
|
|
447
|
+
nearestIndex = index;
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
return nearestIndex;
|
|
451
|
+
},
|
|
452
|
+
[detents]
|
|
453
|
+
);
|
|
454
|
+
const snapToDetent = useCallback(
|
|
455
|
+
(detentIndex) => {
|
|
456
|
+
const clampedIndex = Math.max(0, Math.min(detentIndex, detents.length - 1));
|
|
457
|
+
isSnappingRef.current = true;
|
|
458
|
+
targetDetentRef.current = clampedIndex;
|
|
459
|
+
const viewportHeight = window.innerHeight;
|
|
460
|
+
const newHeight = viewportHeight * getDetentValue(clampedIndex);
|
|
461
|
+
setCurrentDetent(clampedIndex);
|
|
462
|
+
setSheetHeight(newHeight);
|
|
463
|
+
setTimeout(() => {
|
|
464
|
+
isSnappingRef.current = false;
|
|
465
|
+
targetDetentRef.current = null;
|
|
466
|
+
}, 200);
|
|
467
|
+
},
|
|
468
|
+
[detents, getDetentValue]
|
|
469
|
+
);
|
|
470
|
+
const handleTouchStart = useCallback(
|
|
471
|
+
(e) => {
|
|
472
|
+
if (!isOpen || isClosing) return;
|
|
473
|
+
const touch = e.touches[0];
|
|
474
|
+
touchStartYRef.current = touch.clientY;
|
|
475
|
+
touchStartTimeRef.current = Date.now();
|
|
476
|
+
setStartY(touch.clientY);
|
|
477
|
+
setCurrentY(touch.clientY);
|
|
478
|
+
if (contentRef.current) {
|
|
479
|
+
scrollTopRef.current = contentRef.current.scrollTop;
|
|
480
|
+
setIsScrolling(false);
|
|
481
|
+
canDragRef.current = scrollTopRef.current === 0;
|
|
482
|
+
} else {
|
|
483
|
+
canDragRef.current = true;
|
|
484
|
+
}
|
|
485
|
+
},
|
|
486
|
+
[isOpen, isClosing]
|
|
487
|
+
);
|
|
488
|
+
const calculateNewHeight = useCallback(
|
|
489
|
+
(touchY) => {
|
|
490
|
+
const viewportHeight = window.innerHeight;
|
|
491
|
+
const baseHeight = viewportHeight * getDetentValue(currentDetent);
|
|
492
|
+
const delta = touchStartYRef.current - touchY;
|
|
493
|
+
const maxDetentHeight = viewportHeight * Math.max(...detents);
|
|
494
|
+
const minDetentHeight = viewportHeight * Math.min(...detents);
|
|
495
|
+
const isAtMaxDetent = currentDetent === detents.length - 1;
|
|
496
|
+
const isAtMinDetent = currentDetent === 0;
|
|
497
|
+
const isDraggingDown = delta < 0;
|
|
498
|
+
let newHeight = baseHeight + delta;
|
|
499
|
+
if (isAtMaxDetent) {
|
|
500
|
+
newHeight = Math.max(0, Math.min(maxDetentHeight, newHeight));
|
|
501
|
+
} else if (isAtMinDetent) {
|
|
502
|
+
if (isDraggingDown && enableSwipeToClose) {
|
|
503
|
+
newHeight = Math.max(0, Math.min(maxDetentHeight, newHeight));
|
|
504
|
+
} else {
|
|
505
|
+
newHeight = Math.max(minDetentHeight, Math.min(maxDetentHeight, newHeight));
|
|
506
|
+
}
|
|
507
|
+
} else {
|
|
508
|
+
newHeight = Math.max(minDetentHeight, Math.min(maxDetentHeight, newHeight));
|
|
509
|
+
}
|
|
510
|
+
return newHeight;
|
|
511
|
+
},
|
|
512
|
+
[currentDetent, detents, enableSwipeToClose, getDetentValue]
|
|
513
|
+
);
|
|
514
|
+
const handleTouchMoveNative = useCallback(
|
|
515
|
+
(e) => {
|
|
516
|
+
if (!isOpen || isClosing) return;
|
|
517
|
+
const touch = e.touches[0];
|
|
518
|
+
if (!touch) return;
|
|
519
|
+
const deltaY = touch.clientY - touchStartYRef.current;
|
|
520
|
+
const currentScrollTop = contentRef.current?.scrollTop ?? 0;
|
|
521
|
+
const isContentScrollable = contentRef.current ? contentRef.current.scrollHeight > contentRef.current.clientHeight : false;
|
|
522
|
+
if (!contentRef.current) return;
|
|
523
|
+
const touchTarget = e.target;
|
|
524
|
+
const isTouchingContent = contentRef.current.contains(touchTarget);
|
|
525
|
+
const isTouchingHandle = touchTarget.closest(".bottom-sheet-handle-container") !== null;
|
|
526
|
+
if (isDragging) {
|
|
527
|
+
if (e.cancelable) e.preventDefault();
|
|
528
|
+
setCurrentY(touch.clientY);
|
|
529
|
+
setSheetHeight(calculateNewHeight(touch.clientY));
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
if (isTouchingHandle && Math.abs(deltaY) > 5) {
|
|
533
|
+
if (e.cancelable) e.preventDefault();
|
|
534
|
+
setIsDragging(true);
|
|
535
|
+
setIsScrolling(false);
|
|
536
|
+
setCurrentY(touch.clientY);
|
|
537
|
+
setSheetHeight(calculateNewHeight(touch.clientY));
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
if (isTouchingContent && !isDragging) {
|
|
541
|
+
if (currentScrollTop > 0) return;
|
|
542
|
+
if (deltaY < 0 && isContentScrollable) return;
|
|
543
|
+
if (currentScrollTop === 0 && deltaY > 0 && canDragRef.current && Math.abs(deltaY) > 5) {
|
|
544
|
+
if (e.cancelable) e.preventDefault();
|
|
545
|
+
setIsDragging(true);
|
|
546
|
+
setIsScrolling(false);
|
|
547
|
+
setCurrentY(touch.clientY);
|
|
548
|
+
setSheetHeight(calculateNewHeight(touch.clientY));
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
},
|
|
552
|
+
[isOpen, isDragging, isClosing, calculateNewHeight]
|
|
553
|
+
);
|
|
554
|
+
const handleTouchEnd = useCallback(
|
|
555
|
+
(e) => {
|
|
556
|
+
if (!isOpen || isClosing) return;
|
|
557
|
+
if (isScrolling) {
|
|
558
|
+
setIsScrolling(false);
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
if (!isDragging) return;
|
|
562
|
+
setIsDragging(false);
|
|
563
|
+
const viewportHeight = window.innerHeight;
|
|
564
|
+
const currentPosition = sheetHeight / viewportHeight;
|
|
565
|
+
const minDetentValue = Math.min(...detents);
|
|
566
|
+
const maxDetentValue = Math.max(...detents);
|
|
567
|
+
const swipeDuration = Date.now() - touchStartTimeRef.current;
|
|
568
|
+
const finalTouchY = e && "changedTouches" in e && e.changedTouches[0] ? e.changedTouches[0].clientY : currentY;
|
|
569
|
+
const swipeDistance = finalTouchY - touchStartYRef.current;
|
|
570
|
+
const swipeVelocity = Math.abs(swipeDistance) / Math.max(swipeDuration, 1);
|
|
571
|
+
const isAtMaxDetent = currentDetent === detents.length - 1;
|
|
572
|
+
const isAtMinDetent = currentDetent === 0;
|
|
573
|
+
const isSwipingDown = swipeDistance > 0;
|
|
574
|
+
const isSwipingUp = swipeDistance < 0;
|
|
575
|
+
if (isAtMaxDetent && isSwipingDown && enableSwipeToClose) {
|
|
576
|
+
if (swipeDistance > 50 || swipeVelocity > 0.3 || currentPosition < maxDetentValue * 0.7) {
|
|
577
|
+
handleClose();
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
if (isAtMinDetent && detents.length > 1) {
|
|
582
|
+
const nextDetentIndex = Math.min(1, detents.length - 1);
|
|
583
|
+
const nextDetentValue = detents[nextDetentIndex];
|
|
584
|
+
const midpoint = (minDetentValue + nextDetentValue) / 2;
|
|
585
|
+
const shouldGoToNextDetent = isSwipingUp && (Math.abs(swipeDistance) > 30 || swipeVelocity > 0.2) || currentPosition >= midpoint;
|
|
586
|
+
if (shouldGoToNextDetent) {
|
|
587
|
+
snapToDetent(nextDetentIndex);
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
const shouldClose = enableSwipeToClose && (currentPosition < minDetentValue * 0.5 || swipeDistance > 100 && swipeVelocity > 0.5 || sheetHeight < viewportHeight * 0.15);
|
|
592
|
+
if (shouldClose) {
|
|
593
|
+
handleClose();
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
const nearestDetentIndex = findNearestDetent(sheetHeight);
|
|
597
|
+
snapToDetent(nearestDetentIndex);
|
|
598
|
+
},
|
|
599
|
+
[
|
|
600
|
+
isOpen,
|
|
601
|
+
isDragging,
|
|
602
|
+
isClosing,
|
|
603
|
+
isScrolling,
|
|
604
|
+
sheetHeight,
|
|
605
|
+
currentY,
|
|
606
|
+
currentDetent,
|
|
607
|
+
detents,
|
|
608
|
+
enableSwipeToClose,
|
|
609
|
+
handleClose,
|
|
610
|
+
findNearestDetent,
|
|
611
|
+
snapToDetent
|
|
612
|
+
]
|
|
613
|
+
);
|
|
614
|
+
const handleMouseDown = useCallback(
|
|
615
|
+
(e) => {
|
|
616
|
+
if (!isOpen || isClosing) return;
|
|
617
|
+
mouseStartTimeRef.current = Date.now();
|
|
618
|
+
setStartY(e.clientY);
|
|
619
|
+
setCurrentY(e.clientY);
|
|
620
|
+
if (contentRef.current) {
|
|
621
|
+
scrollTopRef.current = contentRef.current.scrollTop;
|
|
622
|
+
setIsScrolling(false);
|
|
623
|
+
canDragRef.current = scrollTopRef.current === 0;
|
|
624
|
+
} else {
|
|
625
|
+
canDragRef.current = true;
|
|
626
|
+
}
|
|
627
|
+
},
|
|
628
|
+
[isOpen, isClosing]
|
|
629
|
+
);
|
|
630
|
+
const handleMouseMove = useCallback(
|
|
631
|
+
(e) => {
|
|
632
|
+
if (!isOpen || !isDragging || isClosing) return;
|
|
633
|
+
const viewportHeight = window.innerHeight;
|
|
634
|
+
const baseHeight = viewportHeight * getDetentValue(currentDetent);
|
|
635
|
+
const delta = startY - e.clientY;
|
|
636
|
+
const maxDetentHeight = viewportHeight * Math.max(...detents);
|
|
637
|
+
const minDetentHeight = viewportHeight * Math.min(...detents);
|
|
638
|
+
const isAtMaxDetent = currentDetent === detents.length - 1;
|
|
639
|
+
const isAtMinDetent = currentDetent === 0;
|
|
640
|
+
let newHeight = baseHeight + delta;
|
|
641
|
+
if (isAtMaxDetent) {
|
|
642
|
+
newHeight = Math.max(0, Math.min(maxDetentHeight, newHeight));
|
|
643
|
+
} else if (isAtMinDetent) {
|
|
644
|
+
newHeight = Math.max(minDetentHeight, Math.min(maxDetentHeight, newHeight));
|
|
645
|
+
} else {
|
|
646
|
+
newHeight = Math.max(minDetentHeight, Math.min(maxDetentHeight, newHeight));
|
|
647
|
+
}
|
|
648
|
+
setSheetHeight(newHeight);
|
|
649
|
+
setCurrentY(e.clientY);
|
|
650
|
+
},
|
|
651
|
+
[isOpen, isDragging, startY, currentDetent, detents, getDetentValue, isClosing]
|
|
652
|
+
);
|
|
653
|
+
const handleMouseUp = useCallback(() => {
|
|
654
|
+
if (!isOpen || !isDragging || isClosing) return;
|
|
655
|
+
setIsDragging(false);
|
|
656
|
+
const viewportHeight = window.innerHeight;
|
|
657
|
+
const currentPosition = sheetHeight / viewportHeight;
|
|
658
|
+
const minDetentValue = Math.min(...detents);
|
|
659
|
+
const maxDetentValue = Math.max(...detents);
|
|
660
|
+
const swipeDuration = Date.now() - mouseStartTimeRef.current;
|
|
661
|
+
const swipeDistance = currentY - startY;
|
|
662
|
+
const swipeVelocity = Math.abs(swipeDistance) / Math.max(swipeDuration, 1);
|
|
663
|
+
const isAtMaxDetent = currentDetent === detents.length - 1;
|
|
664
|
+
const isAtMinDetent = currentDetent === 0;
|
|
665
|
+
const isSwipingDown = swipeDistance > 0;
|
|
666
|
+
const isSwipingUp = swipeDistance < 0;
|
|
667
|
+
if (isAtMaxDetent && isSwipingDown && enableSwipeToClose) {
|
|
668
|
+
if (swipeDistance > 50 || swipeVelocity > 0.3 || currentPosition < maxDetentValue * 0.7) {
|
|
669
|
+
handleClose();
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
if (isAtMinDetent && detents.length > 1) {
|
|
674
|
+
const nextDetentIndex = Math.min(1, detents.length - 1);
|
|
675
|
+
const nextDetentValue = detents[nextDetentIndex];
|
|
676
|
+
const midpoint = (minDetentValue + nextDetentValue) / 2;
|
|
677
|
+
if (isSwipingUp && (swipeDistance < -30 || swipeVelocity > 0.2)) {
|
|
678
|
+
snapToDetent(nextDetentIndex);
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
if (currentPosition >= midpoint) {
|
|
682
|
+
snapToDetent(nextDetentIndex);
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
const shouldClose = enableSwipeToClose && (currentPosition < minDetentValue * 0.5 || swipeDistance > 100 && swipeVelocity > 0.5 || sheetHeight < viewportHeight * 0.15);
|
|
687
|
+
if (shouldClose) {
|
|
688
|
+
handleClose();
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
const nearestDetentIndex = findNearestDetent(sheetHeight);
|
|
692
|
+
snapToDetent(nearestDetentIndex);
|
|
693
|
+
}, [
|
|
694
|
+
isOpen,
|
|
695
|
+
isDragging,
|
|
696
|
+
isClosing,
|
|
697
|
+
sheetHeight,
|
|
698
|
+
currentY,
|
|
699
|
+
startY,
|
|
700
|
+
currentDetent,
|
|
701
|
+
detents,
|
|
702
|
+
enableSwipeToClose,
|
|
703
|
+
handleClose,
|
|
704
|
+
findNearestDetent,
|
|
705
|
+
snapToDetent
|
|
706
|
+
]);
|
|
707
|
+
useEffect(() => {
|
|
708
|
+
if (isDragging) {
|
|
709
|
+
document.addEventListener("mousemove", handleMouseMove);
|
|
710
|
+
document.addEventListener("mouseup", handleMouseUp);
|
|
711
|
+
return () => {
|
|
712
|
+
document.removeEventListener("mousemove", handleMouseMove);
|
|
713
|
+
document.removeEventListener("mouseup", handleMouseUp);
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
}, [isDragging, handleMouseMove, handleMouseUp]);
|
|
717
|
+
const handleTouchEndNative = useCallback(
|
|
718
|
+
(e) => {
|
|
719
|
+
handleTouchEnd(e);
|
|
720
|
+
},
|
|
721
|
+
[handleTouchEnd]
|
|
722
|
+
);
|
|
723
|
+
useEffect(() => {
|
|
724
|
+
const sheetElement = sheetRef.current;
|
|
725
|
+
if (!sheetElement || !isOpen) return;
|
|
726
|
+
sheetElement.addEventListener("touchmove", handleTouchMoveNative, {
|
|
727
|
+
passive: false
|
|
728
|
+
});
|
|
729
|
+
sheetElement.addEventListener("touchend", handleTouchEndNative, {
|
|
730
|
+
passive: false
|
|
731
|
+
});
|
|
732
|
+
return () => {
|
|
733
|
+
sheetElement.removeEventListener("touchmove", handleTouchMoveNative);
|
|
734
|
+
sheetElement.removeEventListener("touchend", handleTouchEndNative);
|
|
735
|
+
};
|
|
736
|
+
}, [isOpen, handleTouchMoveNative, handleTouchEndNative]);
|
|
737
|
+
useEffect(() => {
|
|
738
|
+
if (isOpen) {
|
|
739
|
+
document.body.style.overflow = "hidden";
|
|
740
|
+
} else {
|
|
741
|
+
document.body.style.overflow = "";
|
|
742
|
+
}
|
|
743
|
+
return () => {
|
|
744
|
+
document.body.style.overflow = "";
|
|
745
|
+
if (closeTimeoutRef.current) {
|
|
746
|
+
clearTimeout(closeTimeoutRef.current);
|
|
747
|
+
closeTimeoutRef.current = null;
|
|
748
|
+
}
|
|
749
|
+
if (openTimeoutRef.current) {
|
|
750
|
+
clearTimeout(openTimeoutRef.current);
|
|
751
|
+
openTimeoutRef.current = null;
|
|
752
|
+
}
|
|
753
|
+
isClosingRef.current = false;
|
|
754
|
+
};
|
|
755
|
+
}, [isOpen]);
|
|
756
|
+
if (!isOpen && !isClosing) return null;
|
|
757
|
+
const maxHeightValue = typeof maxHeight === "number" ? `${maxHeight}px` : maxHeight;
|
|
758
|
+
const minHeightValue = typeof minHeight === "number" ? `${minHeight}px` : minHeight;
|
|
759
|
+
const currentHeight = `${sheetHeight}px`;
|
|
760
|
+
const calculateBackdropOpacity = () => {
|
|
761
|
+
if (!isOpen) return 0.5;
|
|
762
|
+
const viewportHeight = window.innerHeight;
|
|
763
|
+
const maxDetentHeight = viewportHeight * Math.max(...detents);
|
|
764
|
+
if (maxDetentHeight === 0) return 0.5;
|
|
765
|
+
const progress = Math.max(0, Math.min(1, sheetHeight / maxDetentHeight));
|
|
766
|
+
return progress * 0.5;
|
|
767
|
+
};
|
|
768
|
+
const calculateBackdropBlur = () => {
|
|
769
|
+
if (!isOpen) return 8;
|
|
770
|
+
const viewportHeight = window.innerHeight;
|
|
771
|
+
const maxDetentHeight = viewportHeight * Math.max(...detents);
|
|
772
|
+
if (maxDetentHeight === 0) return 8;
|
|
773
|
+
const progress = Math.max(0, Math.min(1, sheetHeight / maxDetentHeight));
|
|
774
|
+
return progress * 8;
|
|
775
|
+
};
|
|
776
|
+
const backdropOpacity = calculateBackdropOpacity();
|
|
777
|
+
const backdropBlur = calculateBackdropBlur();
|
|
778
|
+
const backdropStyle = {
|
|
779
|
+
backgroundColor: `rgba(0, 0, 0, ${backdropOpacity})`,
|
|
780
|
+
backdropFilter: `blur(${backdropBlur}px)`,
|
|
781
|
+
WebkitBackdropFilter: `blur(${backdropBlur}px)`,
|
|
782
|
+
...isDragging ? {} : {
|
|
783
|
+
transition: "background-color 0.3s cubic-bezier(0.4, 0, 0.2, 1), backdrop-filter 0.3s cubic-bezier(0.4, 0, 0.2, 1)"
|
|
784
|
+
}
|
|
785
|
+
};
|
|
786
|
+
return /* @__PURE__ */ jsx4(
|
|
787
|
+
"div",
|
|
788
|
+
{
|
|
789
|
+
className: `bottom-sheet-backdrop ${backdropClassName} ${isClosing ? "closing" : ""}`,
|
|
790
|
+
onClick: enableBackdropClose ? handleClose : void 0,
|
|
791
|
+
style: backdropStyle,
|
|
792
|
+
children: /* @__PURE__ */ jsxs3(
|
|
793
|
+
"div",
|
|
794
|
+
{
|
|
795
|
+
ref: sheetRef,
|
|
796
|
+
className: `bottom-sheet ${className} ${isDragging ? "dragging" : ""} ${isClosing ? "closing" : ""}`,
|
|
797
|
+
style: {
|
|
798
|
+
height: currentHeight,
|
|
799
|
+
maxHeight: maxHeightValue,
|
|
800
|
+
minHeight: minHeightValue
|
|
801
|
+
},
|
|
802
|
+
onClick: (e) => e.stopPropagation(),
|
|
803
|
+
onTouchStart: handleTouchStart,
|
|
804
|
+
onMouseDown: handleMouseDown,
|
|
805
|
+
children: [
|
|
806
|
+
/* @__PURE__ */ jsx4("div", { className: "bottom-sheet-handle-container", children: /* @__PURE__ */ jsx4(
|
|
807
|
+
"div",
|
|
808
|
+
{
|
|
809
|
+
className: `bottom-sheet-handle ${handleClassName}`,
|
|
810
|
+
onClick: (e) => {
|
|
811
|
+
e.stopPropagation();
|
|
812
|
+
handleClose();
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
) }),
|
|
816
|
+
/* @__PURE__ */ jsx4(
|
|
817
|
+
"div",
|
|
818
|
+
{
|
|
819
|
+
ref: contentRef,
|
|
820
|
+
className: `bottom-sheet-content ${contentClassName} ${isScrolling ? "scrolling" : ""}`,
|
|
821
|
+
children
|
|
822
|
+
}
|
|
823
|
+
)
|
|
824
|
+
]
|
|
825
|
+
}
|
|
826
|
+
)
|
|
827
|
+
}
|
|
828
|
+
);
|
|
829
|
+
};
|
|
830
|
+
var BottomSheet_default = BottomSheet;
|
|
831
|
+
|
|
832
|
+
// src/components/payment-modal/PaymentModal.css
|
|
833
|
+
import styleInject2 from "#style-inject";
|
|
834
|
+
styleInject2('.pm-content {\n display: flex;\n flex-direction: column;\n align-items: center;\n width: 100%;\n font-family: "Inter", sans-serif;\n color: #000000;\n --pm-bg: #FFFFFF;\n --pm-text: #000000;\n --pm-text-secondary: #666666;\n --pm-text-muted: #8C8C8C;\n --pm-border: rgba(0, 0, 0, 0.1);\n --pm-order-bg: #E9F5FA;\n --pm-order-border: rgba(0, 100, 153, 0.03);\n --pm-order-text: #004062;\n --pm-hover-bg: #F9F9F9;\n --pm-iframe-bg: #f9f9f9;\n --pm-spinner-track: #E5E7EB;\n}\n@media (prefers-color-scheme: dark) {\n .pm-content {\n color: #FFFFFF;\n --pm-bg: #1f2937;\n --pm-text: #FFFFFF;\n --pm-text-secondary: #9CA3AF;\n --pm-text-muted: #6B7280;\n --pm-border: rgba(255, 255, 255, 0.1);\n --pm-order-bg: rgba(0, 152, 234, 0.15);\n --pm-order-border: rgba(0, 152, 234, 0.2);\n --pm-order-text: #7DD3FC;\n --pm-hover-bg: rgba(255, 255, 255, 0.05);\n --pm-iframe-bg: #111827;\n --pm-spinner-track: #374151;\n }\n}\n.pm-header {\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n align-items: center;\n width: 100%;\n padding: 16px;\n box-sizing: border-box;\n position: relative;\n}\n.pm-title {\n font-weight: 600;\n font-size: 18px;\n line-height: 22px;\n text-align: center;\n flex: 1;\n}\n.pm-close-btn,\n.pm-back-btn {\n background: none;\n border: none;\n cursor: pointer;\n padding: 4px;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--pm-text, #000000);\n opacity: 0.6;\n}\n.pm-close-btn:hover,\n.pm-back-btn:hover {\n opacity: 1;\n}\n.pm-body-main {\n display: flex;\n flex-direction: column;\n align-items: center;\n width: 100%;\n padding: 0 16px 24px;\n gap: 20px;\n box-sizing: border-box;\n}\n.pm-order-info {\n display: flex;\n flex-direction: row;\n justify-content: center;\n align-items: center;\n padding: 8px 13px;\n gap: 10px;\n background: var(--pm-order-bg, #E9F5FA);\n border: 1px solid var(--pm-order-border, rgba(0, 100, 153, 0.03));\n border-radius: 9px;\n margin-bottom: 10px;\n}\n.pm-order-text {\n font-size: 13px;\n line-height: 16px;\n color: var(--pm-order-text, #004062);\n text-align: center;\n}\n.pm-amount-container {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 10px;\n margin-bottom: 10px;\n}\n.pm-amount-label {\n font-weight: 500;\n font-size: 12px;\n line-height: 15px;\n color: var(--pm-text-muted, #8C8C8C);\n}\n.pm-amount-value {\n display: flex;\n align-items: center;\n gap: 8px;\n font-weight: 500;\n font-size: 20px;\n line-height: 24px;\n color: var(--pm-text, #000000);\n}\n.pm-actions {\n display: flex;\n flex-direction: column;\n gap: 8px;\n width: 100%;\n}\n.pm-btn {\n display: flex;\n flex-direction: row;\n justify-content: center;\n align-items: center;\n padding: 13px 10px;\n gap: 10px;\n width: 100%;\n height: 44px;\n border-radius: 8px;\n border: none;\n cursor: pointer;\n font-weight: 500;\n font-size: 16px;\n line-height: 19px;\n transition: opacity 0.2s;\n}\n.pm-btn:hover {\n opacity: 0.9;\n}\n.pm-btn-primary {\n background: #0098EA;\n color: #FFFFFF;\n}\n.pm-btn-black {\n background: #000000;\n color: #FFFFFF;\n}\n.pm-footer {\n display: flex;\n flex-direction: row;\n justify-content: center;\n align-items: center;\n gap: 8px;\n margin-top: 10px;\n font-size: 14px;\n line-height: 17px;\n}\n.pm-footer-text {\n color: var(--pm-text-muted, #808080);\n}\n.pm-footer-link {\n color: #0098EA;\n cursor: pointer;\n text-decoration: none;\n}\n.pm-footer-link:hover {\n text-decoration: underline;\n}\n.pm-providers-list {\n display: flex;\n flex-direction: column;\n width: 100%;\n padding: 0;\n}\n.pm-provider-item {\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n align-items: center;\n padding: 13px 16px 13px 32px;\n width: 100%;\n height: 52px;\n background: var(--pm-bg, #FFFFFF);\n border: none;\n border-bottom: 1px solid var(--pm-border, rgba(0, 0, 0, 0.1));\n cursor: pointer;\n box-sizing: border-box;\n position: relative;\n}\n.pm-provider-item:hover {\n background-color: var(--pm-hover-bg, #F9F9F9);\n}\n.pm-provider-item.selected {\n background: var(--pm-hover-bg, #F9F9F9);\n}\n.pm-provider-left {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n.pm-provider-icon {\n width: 32px;\n height: 32px;\n border-radius: 8px;\n display: flex;\n align-items: center;\n justify-content: center;\n overflow: hidden;\n}\n.pm-provider-name {\n font-size: 16px;\n color: var(--pm-text, #000000);\n}\n.pm-check-icon {\n color: #0098EA;\n opacity: 0;\n}\n.pm-provider-item.selected .pm-check-icon {\n opacity: 1;\n}\n.pm-desktop-overlay {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.44);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1000;\n animation: fadeIn 0.2s ease-out;\n}\n.pm-desktop-modal {\n width: 100%;\n max-width: 414px;\n margin: 16px;\n background: var(--pm-bg, #FFFFFF);\n border-radius: 16px;\n box-shadow: 0px 4px 24px rgba(0, 0, 0, 0.25);\n overflow: hidden;\n position: relative;\n animation: scaleIn 0.2s ease-out;\n}\n@media (prefers-color-scheme: dark) {\n .pm-desktop-modal {\n box-shadow: 0px 4px 32px rgba(0, 0, 0, 0.5);\n }\n}\n@keyframes scaleIn {\n from {\n transform: scale(0.95);\n opacity: 0;\n }\n to {\n transform: scale(1);\n opacity: 1;\n }\n}\n.pm-iframe-container {\n width: 100%;\n height: 100%;\n min-height: 400px;\n display: flex;\n flex-direction: column;\n background: var(--pm-iframe-bg, #f9f9f9);\n position: relative;\n}\n.pm-iframe-container iframe {\n flex: 1;\n width: 100%;\n height: 100%;\n border: none;\n display: block;\n}\n.pm-menu-dropdown {\n position: absolute;\n top: 100%;\n right: 0;\n background: var(--pm-bg, #FFFFFF);\n border: 1px solid var(--pm-border, rgba(0, 0, 0, 0.1));\n border-radius: 8px;\n box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.15);\n z-index: 1001;\n min-width: 150px;\n overflow: hidden;\n}\n@media (prefers-color-scheme: dark) {\n .pm-menu-dropdown {\n box-shadow: 0px 4px 16px rgba(0, 0, 0, 0.4);\n }\n}\n.pm-menu-item {\n padding: 10px 16px;\n font-size: 14px;\n cursor: pointer;\n color: var(--pm-text, #000000);\n}\n.pm-menu-item:hover {\n background: var(--pm-hover-bg, #F9F9F9);\n}\n.pm-menu-item.danger {\n color: #E74C3C;\n}\n.pm-menu-item.disabled {\n cursor: default;\n color: var(--pm-text-muted, #8C8C8C);\n}\n.pm-menu-item.disabled:hover {\n background: transparent;\n}\n.icon-moonpay {\n background: #7D00FF;\n color: white;\n}\n.icon-onramper {\n background: #000000;\n color: white;\n}\n.icon-transak {\n background:\n linear-gradient(\n 120deg,\n #348BED 22.91%,\n #2B80E8 36.09%,\n #1461DB 60.25%,\n #0E57D7 66.11%);\n color: white;\n}\n.icon-mercurio {\n background: #000000;\n color: white;\n}\n.pm-success-container {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 60px 24px;\n text-align: center;\n}\n.pm-success-icon {\n width: 64px;\n height: 64px;\n background: #0098EA;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-bottom: 20px;\n color: white;\n}\n.pm-success-icon svg {\n width: 32px;\n height: 32px;\n}\n.pm-success-title {\n font-size: 24px;\n font-weight: 600;\n color: var(--pm-text, #000000);\n margin: 0 0 8px;\n}\n.pm-success-text {\n font-size: 16px;\n color: var(--pm-text-secondary, #666666);\n margin: 0;\n}\n.pm-error-container {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 60px 24px;\n text-align: center;\n}\n.pm-error-icon {\n width: 64px;\n height: 64px;\n background: #E74C3C;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-bottom: 20px;\n color: white;\n}\n.pm-error-icon svg {\n width: 32px;\n height: 32px;\n}\n.pm-error-title {\n font-size: 24px;\n font-weight: 600;\n color: var(--pm-text, #000000);\n margin: 0 0 8px;\n}\n.pm-error-text {\n font-size: 16px;\n color: var(--pm-text-secondary, #666666);\n margin: 0 0 24px;\n max-width: 300px;\n}\n.pm-error-actions {\n display: flex;\n flex-direction: column;\n gap: 12px;\n width: 100%;\n max-width: 280px;\n}\n.pm-error-inline {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n padding: 40px 20px;\n text-align: center;\n color: #E74C3C;\n}\n.pm-error-inline svg {\n width: 48px;\n height: 48px;\n margin-bottom: 16px;\n}\n.pm-error-inline p {\n margin: 0 0 20px;\n color: var(--pm-text-secondary, #666666);\n font-size: 14px;\n}\n.pm-btn-outline {\n background: transparent;\n color: #0098EA;\n border: 1px solid #0098EA;\n}\n.pm-btn-outline:hover {\n background: rgba(0, 152, 234, 0.08);\n}\n.pm-loading-container {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: var(--pm-text-muted, #6b7280);\n}\n.pm-loading-container p {\n margin: 16px 0 0;\n font-size: 14px;\n}\n.pm-loading-overlay {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--pm-iframe-bg, #f9f9f9);\n z-index: 1;\n}\n.pm-spinner {\n width: 32px;\n height: 32px;\n border: 3px solid var(--pm-spinner-track, #E5E7EB);\n border-top-color: #0098EA;\n border-radius: 50%;\n animation: pm-spin 0.8s linear infinite;\n}\n@keyframes pm-spin {\n to {\n transform: rotate(360deg);\n }\n}\n');
|
|
835
|
+
|
|
836
|
+
// src/components/payment-modal/PaymentModal.tsx
|
|
837
|
+
import { Fragment, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
838
|
+
var PROVIDER = { id: "moonpay", name: "Moonpay", iconClass: "icon-moonpay" };
|
|
839
|
+
var IFRAME_LOAD_TIMEOUT = 3e4;
|
|
840
|
+
var PaymentModal = ({
|
|
841
|
+
isOpen,
|
|
842
|
+
onClose,
|
|
843
|
+
onPayWithCrypto,
|
|
844
|
+
amount = "0.1",
|
|
845
|
+
currency = "TON",
|
|
846
|
+
itemTitle = "Pay to Guitar from Demo Store",
|
|
847
|
+
walletAddress,
|
|
848
|
+
onDisconnect,
|
|
849
|
+
fetchOnRampLink,
|
|
850
|
+
onRampAvailable = false,
|
|
851
|
+
onPaymentSuccess
|
|
852
|
+
}) => {
|
|
853
|
+
const [view, setView] = useState2("main");
|
|
854
|
+
const [isMobile, setIsMobile] = useState2(false);
|
|
855
|
+
const [showMenu, setShowMenu] = useState2(false);
|
|
856
|
+
const [onRampLink, setOnRampLink] = useState2(null);
|
|
857
|
+
const [onRampError, setOnRampError] = useState2(null);
|
|
858
|
+
const [isOnRampLoading, setIsOnRampLoading] = useState2(false);
|
|
859
|
+
const [iframeLoaded, setIframeLoaded] = useState2(false);
|
|
860
|
+
const [iframeError, setIframeError] = useState2(null);
|
|
861
|
+
const [sheetDetent, setSheetDetent] = useState2([0.55]);
|
|
862
|
+
const iframeTimeoutRef = useRef2(null);
|
|
863
|
+
const fetchStartedRef = useRef2(false);
|
|
864
|
+
const contentRef = useRef2(null);
|
|
865
|
+
const handlePaymentSuccess = useCallback2(() => {
|
|
866
|
+
if (iframeTimeoutRef.current) {
|
|
867
|
+
clearTimeout(iframeTimeoutRef.current);
|
|
868
|
+
}
|
|
869
|
+
setView("success");
|
|
870
|
+
onPaymentSuccess?.();
|
|
871
|
+
setTimeout(() => {
|
|
872
|
+
onClose();
|
|
873
|
+
}, 2e3);
|
|
874
|
+
}, [onClose, onPaymentSuccess]);
|
|
875
|
+
const handlePaymentError = useCallback2((errorMessage) => {
|
|
876
|
+
if (iframeTimeoutRef.current) {
|
|
877
|
+
clearTimeout(iframeTimeoutRef.current);
|
|
878
|
+
}
|
|
879
|
+
setIframeError(errorMessage);
|
|
880
|
+
setView("error");
|
|
881
|
+
}, []);
|
|
882
|
+
const handleRetry = useCallback2(() => {
|
|
883
|
+
setOnRampLink(null);
|
|
884
|
+
setOnRampError(null);
|
|
885
|
+
setIframeError(null);
|
|
886
|
+
setIframeLoaded(false);
|
|
887
|
+
fetchStartedRef.current = false;
|
|
888
|
+
setView("card");
|
|
889
|
+
}, []);
|
|
890
|
+
useEffect2(() => {
|
|
891
|
+
const handleMessage = (event) => {
|
|
892
|
+
if (event.data?.type === "TONPAY_PAYMENT_SUCCESS") {
|
|
893
|
+
handlePaymentSuccess();
|
|
894
|
+
}
|
|
895
|
+
if (event.data?.type === "TONPAY_PAYMENT_ERROR") {
|
|
896
|
+
const payload = event.data.payload;
|
|
897
|
+
handlePaymentError(payload?.message || "Payment failed");
|
|
898
|
+
}
|
|
899
|
+
if (event.data?.type === "TONPAY_IFRAME_LOADED") {
|
|
900
|
+
setIframeLoaded(true);
|
|
901
|
+
if (iframeTimeoutRef.current) {
|
|
902
|
+
clearTimeout(iframeTimeoutRef.current);
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
if (event.data?.type === "TONPAY_MOONPAY_EVENT") {
|
|
906
|
+
const payload = event.data.payload;
|
|
907
|
+
if (payload?.type === "onTransactionCompleted" || payload?.eventName === "transactionCompleted") {
|
|
908
|
+
handlePaymentSuccess();
|
|
909
|
+
}
|
|
910
|
+
if (payload?.type === "onTransactionFailed" || payload?.eventName === "transactionFailed") {
|
|
911
|
+
handlePaymentError(payload?.message || "Transaction failed");
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
};
|
|
915
|
+
window.addEventListener("message", handleMessage);
|
|
916
|
+
return () => window.removeEventListener("message", handleMessage);
|
|
917
|
+
}, [handlePaymentSuccess, handlePaymentError]);
|
|
918
|
+
useEffect2(() => {
|
|
919
|
+
if (view !== "card") {
|
|
920
|
+
fetchStartedRef.current = false;
|
|
921
|
+
}
|
|
922
|
+
}, [view]);
|
|
923
|
+
useEffect2(() => {
|
|
924
|
+
if (view === "card" && !onRampLink && !onRampError && fetchOnRampLink && !fetchStartedRef.current) {
|
|
925
|
+
fetchStartedRef.current = true;
|
|
926
|
+
setIsOnRampLoading(true);
|
|
927
|
+
setOnRampError(null);
|
|
928
|
+
setIframeLoaded(false);
|
|
929
|
+
setIframeError(null);
|
|
930
|
+
fetchOnRampLink(PROVIDER.id).then((link) => {
|
|
931
|
+
setOnRampLink(link);
|
|
932
|
+
iframeTimeoutRef.current = setTimeout(() => {
|
|
933
|
+
if (!iframeLoaded) {
|
|
934
|
+
handlePaymentError(
|
|
935
|
+
"Payment service is taking too long to load. Please try again."
|
|
936
|
+
);
|
|
937
|
+
}
|
|
938
|
+
}, IFRAME_LOAD_TIMEOUT);
|
|
939
|
+
}).catch((err) => {
|
|
940
|
+
const errorMsg = err?.message || "Failed to initialize payment";
|
|
941
|
+
setOnRampError(errorMsg);
|
|
942
|
+
fetchStartedRef.current = false;
|
|
943
|
+
}).finally(() => setIsOnRampLoading(false));
|
|
944
|
+
}
|
|
945
|
+
return () => {
|
|
946
|
+
if (iframeTimeoutRef.current) {
|
|
947
|
+
clearTimeout(iframeTimeoutRef.current);
|
|
948
|
+
}
|
|
949
|
+
};
|
|
950
|
+
}, [view, onRampLink, onRampError, fetchOnRampLink, iframeLoaded, handlePaymentError]);
|
|
951
|
+
useEffect2(() => {
|
|
952
|
+
const checkMobile = () => {
|
|
953
|
+
setIsMobile(window.innerWidth < 768);
|
|
954
|
+
};
|
|
955
|
+
checkMobile();
|
|
956
|
+
window.addEventListener("resize", checkMobile);
|
|
957
|
+
return () => window.removeEventListener("resize", checkMobile);
|
|
958
|
+
}, []);
|
|
959
|
+
useEffect2(() => {
|
|
960
|
+
if (isOpen) {
|
|
961
|
+
setView("main");
|
|
962
|
+
setShowMenu(false);
|
|
963
|
+
setOnRampLink(null);
|
|
964
|
+
setOnRampError(null);
|
|
965
|
+
setIframeLoaded(false);
|
|
966
|
+
setIframeError(null);
|
|
967
|
+
if (iframeTimeoutRef.current) {
|
|
968
|
+
clearTimeout(iframeTimeoutRef.current);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
}, [isOpen]);
|
|
972
|
+
useEffect2(() => {
|
|
973
|
+
if (!isMobile || !isOpen) return;
|
|
974
|
+
const updateHeight = () => {
|
|
975
|
+
if (contentRef.current) {
|
|
976
|
+
const height = contentRef.current.scrollHeight;
|
|
977
|
+
const windowHeight = window.innerHeight;
|
|
978
|
+
const detent = Math.min((height + 40) / windowHeight, 0.95);
|
|
979
|
+
setSheetDetent([detent]);
|
|
980
|
+
}
|
|
981
|
+
};
|
|
982
|
+
setTimeout(updateHeight, 50);
|
|
983
|
+
const observer = new ResizeObserver(updateHeight);
|
|
984
|
+
if (contentRef.current) {
|
|
985
|
+
observer.observe(contentRef.current);
|
|
986
|
+
}
|
|
987
|
+
return () => observer.disconnect();
|
|
988
|
+
}, [view, isMobile, isOpen]);
|
|
989
|
+
const handleBack = () => setView("main");
|
|
990
|
+
const handleIframeLoad = () => {
|
|
991
|
+
setIframeLoaded(true);
|
|
992
|
+
if (iframeTimeoutRef.current) {
|
|
993
|
+
clearTimeout(iframeTimeoutRef.current);
|
|
994
|
+
}
|
|
995
|
+
};
|
|
996
|
+
const handleIframeError = () => {
|
|
997
|
+
handlePaymentError("Failed to load payment service. Please try again.");
|
|
998
|
+
};
|
|
999
|
+
const renderHeader = () => /* @__PURE__ */ jsxs4("div", { className: "pm-header", children: [
|
|
1000
|
+
view !== "main" ? /* @__PURE__ */ jsx5("button", { className: "pm-back-btn", onClick: handleBack, children: /* @__PURE__ */ jsx5(BackIcon, {}) }) : /* @__PURE__ */ jsx5("div", { style: { width: 32 } }),
|
|
1001
|
+
/* @__PURE__ */ jsx5("div", { className: "pm-title", children: "New Purchase" }),
|
|
1002
|
+
/* @__PURE__ */ jsxs4("div", { style: { display: "flex", gap: 8 }, children: [
|
|
1003
|
+
walletAddress && view === "main" && /* @__PURE__ */ jsxs4("div", { style: { position: "relative" }, children: [
|
|
1004
|
+
/* @__PURE__ */ jsx5("button", { className: "pm-close-btn", onClick: () => setShowMenu(!showMenu), children: /* @__PURE__ */ jsx5(MenuIcon, {}) }),
|
|
1005
|
+
showMenu && /* @__PURE__ */ jsxs4("div", { className: "pm-menu-dropdown", children: [
|
|
1006
|
+
/* @__PURE__ */ jsxs4("div", { className: "pm-menu-item disabled", children: [
|
|
1007
|
+
walletAddress.slice(0, 4),
|
|
1008
|
+
"...",
|
|
1009
|
+
walletAddress.slice(-4)
|
|
1010
|
+
] }),
|
|
1011
|
+
/* @__PURE__ */ jsx5(
|
|
1012
|
+
"div",
|
|
1013
|
+
{
|
|
1014
|
+
className: "pm-menu-item danger",
|
|
1015
|
+
onClick: () => {
|
|
1016
|
+
onDisconnect?.();
|
|
1017
|
+
setShowMenu(false);
|
|
1018
|
+
},
|
|
1019
|
+
children: "Disconnect"
|
|
1020
|
+
}
|
|
1021
|
+
)
|
|
1022
|
+
] })
|
|
1023
|
+
] }),
|
|
1024
|
+
/* @__PURE__ */ jsx5("button", { className: "pm-close-btn", onClick: onClose, children: /* @__PURE__ */ jsx5(CloseIcon, {}) })
|
|
1025
|
+
] })
|
|
1026
|
+
] });
|
|
1027
|
+
const renderMainView = () => /* @__PURE__ */ jsxs4("div", { className: "pm-body-main", children: [
|
|
1028
|
+
/* @__PURE__ */ jsx5("div", { className: "pm-order-info", children: /* @__PURE__ */ jsx5("span", { className: "pm-order-text", children: itemTitle }) }),
|
|
1029
|
+
/* @__PURE__ */ jsxs4("div", { className: "pm-amount-container", children: [
|
|
1030
|
+
/* @__PURE__ */ jsx5("span", { className: "pm-amount-label", children: "Amount" }),
|
|
1031
|
+
/* @__PURE__ */ jsxs4("div", { className: "pm-amount-value", children: [
|
|
1032
|
+
amount,
|
|
1033
|
+
" ",
|
|
1034
|
+
currency,
|
|
1035
|
+
" ",
|
|
1036
|
+
/* @__PURE__ */ jsx5(TonIconBlue, {})
|
|
1037
|
+
] })
|
|
1038
|
+
] }),
|
|
1039
|
+
/* @__PURE__ */ jsxs4("div", { className: "pm-actions", children: [
|
|
1040
|
+
/* @__PURE__ */ jsx5("button", { className: "pm-btn pm-btn-primary", onClick: onPayWithCrypto, children: "Pay with Crypto" }),
|
|
1041
|
+
onRampAvailable && /* @__PURE__ */ jsx5("button", { className: "pm-btn pm-btn-black", onClick: () => setView("card"), children: "Pay with Card" })
|
|
1042
|
+
] }),
|
|
1043
|
+
onRampAvailable && /* @__PURE__ */ jsx5("div", { className: "pm-footer", children: /* @__PURE__ */ jsxs4("span", { className: "pm-footer-text", children: [
|
|
1044
|
+
"Processed by ",
|
|
1045
|
+
PROVIDER.name
|
|
1046
|
+
] }) })
|
|
1047
|
+
] });
|
|
1048
|
+
const renderCardView = () => /* @__PURE__ */ jsxs4("div", { className: "pm-iframe-container", children: [
|
|
1049
|
+
isOnRampLoading && /* @__PURE__ */ jsxs4("div", { className: "pm-loading-container", children: [
|
|
1050
|
+
/* @__PURE__ */ jsx5("div", { className: "pm-spinner" }),
|
|
1051
|
+
/* @__PURE__ */ jsxs4("p", { children: [
|
|
1052
|
+
"Loading ",
|
|
1053
|
+
PROVIDER.name,
|
|
1054
|
+
"..."
|
|
1055
|
+
] })
|
|
1056
|
+
] }),
|
|
1057
|
+
onRampError && /* @__PURE__ */ jsxs4("div", { className: "pm-error-inline", children: [
|
|
1058
|
+
/* @__PURE__ */ jsx5(ErrorIcon, {}),
|
|
1059
|
+
/* @__PURE__ */ jsx5("p", { children: onRampError }),
|
|
1060
|
+
/* @__PURE__ */ jsx5("button", { className: "pm-btn pm-btn-outline", onClick: handleRetry, children: "Try Again" })
|
|
1061
|
+
] }),
|
|
1062
|
+
onRampLink && !isOnRampLoading && !onRampError && /* @__PURE__ */ jsxs4(Fragment, { children: [
|
|
1063
|
+
!iframeLoaded && /* @__PURE__ */ jsx5("div", { className: "pm-loading-overlay", children: /* @__PURE__ */ jsx5("div", { className: "pm-spinner" }) }),
|
|
1064
|
+
/* @__PURE__ */ jsx5(
|
|
1065
|
+
"iframe",
|
|
1066
|
+
{
|
|
1067
|
+
src: onRampLink,
|
|
1068
|
+
title: PROVIDER.name,
|
|
1069
|
+
width: "100%",
|
|
1070
|
+
height: "100%",
|
|
1071
|
+
frameBorder: "0",
|
|
1072
|
+
allow: "accelerometer; autoplay; camera; gyroscope; payment",
|
|
1073
|
+
onLoad: handleIframeLoad,
|
|
1074
|
+
onError: handleIframeError,
|
|
1075
|
+
style: { opacity: iframeLoaded ? 1 : 0 }
|
|
1076
|
+
}
|
|
1077
|
+
)
|
|
1078
|
+
] })
|
|
1079
|
+
] });
|
|
1080
|
+
const renderSuccessView = () => /* @__PURE__ */ jsxs4("div", { className: "pm-success-container", children: [
|
|
1081
|
+
/* @__PURE__ */ jsx5("div", { className: "pm-success-icon", children: /* @__PURE__ */ jsx5(CheckIcon, {}) }),
|
|
1082
|
+
/* @__PURE__ */ jsx5("h2", { className: "pm-success-title", children: "Payment Successful" }),
|
|
1083
|
+
/* @__PURE__ */ jsx5("p", { className: "pm-success-text", children: "Your purchase is being processed" })
|
|
1084
|
+
] });
|
|
1085
|
+
const renderErrorView = () => /* @__PURE__ */ jsxs4("div", { className: "pm-error-container", children: [
|
|
1086
|
+
/* @__PURE__ */ jsx5("div", { className: "pm-error-icon", children: /* @__PURE__ */ jsx5(ErrorIcon, {}) }),
|
|
1087
|
+
/* @__PURE__ */ jsx5("h2", { className: "pm-error-title", children: "Payment Failed" }),
|
|
1088
|
+
/* @__PURE__ */ jsx5("p", { className: "pm-error-text", children: iframeError || "Something went wrong" }),
|
|
1089
|
+
/* @__PURE__ */ jsxs4("div", { className: "pm-error-actions", children: [
|
|
1090
|
+
/* @__PURE__ */ jsx5("button", { className: "pm-btn pm-btn-primary", onClick: handleRetry, children: "Try Again" }),
|
|
1091
|
+
/* @__PURE__ */ jsx5("button", { className: "pm-btn pm-btn-outline", onClick: onPayWithCrypto, children: "Pay with Crypto Instead" })
|
|
1092
|
+
] })
|
|
1093
|
+
] });
|
|
1094
|
+
const renderContent = () => /* @__PURE__ */ jsxs4("div", { className: "pm-content", children: [
|
|
1095
|
+
view !== "success" && view !== "error" && renderHeader(),
|
|
1096
|
+
view === "main" && renderMainView(),
|
|
1097
|
+
view === "card" && renderCardView(),
|
|
1098
|
+
view === "success" && renderSuccessView(),
|
|
1099
|
+
view === "error" && renderErrorView()
|
|
1100
|
+
] });
|
|
1101
|
+
if (isMobile) {
|
|
1102
|
+
return /* @__PURE__ */ jsx5(
|
|
1103
|
+
BottomSheet_default,
|
|
1104
|
+
{
|
|
1105
|
+
isOpen,
|
|
1106
|
+
onClose,
|
|
1107
|
+
detents: sheetDetent,
|
|
1108
|
+
initialDetent: 0,
|
|
1109
|
+
enableSwipeToClose: view === "main",
|
|
1110
|
+
children: /* @__PURE__ */ jsx5("div", { ref: contentRef, children: renderContent() })
|
|
1111
|
+
}
|
|
1112
|
+
);
|
|
1113
|
+
}
|
|
1114
|
+
if (!isOpen) return null;
|
|
1115
|
+
return /* @__PURE__ */ jsx5("div", { className: "pm-desktop-overlay", onClick: onClose, children: /* @__PURE__ */ jsx5("div", { className: "pm-desktop-modal", onClick: (e) => e.stopPropagation(), children: renderContent() }) });
|
|
1116
|
+
};
|
|
1117
|
+
|
|
1118
|
+
// src/hooks/useMoonPayIframe.ts
|
|
1119
|
+
import * as React6 from "react";
|
|
1120
|
+
|
|
1121
|
+
// ../api/src/common/const.ts
|
|
1122
|
+
var BASE_URL = "https://tonpay.tech";
|
|
1123
|
+
var TESTNET_BASE_URL = "https://testnet.tonpay.tech";
|
|
1124
|
+
|
|
1125
|
+
// ../api/src/common/get-base-url.ts
|
|
1126
|
+
var getBaseUrl = (chain) => {
|
|
1127
|
+
if (typeof process !== "undefined" && process.env && process.env.TONPAY_BASE_URL) {
|
|
1128
|
+
return process.env.TONPAY_BASE_URL;
|
|
1129
|
+
}
|
|
1130
|
+
if (!chain || chain === "mainnet") {
|
|
1131
|
+
return BASE_URL;
|
|
1132
|
+
}
|
|
1133
|
+
return TESTNET_BASE_URL;
|
|
1134
|
+
};
|
|
1135
|
+
|
|
1136
|
+
// ../api/src/create-moonpay-transfer/create-moonpay-transfer.ts
|
|
1137
|
+
var createMoonpayTransfer = async (params, options) => {
|
|
1138
|
+
if (!options?.apiKey) {
|
|
1139
|
+
throw new Error("API key is required for MoonPay transfers");
|
|
1140
|
+
}
|
|
1141
|
+
const baseUrl = getBaseUrl(options.chain);
|
|
1142
|
+
const headers = {
|
|
1143
|
+
"Content-Type": "application/json",
|
|
1144
|
+
"x-api-key": options.apiKey
|
|
1145
|
+
};
|
|
1146
|
+
const response = await fetch(
|
|
1147
|
+
`${baseUrl}/api/merchant/v1/create-moonpay-transfer`,
|
|
1148
|
+
{
|
|
1149
|
+
method: "POST",
|
|
1150
|
+
body: JSON.stringify(params),
|
|
1151
|
+
headers
|
|
1152
|
+
}
|
|
1153
|
+
);
|
|
1154
|
+
if (!response.ok) {
|
|
1155
|
+
const errorText = await response.text();
|
|
1156
|
+
throw new Error(`Failed to create MoonPay transfer: ${errorText}`, {
|
|
1157
|
+
cause: response.statusText
|
|
1158
|
+
});
|
|
1159
|
+
}
|
|
1160
|
+
const data = await response.json();
|
|
1161
|
+
if (data.link && data.link.startsWith("/")) {
|
|
1162
|
+
data.link = `${baseUrl}${data.link}`;
|
|
1163
|
+
}
|
|
1164
|
+
return data;
|
|
1165
|
+
};
|
|
1166
|
+
|
|
1167
|
+
// ../api/src/check-moonpay-geo/check-moonpay-geo.ts
|
|
1168
|
+
var checkMoonpayGeo = async (params, options) => {
|
|
1169
|
+
const baseUrl = getBaseUrl(options?.chain);
|
|
1170
|
+
const headers = {
|
|
1171
|
+
"Content-Type": "application/json",
|
|
1172
|
+
...options?.apiKey ? { "x-api-key": options.apiKey } : {}
|
|
1173
|
+
};
|
|
1174
|
+
const response = await fetch(
|
|
1175
|
+
`${baseUrl}/api/external/moonpay/check-geo?ipAddress=${encodeURIComponent(params.ipAddress)}`,
|
|
1176
|
+
{
|
|
1177
|
+
method: "GET",
|
|
1178
|
+
headers
|
|
1179
|
+
}
|
|
1180
|
+
);
|
|
1181
|
+
if (!response.ok) {
|
|
1182
|
+
const errorText = await response.text();
|
|
1183
|
+
throw new Error(`Failed to check MoonPay geo: ${errorText}`, {
|
|
1184
|
+
cause: response.statusText
|
|
1185
|
+
});
|
|
1186
|
+
}
|
|
1187
|
+
return response.json();
|
|
1188
|
+
};
|
|
1189
|
+
|
|
1190
|
+
// ../api/src/check-moonpay-limits/check-moonpay-limits.ts
|
|
1191
|
+
var checkMoonpayLimits = async (params, options) => {
|
|
1192
|
+
const baseUrl = getBaseUrl(options?.chain);
|
|
1193
|
+
const headers = {
|
|
1194
|
+
"Content-Type": "application/json",
|
|
1195
|
+
...options?.apiKey ? { "x-api-key": options.apiKey } : {}
|
|
1196
|
+
};
|
|
1197
|
+
const response = await fetch(`${baseUrl}/api/external/moonpay/limits`, {
|
|
1198
|
+
method: "POST",
|
|
1199
|
+
body: JSON.stringify(params),
|
|
1200
|
+
headers
|
|
1201
|
+
});
|
|
1202
|
+
if (!response.ok) {
|
|
1203
|
+
const errorText = await response.text();
|
|
1204
|
+
throw new Error(`Failed to check MoonPay limits: ${errorText}`, {
|
|
1205
|
+
cause: response.statusText
|
|
1206
|
+
});
|
|
1207
|
+
}
|
|
1208
|
+
return response.json();
|
|
1209
|
+
};
|
|
1210
|
+
|
|
1211
|
+
// ../api/src/utils/verify-signature.ts
|
|
1212
|
+
import * as CryptoJS from "crypto-js";
|
|
1213
|
+
|
|
1214
|
+
// src/hooks/useMoonPayIframe.ts
|
|
1215
|
+
function useMoonPayIframe({
|
|
1216
|
+
apiKey,
|
|
1217
|
+
chain = "mainnet"
|
|
1218
|
+
}) {
|
|
1219
|
+
const [loading, setLoading] = React6.useState(false);
|
|
1220
|
+
const [error, setError] = React6.useState(null);
|
|
1221
|
+
const [link, setLink] = React6.useState(null);
|
|
1222
|
+
const [geoResult, setGeoResult] = React6.useState(
|
|
1223
|
+
null
|
|
1224
|
+
);
|
|
1225
|
+
const [limits, setLimits] = React6.useState(null);
|
|
1226
|
+
const checkAvailability = React6.useCallback(
|
|
1227
|
+
async (amount, asset, ipAddress) => {
|
|
1228
|
+
if (!apiKey) return false;
|
|
1229
|
+
setLoading(true);
|
|
1230
|
+
setError(null);
|
|
1231
|
+
try {
|
|
1232
|
+
const geo = await checkMoonpayGeo({ ipAddress }, { apiKey, chain });
|
|
1233
|
+
setGeoResult(geo);
|
|
1234
|
+
if (!geo.isBuyAllowed) {
|
|
1235
|
+
return false;
|
|
1236
|
+
}
|
|
1237
|
+
const limitRes = await checkMoonpayLimits(
|
|
1238
|
+
{ asset },
|
|
1239
|
+
{ apiKey, chain }
|
|
1240
|
+
);
|
|
1241
|
+
setLimits(limitRes);
|
|
1242
|
+
const limitsData = limitRes?.limits || limitRes;
|
|
1243
|
+
if (!limitsData?.quoteCurrency) {
|
|
1244
|
+
return false;
|
|
1245
|
+
}
|
|
1246
|
+
const { minBuyAmount, maxBuyAmount } = limitsData.quoteCurrency;
|
|
1247
|
+
if (amount < minBuyAmount || amount > maxBuyAmount) {
|
|
1248
|
+
return false;
|
|
1249
|
+
}
|
|
1250
|
+
return true;
|
|
1251
|
+
} catch {
|
|
1252
|
+
return false;
|
|
1253
|
+
} finally {
|
|
1254
|
+
setLoading(false);
|
|
1255
|
+
}
|
|
1256
|
+
},
|
|
1257
|
+
[apiKey, chain]
|
|
1258
|
+
);
|
|
1259
|
+
const fetchOnRampLink = React6.useCallback(
|
|
1260
|
+
async (params) => {
|
|
1261
|
+
if (!apiKey) throw new Error("API Key is required");
|
|
1262
|
+
setLoading(true);
|
|
1263
|
+
setError(null);
|
|
1264
|
+
try {
|
|
1265
|
+
const response = await createMoonpayTransfer(params, { apiKey, chain });
|
|
1266
|
+
setLink(response.link);
|
|
1267
|
+
return response.link;
|
|
1268
|
+
} catch (e) {
|
|
1269
|
+
const msg = e instanceof Error ? e.message : "Failed to generate OnRamp link";
|
|
1270
|
+
setError(msg);
|
|
1271
|
+
throw new Error(msg);
|
|
1272
|
+
} finally {
|
|
1273
|
+
setLoading(false);
|
|
1274
|
+
}
|
|
1275
|
+
},
|
|
1276
|
+
[apiKey, chain]
|
|
1277
|
+
);
|
|
1278
|
+
return {
|
|
1279
|
+
loading,
|
|
1280
|
+
error,
|
|
1281
|
+
link,
|
|
1282
|
+
fetchOnRampLink,
|
|
1283
|
+
checkAvailability,
|
|
1284
|
+
geoResult,
|
|
1285
|
+
limits
|
|
1286
|
+
};
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
// src/components/ton-pay-button/styles.ts
|
|
67
1290
|
var PRESETS = {
|
|
68
1291
|
default: {
|
|
69
1292
|
bgColor: "#0098EA",
|
|
@@ -74,86 +1297,57 @@ var PRESETS = {
|
|
|
74
1297
|
textColor: "#FFFFFF"
|
|
75
1298
|
}
|
|
76
1299
|
};
|
|
77
|
-
var
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
1300
|
+
var buttonStyles = `
|
|
1301
|
+
@keyframes tp-pulse{0%{opacity:1;transform:scale(1)}50%{opacity:.5;transform:scale(1.02)}100%{opacity:1;transform:scale(1)}}
|
|
1302
|
+
@keyframes tp-fade-in{from{opacity:0;transform:translateY(-4px) scale(.98)}to{opacity:1;transform:translateY(0) scale(1)}}
|
|
1303
|
+
@keyframes tp-spin{to{transform:rotate(360deg)}}
|
|
1304
|
+
|
|
1305
|
+
.tp-wrap{display:inline-flex;flex-direction:column;position:relative;width:var(--tp-width,300px);max-width:100%;--tp-menu-bg:#ffffff;--tp-menu-text:#111827;--tp-menu-muted:#6b7280;--tp-menu-hover:rgba(0,0,0,.06);--tp-menu-border:rgba(0,0,0,.08);--tp-menu-shadow:0 8px 24px rgba(0,0,0,.12)}
|
|
1306
|
+
@media(prefers-color-scheme:dark){.tp-wrap{--tp-menu-bg:#1C2633;--tp-menu-text:#F9FAFB;--tp-menu-muted:#9CA3AF;--tp-menu-hover:rgba(255,255,255,.08);--tp-menu-border:rgba(255,255,255,.1);--tp-menu-shadow:0 8px 32px rgba(0,0,0,.4)}}
|
|
1307
|
+
.tp-btn-container{display:flex;flex-direction:row;width:100%}
|
|
1308
|
+
.tp-btn{display:flex;flex-direction:column;justify-content:center;align-items:center;padding:13px 10px;gap:10px;flex:1;min-height:var(--tp-height,44px);background:var(--tp-bg,#0098EA);color:var(--tp-text,#fff);border:none;border-radius:var(--tp-radius,8px) 0 0 var(--tp-radius,8px);cursor:pointer;transition:filter .12s ease,transform .12s ease;font-family:var(--tp-font,inherit);font-style:normal;font-weight:500;font-size:20px;line-height:25px;text-align:center;position:relative}
|
|
1309
|
+
.tp-btn.with-menu{padding-left:calc(10px + (var(--tp-height,44px))/2)}
|
|
1310
|
+
.tp-btn.no-menu{border-radius:var(--tp-radius,8px)}
|
|
1311
|
+
.tp-btn-content{display:flex;flex-direction:row;align-items:center;padding:0;gap:5px;white-space:nowrap;margin-top:-4px}
|
|
1312
|
+
.tp-btn-content svg{margin-top:4px}
|
|
1313
|
+
.tp-btn:hover:not(:disabled){filter:brightness(0.92)}
|
|
1314
|
+
.tp-btn:active:not(:disabled){filter:brightness(0.85);transform:translateY(1px)}
|
|
1315
|
+
.tp-btn:disabled{cursor:not-allowed;opacity:.85}
|
|
1316
|
+
.tp-btn.loading{animation:none}
|
|
1317
|
+
|
|
1318
|
+
.tp-arrow{display:flex;align-items:center;justify-content:center;padding:13px 10px;min-width:calc(var(--tp-height,44px));min-height:var(--tp-height,44px);background:var(--tp-bg,#0098EA);color:var(--tp-text,#fff);border:none;border-left:1px solid rgba(255,255,255,.2);border-radius:0 var(--tp-radius,8px) var(--tp-radius,8px) 0;cursor:pointer;transition:filter .12s ease,transform .12s ease;font-size:14px}
|
|
1319
|
+
.tp-arrow:hover:not(:disabled){filter:brightness(0.92)}
|
|
1320
|
+
.tp-arrow:active:not(:disabled){filter:brightness(0.85);transform:translateY(1px)}
|
|
1321
|
+
.tp-arrow:disabled{cursor:not-allowed;opacity:.85;transition:none;filter:none;transform:none}
|
|
1322
|
+
|
|
1323
|
+
.tp-menu{position:absolute;right:0;top:calc(100% + 8px);width:256px;background:var(--tp-menu-bg);color:var(--tp-menu-text);border:1px solid var(--tp-menu-border);border-radius:var(--tp-menu-radius,16px);padding:8px;box-shadow:var(--tp-menu-shadow);z-index:1000;animation:tp-fade-in .15s ease}
|
|
1324
|
+
.tp-menu-arrow{position:absolute;top:-8px;right:20px;width:0;height:0;border-style:solid;border-width:0 8px 8px 8px;border-color:transparent transparent var(--tp-menu-bg) transparent;filter:drop-shadow(0 -1px 1px rgba(0,0,0,.08))}
|
|
1325
|
+
.tp-menu-address{padding:.5rem .75rem;font-size:.85rem;color:var(--tp-menu-muted);cursor:default;user-select:text}
|
|
1326
|
+
.tp-menu-item{display:flex;align-items:center;gap:8px;width:100%;height:40px;padding-left:12px;padding-right:12px;border:none;background:transparent;text-align:left;cursor:pointer;font-size:15px;font-weight:590;color:var(--tp-menu-text);transition:background-color .15s ease,transform .1s ease-in-out;border-radius:8px;margin:2px}
|
|
1327
|
+
.tp-menu-item:hover:not(:disabled){background:var(--tp-menu-hover)}
|
|
1328
|
+
.tp-menu-item:active{transform:scale(0.96)}
|
|
1329
|
+
.tp-menu-item.danger{color:#e74c3c}
|
|
1330
|
+
.tp-menu-item.danger:hover:not(:disabled){background:rgba(231,76,60,.12);color:#c0392b}
|
|
1331
|
+
.tp-menu-item:disabled{cursor:default;opacity:1;color:var(--tp-menu-muted)}
|
|
1332
|
+
.tp-menu-item:disabled:hover{background:transparent}
|
|
1333
|
+
.tp-menu-icon{width:24px;height:24px;display:flex;align-items:center;justify-content:center;color:currentColor}
|
|
1334
|
+
.tp-menu-item:disabled .tp-menu-icon{opacity:.5}
|
|
1335
|
+
|
|
1336
|
+
.tp-spinner{border:2px solid rgba(255,255,255,.35);border-top-color:var(--tp-text,#fff);border-radius:50%;width:18px;height:18px;animation:tp-spin .6s linear infinite}
|
|
109
1337
|
`;
|
|
110
|
-
|
|
1338
|
+
var stylesInjected2 = false;
|
|
1339
|
+
function injectStyles() {
|
|
1340
|
+
if (typeof document === "undefined" || stylesInjected2) return;
|
|
111
1341
|
const style = document.createElement("style");
|
|
112
|
-
style.
|
|
1342
|
+
style.id = "tonpay-button-styles";
|
|
1343
|
+
style.textContent = buttonStyles;
|
|
113
1344
|
document.head.appendChild(style);
|
|
1345
|
+
stylesInjected2 = true;
|
|
114
1346
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
/* @__PURE__ */ jsx3("path", { d: "M16.0972 6.69763H7.9022C6.39543 6.69763 5.4404 8.32299 6.19846 9.63695L11.2561 18.4033C11.5862 18.9757 12.4133 18.9757 12.7433 18.4033L17.802 9.63695C18.559 8.32509 17.604 6.69763 16.0982 6.69763H16.0972ZM11.252 15.7744L10.1505 13.6426L7.49278 8.88922C7.31746 8.58497 7.53401 8.1951 7.90117 8.1951H11.251V15.7754L11.252 15.7744ZM16.5046 8.88819L13.8479 13.6437L12.7464 15.7744V8.19407H16.0962C16.4633 8.19407 16.6799 8.58395 16.5046 8.88819Z", fill: "#0098EA" })
|
|
120
|
-
] }),
|
|
121
|
-
/* @__PURE__ */ jsx3("defs", { children: /* @__PURE__ */ jsx3("clipPath", { id: "clip0_144_4719", children: /* @__PURE__ */ jsx3("rect", { width: "24", height: "24", fill: "white" }) }) })
|
|
122
|
-
] });
|
|
123
|
-
var CopyGlyph = () => /* @__PURE__ */ jsx3("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": true, children: /* @__PURE__ */ jsx3(
|
|
124
|
-
"path",
|
|
125
|
-
{
|
|
126
|
-
fillRule: "evenodd",
|
|
127
|
-
clipRule: "evenodd",
|
|
128
|
-
d: "M7.76228 2.09998H10.2378C11.0458 2.09997 11.7067 2.09996 12.2438 2.14384C12.7997 2.18926 13.3017 2.28614 13.7706 2.52505C14.5045 2.89896 15.1011 3.49558 15.475 4.22941C15.7139 4.6983 15.8108 5.20038 15.8562 5.75629C15.9001 6.29337 15.9001 6.95422 15.9001 7.76227V8.1H16.2377C17.0457 8.09999 17.7066 8.09998 18.2437 8.14386C18.7996 8.18928 19.3017 8.28616 19.7705 8.52507C20.5044 8.89898 21.101 9.4956 21.4749 10.2294C21.7138 10.6983 21.8107 11.2004 21.8561 11.7563C21.9 12.2934 21.9 12.9542 21.9 13.7623V16.2377C21.9 17.0458 21.9 17.7066 21.8561 18.2437C21.8107 18.7996 21.7138 19.3017 21.4749 19.7706C21.101 20.5044 20.5044 21.101 19.7705 21.4749C19.3017 21.7138 18.7996 21.8107 18.2437 21.8561C17.7066 21.9 17.0458 21.9 16.2378 21.9H13.7623C12.9543 21.9 12.2934 21.9 11.7563 21.8561C11.2004 21.8107 10.6983 21.7138 10.2294 21.4749C9.49561 21.101 8.89898 20.5044 8.52508 19.7706C8.28616 19.3017 8.18928 18.7996 8.14386 18.2437C8.09998 17.7066 8.09999 17.0458 8.1 16.2377V15.9H7.76227C6.95426 15.9 6.29335 15.9 5.75629 15.8561C5.20038 15.8107 4.6983 15.7138 4.22941 15.4749C3.49558 15.101 2.89896 14.5044 2.52505 13.7705C2.28614 13.3017 2.18926 12.7996 2.14384 12.2437C2.09996 11.7066 2.09997 11.0458 2.09998 10.2377V7.76228C2.09997 6.95424 2.09996 6.29336 2.14384 5.75629C2.18926 5.20038 2.28614 4.6983 2.52505 4.22941C2.89896 3.49558 3.49558 2.89896 4.22941 2.52505C4.6983 2.28614 5.20038 2.18926 5.75629 2.14384C6.29336 2.09996 6.95425 2.09997 7.76228 2.09998ZM8.1 14.1V13.7623C8.09999 12.9542 8.09998 12.2934 8.14386 11.7563C8.18928 11.2004 8.28616 10.6983 8.52508 10.2294C8.89898 9.4956 9.49561 8.89898 10.2294 8.52507C10.6983 8.28616 11.2004 8.18928 11.7563 8.14386C12.2934 8.09998 12.9542 8.09999 13.7623 8.1H14.1001V7.79998C14.1001 6.94505 14.0994 6.35798 14.0622 5.90287C14.0259 5.45827 13.9593 5.21944 13.8712 5.0466C13.6699 4.65146 13.3486 4.3302 12.9535 4.12886C12.7806 4.04079 12.5418 3.97419 12.0972 3.93786C11.6421 3.90068 11.055 3.89998 10.2001 3.89998H7.79998C6.94505 3.89998 6.35798 3.90068 5.90287 3.93786C5.45827 3.97419 5.21944 4.04079 5.0466 4.12886C4.65146 4.3302 4.3302 4.65146 4.12886 5.0466C4.04079 5.21944 3.97419 5.45827 3.93786 5.90287C3.90068 6.35798 3.89998 6.94505 3.89998 7.79998V10.2C3.89998 11.0549 3.90068 11.642 3.93786 12.0971C3.97419 12.5417 4.04079 12.7805 4.12886 12.9534C4.3302 13.3485 4.65146 13.6698 5.0466 13.8711C5.21944 13.9592 5.45827 14.0258 5.90287 14.0621C6.35798 14.0993 6.94505 14.1 7.79998 14.1H8.1ZM11.0466 10.1289C11.2195 10.0408 11.4583 9.97421 11.9029 9.93788C12.358 9.9007 12.9451 9.9 13.8 9.9H16.2C17.0549 9.9 17.642 9.9007 18.0971 9.93788C18.5417 9.97421 18.7805 10.0408 18.9534 10.1289C19.3485 10.3302 19.6698 10.6515 19.8711 11.0466C19.9592 11.2195 20.0258 11.4583 20.0621 11.9029C20.0993 12.358 20.1 12.9451 20.1 13.8V16.2C20.1 17.0549 20.0993 17.642 20.0621 18.0971C20.0258 18.5417 19.9592 18.7805 19.8711 18.9534C19.6698 19.3485 19.3485 19.6698 18.9534 19.8711C18.7805 19.9592 18.5417 20.0258 18.0971 20.0621C17.642 20.0993 17.0549 20.1 16.2 20.1H13.8C12.9451 20.1 12.358 20.0993 11.9029 20.0621C11.4583 20.0258 11.2195 19.9592 11.0466 19.8711C10.6515 19.6698 10.3302 19.3485 10.1289 18.9534C10.0408 18.7805 9.97421 18.5417 9.93788 18.0971C9.9007 17.642 9.9 17.0549 9.9 16.2V13.8C9.9 12.9451 9.9007 12.358 9.93788 11.9029C9.97421 11.4583 10.0408 11.2195 10.1289 11.0466C10.3302 10.6515 10.6515 10.3302 11.0466 10.1289Z",
|
|
129
|
-
fill: "currentColor"
|
|
130
|
-
}
|
|
131
|
-
) });
|
|
132
|
-
var DisconnectGlyph = () => /* @__PURE__ */ jsxs2("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": true, children: [
|
|
133
|
-
/* @__PURE__ */ jsx3(
|
|
134
|
-
"path",
|
|
135
|
-
{
|
|
136
|
-
d: "M8.7624 3.10001C7.95435 3.1 7.29349 3.09999 6.75642 3.14387C6.2005 3.18929 5.69842 3.28617 5.22954 3.52508C4.4957 3.89899 3.89908 4.49561 3.52517 5.22944C3.28626 5.69833 3.18938 6.20041 3.14396 6.75632C3.10008 7.2934 3.10009 7.95424 3.1001 8.76229V15.2377C3.10009 16.0458 3.10008 16.7066 3.14396 17.2437C3.18938 17.7996 3.28626 18.3017 3.52517 18.7706C3.89908 19.5044 4.4957 20.101 5.22954 20.4749C5.69842 20.7138 6.2005 20.8107 6.75642 20.8561C7.29349 20.9 7.95434 20.9 8.76239 20.9H12.0001C12.4972 20.9 12.9001 20.4971 12.9001 20C12.9001 19.503 12.4972 19.1 12.0001 19.1H8.8001C7.94517 19.1 7.3581 19.0993 6.90299 19.0621C6.45839 19.0258 6.21956 18.9592 6.04672 18.8711C5.65158 18.6698 5.33032 18.3485 5.12898 17.9534C5.04092 17.7805 4.97431 17.5417 4.93798 17.0971C4.9008 16.642 4.9001 16.0549 4.9001 15.2V8.80001C4.9001 7.94508 4.9008 7.35801 4.93798 6.9029C4.97431 6.4583 5.04092 6.21947 5.12898 6.04663C5.33032 5.65149 5.65158 5.33023 6.04672 5.12889C6.21956 5.04082 6.45839 4.97422 6.90299 4.93789C7.3581 4.90071 7.94517 4.90001 8.8001 4.90001H12.0001C12.4972 4.90001 12.9001 4.49706 12.9001 4.00001C12.9001 3.50295 12.4972 3.10001 12.0001 3.10001H8.7624Z",
|
|
137
|
-
fill: "currentColor"
|
|
138
|
-
}
|
|
139
|
-
),
|
|
140
|
-
/* @__PURE__ */ jsx3(
|
|
141
|
-
"path",
|
|
142
|
-
{
|
|
143
|
-
d: "M17.6364 7.3636C17.2849 7.01212 16.7151 7.01212 16.3636 7.3636C16.0121 7.71507 16.0121 8.28492 16.3636 8.63639L18.8272 11.1H9.00001C8.50295 11.1 8.10001 11.5029 8.10001 12C8.10001 12.497 8.50295 12.9 9.00001 12.9H18.8272L16.3636 15.3636C16.0121 15.7151 16.0121 16.2849 16.3636 16.6364C16.7151 16.9879 17.2849 16.9879 17.6364 16.6364L21.6364 12.6364C21.9879 12.2849 21.9879 11.7151 21.6364 11.3636L17.6364 7.3636Z",
|
|
144
|
-
fill: "currentColor"
|
|
145
|
-
}
|
|
146
|
-
)
|
|
147
|
-
] });
|
|
148
|
-
function shortenAddress(addr, head = 4, tail = 4) {
|
|
149
|
-
if (!addr || typeof addr !== "string") return "";
|
|
150
|
-
if (addr.length <= head + tail + 3) return addr;
|
|
151
|
-
return `${addr.slice(0, head)}...${addr.slice(-tail)}`;
|
|
152
|
-
}
|
|
153
|
-
function toCssSize(v) {
|
|
154
|
-
if (v === void 0) return void 0;
|
|
155
|
-
return typeof v === "number" ? `${v}px` : v;
|
|
156
|
-
}
|
|
1347
|
+
|
|
1348
|
+
// src/components/ton-pay-button/TonPayButton.tsx
|
|
1349
|
+
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1350
|
+
injectStyles();
|
|
157
1351
|
var TonPayButton = ({
|
|
158
1352
|
handlePay,
|
|
159
1353
|
isLoading = false,
|
|
@@ -169,168 +1363,177 @@ var TonPayButton = ({
|
|
|
169
1363
|
loadingText = "Processing...",
|
|
170
1364
|
style,
|
|
171
1365
|
className,
|
|
172
|
-
showMenu = true,
|
|
173
1366
|
disabled = false,
|
|
174
1367
|
onError,
|
|
175
|
-
showErrorNotification = true
|
|
1368
|
+
showErrorNotification = true,
|
|
1369
|
+
amount,
|
|
1370
|
+
currency,
|
|
1371
|
+
apiKey,
|
|
1372
|
+
isOnRampAvailable = false,
|
|
1373
|
+
onCardPaymentSuccess,
|
|
1374
|
+
itemTitle
|
|
176
1375
|
}) => {
|
|
177
1376
|
const address = useTonAddress(true);
|
|
178
|
-
const modal = useTonConnectModal();
|
|
179
1377
|
const [tonConnectUI] = useTonConnectUI();
|
|
180
|
-
const
|
|
181
|
-
const
|
|
182
|
-
const [
|
|
183
|
-
const [
|
|
184
|
-
const [
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}, [showContextMenu]);
|
|
194
|
-
useEffect(() => {
|
|
195
|
-
if (!showContextMenu && isCopiedShown) {
|
|
196
|
-
setIsCopiedShown(false);
|
|
197
|
-
}
|
|
198
|
-
}, [showContextMenu, isCopiedShown]);
|
|
199
|
-
useEffect(() => {
|
|
1378
|
+
const [isModalOpen, setIsModalOpen] = useState4(false);
|
|
1379
|
+
const [onRampAvailable, setOnRampAvailable] = useState4(false);
|
|
1380
|
+
const [errorMessage, setErrorMessage] = useState4(null);
|
|
1381
|
+
const [internalLoading, setInternalLoading] = useState4(false);
|
|
1382
|
+
const [userIp, setUserIp] = useState4("");
|
|
1383
|
+
const { checkAvailability, fetchOnRampLink } = useMoonPayIframe({
|
|
1384
|
+
apiKey,
|
|
1385
|
+
chain: "mainnet"
|
|
1386
|
+
});
|
|
1387
|
+
useEffect3(() => {
|
|
1388
|
+
getUserIp().then(setUserIp);
|
|
1389
|
+
}, []);
|
|
1390
|
+
useEffect3(() => {
|
|
200
1391
|
if (!errorMessage) return;
|
|
201
1392
|
const timerId = setTimeout(() => setErrorMessage(null), 3e3);
|
|
202
1393
|
return () => clearTimeout(timerId);
|
|
203
1394
|
}, [errorMessage]);
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
} catch {
|
|
1395
|
+
useEffect3(() => {
|
|
1396
|
+
let isActive = true;
|
|
1397
|
+
const check = async () => {
|
|
1398
|
+
if (isOnRampAvailable && apiKey && userIp) {
|
|
1399
|
+
setInternalLoading(true);
|
|
1400
|
+
try {
|
|
1401
|
+
const parsedAmount = typeof amount === "string" ? parseFloat(amount) : amount || 0;
|
|
1402
|
+
const available = await checkAvailability(
|
|
1403
|
+
parsedAmount,
|
|
1404
|
+
currency || "TON",
|
|
1405
|
+
userIp
|
|
1406
|
+
);
|
|
1407
|
+
if (isActive) setOnRampAvailable(available);
|
|
1408
|
+
} catch {
|
|
1409
|
+
if (isActive) setOnRampAvailable(false);
|
|
1410
|
+
} finally {
|
|
1411
|
+
if (isActive) setInternalLoading(false);
|
|
1412
|
+
}
|
|
1413
|
+
} else {
|
|
1414
|
+
if (isActive) setOnRampAvailable(false);
|
|
225
1415
|
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
};
|
|
232
|
-
const
|
|
1416
|
+
};
|
|
1417
|
+
check();
|
|
1418
|
+
return () => {
|
|
1419
|
+
isActive = false;
|
|
1420
|
+
};
|
|
1421
|
+
}, [apiKey, amount, currency, isOnRampAvailable, checkAvailability, userIp]);
|
|
1422
|
+
const handleDisconnect = useCallback4(() => {
|
|
1423
|
+
tonConnectUI.disconnect();
|
|
1424
|
+
}, [tonConnectUI]);
|
|
1425
|
+
const handlePayWithCrypto = useCallback4(async () => {
|
|
1426
|
+
setIsModalOpen(false);
|
|
233
1427
|
try {
|
|
234
1428
|
await handlePay();
|
|
235
1429
|
} catch (err) {
|
|
236
|
-
|
|
237
|
-
onError?.(err);
|
|
238
|
-
} catch {
|
|
239
|
-
}
|
|
1430
|
+
onError?.(err);
|
|
240
1431
|
if (showErrorNotification) {
|
|
241
1432
|
const raw = typeof err === "object" && err && "message" in err ? String(err.message) : String(err ?? "");
|
|
242
|
-
|
|
243
|
-
setErrorMessage(msg);
|
|
1433
|
+
setErrorMessage(raw || "Wallet connection modal closed");
|
|
244
1434
|
}
|
|
245
1435
|
}
|
|
246
|
-
};
|
|
1436
|
+
}, [handlePay, onError, showErrorNotification]);
|
|
1437
|
+
const onPayClick = useCallback4(async () => {
|
|
1438
|
+
if (onRampAvailable) {
|
|
1439
|
+
setIsModalOpen(true);
|
|
1440
|
+
} else {
|
|
1441
|
+
handlePayWithCrypto();
|
|
1442
|
+
}
|
|
1443
|
+
}, [onRampAvailable, handlePayWithCrypto]);
|
|
1444
|
+
const handleFetchOnRampLink = useCallback4(
|
|
1445
|
+
async (_providerId) => {
|
|
1446
|
+
return fetchOnRampLink({
|
|
1447
|
+
amount: typeof amount === "string" ? parseFloat(amount) : amount || 0,
|
|
1448
|
+
asset: currency || "TON",
|
|
1449
|
+
recipientAddr: address,
|
|
1450
|
+
userIp,
|
|
1451
|
+
redirectURL: ""
|
|
1452
|
+
});
|
|
1453
|
+
},
|
|
1454
|
+
[fetchOnRampLink, amount, currency, address, userIp]
|
|
1455
|
+
);
|
|
247
1456
|
const presetConfig = preset ? PRESETS[preset] : null;
|
|
248
1457
|
const finalBgColor = bgColor ?? presetConfig?.bgColor ?? PRESETS.default.bgColor;
|
|
249
1458
|
const finalTextColor = textColor ?? presetConfig?.textColor ?? PRESETS.default.textColor;
|
|
250
|
-
const
|
|
1459
|
+
const cssVars = {
|
|
251
1460
|
"--tp-bg": finalBgColor,
|
|
252
1461
|
"--tp-text": finalTextColor,
|
|
253
1462
|
"--tp-radius": typeof borderRadius === "number" ? `${borderRadius}px` : borderRadius,
|
|
254
1463
|
"--tp-font": fontFamily,
|
|
255
1464
|
"--tp-width": toCssSize(width),
|
|
256
|
-
"--tp-height": toCssSize(height)
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
"
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
errorMessage && /* @__PURE__ */ jsx3(NotificationRoot, { children: /* @__PURE__ */ jsx3(ErrorTransactionNotification, { text: errorMessage }) })
|
|
313
|
-
] });
|
|
1465
|
+
"--tp-height": toCssSize(height)
|
|
1466
|
+
};
|
|
1467
|
+
const isDisabled = isLoading || disabled || internalLoading;
|
|
1468
|
+
const renderContent = () => {
|
|
1469
|
+
if (text) return /* @__PURE__ */ jsx6("span", { children: text });
|
|
1470
|
+
if (variant === "short") {
|
|
1471
|
+
return /* @__PURE__ */ jsxs5("div", { className: "tp-btn-content", children: [
|
|
1472
|
+
/* @__PURE__ */ jsx6(TonIcon, {}),
|
|
1473
|
+
/* @__PURE__ */ jsx6("span", { children: "Pay" })
|
|
1474
|
+
] });
|
|
1475
|
+
}
|
|
1476
|
+
return /* @__PURE__ */ jsxs5("div", { className: "tp-btn-content", children: [
|
|
1477
|
+
/* @__PURE__ */ jsx6("span", { children: "Pay with" }),
|
|
1478
|
+
/* @__PURE__ */ jsx6(TonIcon, {}),
|
|
1479
|
+
/* @__PURE__ */ jsx6("span", { children: "Pay" })
|
|
1480
|
+
] });
|
|
1481
|
+
};
|
|
1482
|
+
return /* @__PURE__ */ jsxs5(
|
|
1483
|
+
"div",
|
|
1484
|
+
{
|
|
1485
|
+
style: { ...cssVars, ...style },
|
|
1486
|
+
className: classNames("tp-wrap", className),
|
|
1487
|
+
children: [
|
|
1488
|
+
/* @__PURE__ */ jsx6("div", { className: "tp-btn-container", children: /* @__PURE__ */ jsx6(
|
|
1489
|
+
"button",
|
|
1490
|
+
{
|
|
1491
|
+
type: "button",
|
|
1492
|
+
className: classNames("tp-btn", isLoading && "loading", "no-menu"),
|
|
1493
|
+
onClick: isDisabled ? void 0 : onPayClick,
|
|
1494
|
+
disabled: isDisabled,
|
|
1495
|
+
children: isLoading ? /* @__PURE__ */ jsxs5("div", { className: "tp-btn-content", children: [
|
|
1496
|
+
/* @__PURE__ */ jsx6("span", { className: "tp-spinner" }),
|
|
1497
|
+
/* @__PURE__ */ jsx6("span", { children: loadingText })
|
|
1498
|
+
] }) : renderContent()
|
|
1499
|
+
}
|
|
1500
|
+
) }),
|
|
1501
|
+
/* @__PURE__ */ jsx6(
|
|
1502
|
+
PaymentModal,
|
|
1503
|
+
{
|
|
1504
|
+
isOpen: isModalOpen,
|
|
1505
|
+
onClose: () => setIsModalOpen(false),
|
|
1506
|
+
onPayWithCrypto: handlePayWithCrypto,
|
|
1507
|
+
walletAddress: address,
|
|
1508
|
+
onDisconnect: handleDisconnect,
|
|
1509
|
+
amount: amount ? String(amount) : "0.1",
|
|
1510
|
+
currency: currency || "TON",
|
|
1511
|
+
itemTitle,
|
|
1512
|
+
fetchOnRampLink: handleFetchOnRampLink,
|
|
1513
|
+
onRampAvailable,
|
|
1514
|
+
onPaymentSuccess: onCardPaymentSuccess
|
|
1515
|
+
}
|
|
1516
|
+
),
|
|
1517
|
+
errorMessage && /* @__PURE__ */ jsx6(NotificationRoot, { children: /* @__PURE__ */ jsx6(ErrorTransactionNotification, { text: errorMessage }) })
|
|
1518
|
+
]
|
|
1519
|
+
}
|
|
1520
|
+
);
|
|
314
1521
|
};
|
|
315
1522
|
|
|
316
1523
|
// src/hooks/useTonPay.ts
|
|
317
|
-
import
|
|
1524
|
+
import * as React8 from "react";
|
|
318
1525
|
import {
|
|
319
1526
|
useTonAddress as useTonAddress2,
|
|
320
|
-
useTonConnectModal
|
|
1527
|
+
useTonConnectModal,
|
|
321
1528
|
useTonConnectUI as useTonConnectUI2
|
|
322
1529
|
} from "@tonconnect/ui-react";
|
|
323
|
-
|
|
324
|
-
var
|
|
1530
|
+
var WALLET_CONNECTION_TIMEOUT = 5 * 60 * 1e3;
|
|
1531
|
+
var TX_VALID_DURATION = 5 * 60;
|
|
1532
|
+
function useTonPay() {
|
|
325
1533
|
const address = useTonAddress2(true);
|
|
326
|
-
const modal =
|
|
1534
|
+
const modal = useTonConnectModal();
|
|
327
1535
|
const [tonConnectUI] = useTonConnectUI2();
|
|
328
|
-
|
|
329
|
-
if (address) {
|
|
330
|
-
console.log(address);
|
|
331
|
-
}
|
|
332
|
-
}, [address]);
|
|
333
|
-
const waitForWalletConnection = React3.useCallback(() => {
|
|
1536
|
+
const waitForWalletConnection = React8.useCallback(() => {
|
|
334
1537
|
return new Promise((resolve, reject) => {
|
|
335
1538
|
if (address) {
|
|
336
1539
|
resolve(address);
|
|
@@ -338,7 +1541,7 @@ var useTonPay = () => {
|
|
|
338
1541
|
}
|
|
339
1542
|
modal.open();
|
|
340
1543
|
const unsubscribe = tonConnectUI.onStatusChange((wallet) => {
|
|
341
|
-
if (wallet
|
|
1544
|
+
if (wallet?.account) {
|
|
342
1545
|
unsubscribe();
|
|
343
1546
|
unsubscribeModal();
|
|
344
1547
|
resolve(wallet.account.address);
|
|
@@ -355,36 +1558,1731 @@ var useTonPay = () => {
|
|
|
355
1558
|
unsubscribe();
|
|
356
1559
|
unsubscribeModal();
|
|
357
1560
|
reject(new Error("Wallet connection timeout"));
|
|
358
|
-
},
|
|
1561
|
+
}, WALLET_CONNECTION_TIMEOUT);
|
|
359
1562
|
});
|
|
360
1563
|
}, [address, modal, tonConnectUI]);
|
|
361
|
-
const pay =
|
|
1564
|
+
const pay = React8.useCallback(
|
|
362
1565
|
async (getMessage) => {
|
|
1566
|
+
const walletAddress = await waitForWalletConnection();
|
|
1567
|
+
const validUntil = Math.floor(Date.now() / 1e3) + TX_VALID_DURATION;
|
|
1568
|
+
const messageResult = await getMessage(walletAddress);
|
|
1569
|
+
const txResult = await tonConnectUI.sendTransaction({
|
|
1570
|
+
messages: [messageResult.message],
|
|
1571
|
+
validUntil,
|
|
1572
|
+
from: walletAddress
|
|
1573
|
+
});
|
|
1574
|
+
return { ...messageResult, txResult };
|
|
1575
|
+
},
|
|
1576
|
+
[waitForWalletConnection, tonConnectUI]
|
|
1577
|
+
);
|
|
1578
|
+
return { pay, address };
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
// src/signless/context.tsx
|
|
1582
|
+
import * as React9 from "react";
|
|
1583
|
+
import { useTonAddress as useTonAddress3 } from "@tonconnect/ui-react";
|
|
1584
|
+
|
|
1585
|
+
// src/signless/crypto/ed25519.ts
|
|
1586
|
+
var ALGORITHM = "Ed25519";
|
|
1587
|
+
function arrayBufferToUint8Array(buffer) {
|
|
1588
|
+
return new Uint8Array(buffer);
|
|
1589
|
+
}
|
|
1590
|
+
async function generateKeyPair() {
|
|
1591
|
+
const keyPair = await crypto.subtle.generateKey(ALGORITHM, true, [
|
|
1592
|
+
"sign",
|
|
1593
|
+
"verify"
|
|
1594
|
+
]);
|
|
1595
|
+
const privateKeyBuffer = await crypto.subtle.exportKey(
|
|
1596
|
+
"pkcs8",
|
|
1597
|
+
keyPair.privateKey
|
|
1598
|
+
);
|
|
1599
|
+
const publicKeyBuffer = await crypto.subtle.exportKey(
|
|
1600
|
+
"spki",
|
|
1601
|
+
keyPair.publicKey
|
|
1602
|
+
);
|
|
1603
|
+
return {
|
|
1604
|
+
publicKey: arrayBufferToUint8Array(publicKeyBuffer),
|
|
1605
|
+
privateKey: arrayBufferToUint8Array(privateKeyBuffer)
|
|
1606
|
+
};
|
|
1607
|
+
}
|
|
1608
|
+
async function signMessage(privateKey, message) {
|
|
1609
|
+
const importedKey = await crypto.subtle.importKey(
|
|
1610
|
+
"pkcs8",
|
|
1611
|
+
privateKey,
|
|
1612
|
+
ALGORITHM,
|
|
1613
|
+
false,
|
|
1614
|
+
["sign"]
|
|
1615
|
+
);
|
|
1616
|
+
const signature = await crypto.subtle.sign(ALGORITHM, importedKey, message);
|
|
1617
|
+
return arrayBufferToUint8Array(signature);
|
|
1618
|
+
}
|
|
1619
|
+
async function verifySignlessSignature(publicKey, message, signature) {
|
|
1620
|
+
const importedKey = await crypto.subtle.importKey(
|
|
1621
|
+
"spki",
|
|
1622
|
+
publicKey,
|
|
1623
|
+
ALGORITHM,
|
|
1624
|
+
false,
|
|
1625
|
+
["verify"]
|
|
1626
|
+
);
|
|
1627
|
+
return crypto.subtle.verify(ALGORITHM, importedKey, signature, message);
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
// src/signless/crypto/encryption.ts
|
|
1631
|
+
var PBKDF2_ITERATIONS = 1e5;
|
|
1632
|
+
var SALT_LENGTH = 16;
|
|
1633
|
+
var IV_LENGTH = 12;
|
|
1634
|
+
var KEY_LENGTH = 256;
|
|
1635
|
+
function arrayBufferToBase64(buffer) {
|
|
1636
|
+
const bytes = new Uint8Array(buffer);
|
|
1637
|
+
let binary = "";
|
|
1638
|
+
for (let i = 0; i < bytes.byteLength; i++) {
|
|
1639
|
+
binary += String.fromCharCode(bytes[i]);
|
|
1640
|
+
}
|
|
1641
|
+
return btoa(binary);
|
|
1642
|
+
}
|
|
1643
|
+
function base64ToArrayBuffer(base64) {
|
|
1644
|
+
const binary = atob(base64);
|
|
1645
|
+
const bytes = new Uint8Array(binary.length);
|
|
1646
|
+
for (let i = 0; i < binary.length; i++) {
|
|
1647
|
+
bytes[i] = binary.charCodeAt(i);
|
|
1648
|
+
}
|
|
1649
|
+
return bytes.buffer;
|
|
1650
|
+
}
|
|
1651
|
+
function generateSalt() {
|
|
1652
|
+
return crypto.getRandomValues(new Uint8Array(SALT_LENGTH));
|
|
1653
|
+
}
|
|
1654
|
+
function generateIv() {
|
|
1655
|
+
return crypto.getRandomValues(new Uint8Array(IV_LENGTH));
|
|
1656
|
+
}
|
|
1657
|
+
async function deriveKeyFromPin(pin, salt) {
|
|
1658
|
+
const encoder = new TextEncoder();
|
|
1659
|
+
const pinData = encoder.encode(pin);
|
|
1660
|
+
const baseKey = await crypto.subtle.importKey(
|
|
1661
|
+
"raw",
|
|
1662
|
+
pinData,
|
|
1663
|
+
"PBKDF2",
|
|
1664
|
+
false,
|
|
1665
|
+
["deriveKey"]
|
|
1666
|
+
);
|
|
1667
|
+
return crypto.subtle.deriveKey(
|
|
1668
|
+
{
|
|
1669
|
+
name: "PBKDF2",
|
|
1670
|
+
salt,
|
|
1671
|
+
iterations: PBKDF2_ITERATIONS,
|
|
1672
|
+
hash: "SHA-256"
|
|
1673
|
+
},
|
|
1674
|
+
baseKey,
|
|
1675
|
+
{ name: "AES-GCM", length: KEY_LENGTH },
|
|
1676
|
+
false,
|
|
1677
|
+
["encrypt", "decrypt"]
|
|
1678
|
+
);
|
|
1679
|
+
}
|
|
1680
|
+
async function encryptPrivateKey(privateKey, publicKey, pin) {
|
|
1681
|
+
const salt = generateSalt();
|
|
1682
|
+
const iv = generateIv();
|
|
1683
|
+
const derivedKey = await deriveKeyFromPin(pin, salt);
|
|
1684
|
+
const encryptedBuffer = await crypto.subtle.encrypt(
|
|
1685
|
+
{ name: "AES-GCM", iv },
|
|
1686
|
+
derivedKey,
|
|
1687
|
+
privateKey
|
|
1688
|
+
);
|
|
1689
|
+
return {
|
|
1690
|
+
salt: arrayBufferToBase64(salt.buffer),
|
|
1691
|
+
iv: arrayBufferToBase64(iv.buffer),
|
|
1692
|
+
encryptedBlob: arrayBufferToBase64(encryptedBuffer),
|
|
1693
|
+
publicKey: arrayBufferToBase64(publicKey.buffer),
|
|
1694
|
+
version: 1
|
|
1695
|
+
};
|
|
1696
|
+
}
|
|
1697
|
+
async function decryptPrivateKey(vault, pin) {
|
|
1698
|
+
const salt = new Uint8Array(base64ToArrayBuffer(vault.salt));
|
|
1699
|
+
const iv = new Uint8Array(base64ToArrayBuffer(vault.iv));
|
|
1700
|
+
const encryptedBlob = base64ToArrayBuffer(vault.encryptedBlob);
|
|
1701
|
+
const derivedKey = await deriveKeyFromPin(pin, salt);
|
|
1702
|
+
try {
|
|
1703
|
+
const decryptedBuffer = await crypto.subtle.decrypt(
|
|
1704
|
+
{ name: "AES-GCM", iv },
|
|
1705
|
+
derivedKey,
|
|
1706
|
+
encryptedBlob
|
|
1707
|
+
);
|
|
1708
|
+
return new Uint8Array(decryptedBuffer);
|
|
1709
|
+
} catch {
|
|
1710
|
+
throw new Error("Invalid PIN or corrupted vault");
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
// src/signless/crypto/webauthn.ts
|
|
1715
|
+
var WEBAUTHN_RP_NAME = "TON Pay";
|
|
1716
|
+
var WEBAUTHN_RP_ID_FALLBACK = "tonpay.io";
|
|
1717
|
+
var STATIC_CHALLENGE = new Uint8Array([
|
|
1718
|
+
1,
|
|
1719
|
+
2,
|
|
1720
|
+
3,
|
|
1721
|
+
4,
|
|
1722
|
+
5,
|
|
1723
|
+
6,
|
|
1724
|
+
7,
|
|
1725
|
+
8,
|
|
1726
|
+
9,
|
|
1727
|
+
10,
|
|
1728
|
+
11,
|
|
1729
|
+
12,
|
|
1730
|
+
13,
|
|
1731
|
+
14,
|
|
1732
|
+
15,
|
|
1733
|
+
16
|
|
1734
|
+
]);
|
|
1735
|
+
function arrayBufferToBase64Url(buffer) {
|
|
1736
|
+
const bytes = new Uint8Array(buffer);
|
|
1737
|
+
let binary = "";
|
|
1738
|
+
for (let i = 0; i < bytes.byteLength; i++) {
|
|
1739
|
+
binary += String.fromCharCode(bytes[i]);
|
|
1740
|
+
}
|
|
1741
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
1742
|
+
}
|
|
1743
|
+
function base64UrlToArrayBuffer(base64Url) {
|
|
1744
|
+
const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
|
|
1745
|
+
const padding = "=".repeat((4 - base64.length % 4) % 4);
|
|
1746
|
+
const binary = atob(base64 + padding);
|
|
1747
|
+
const bytes = new Uint8Array(binary.length);
|
|
1748
|
+
for (let i = 0; i < binary.length; i++) {
|
|
1749
|
+
bytes[i] = binary.charCodeAt(i);
|
|
1750
|
+
}
|
|
1751
|
+
return bytes.buffer;
|
|
1752
|
+
}
|
|
1753
|
+
function isWebAuthnSupported() {
|
|
1754
|
+
return typeof window !== "undefined" && !!window.PublicKeyCredential && typeof window.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable === "function";
|
|
1755
|
+
}
|
|
1756
|
+
function getRpId() {
|
|
1757
|
+
if (typeof window === "undefined") return WEBAUTHN_RP_ID_FALLBACK;
|
|
1758
|
+
return window.location.hostname || WEBAUTHN_RP_ID_FALLBACK;
|
|
1759
|
+
}
|
|
1760
|
+
async function createWebAuthnCredential(walletAddress) {
|
|
1761
|
+
if (!isWebAuthnSupported()) {
|
|
1762
|
+
throw new Error("WebAuthn is not supported in this browser");
|
|
1763
|
+
}
|
|
1764
|
+
const userId = new TextEncoder().encode(walletAddress);
|
|
1765
|
+
const createOptions = {
|
|
1766
|
+
challenge: STATIC_CHALLENGE,
|
|
1767
|
+
rp: {
|
|
1768
|
+
name: WEBAUTHN_RP_NAME,
|
|
1769
|
+
id: getRpId()
|
|
1770
|
+
},
|
|
1771
|
+
user: {
|
|
1772
|
+
id: userId,
|
|
1773
|
+
name: `TON Pay - ${walletAddress.slice(0, 8)}...`,
|
|
1774
|
+
displayName: "TON Pay Signless"
|
|
1775
|
+
},
|
|
1776
|
+
pubKeyCredParams: [
|
|
1777
|
+
{ type: "public-key", alg: -7 },
|
|
1778
|
+
{ type: "public-key", alg: -257 }
|
|
1779
|
+
],
|
|
1780
|
+
timeout: 6e4,
|
|
1781
|
+
authenticatorSelection: {
|
|
1782
|
+
authenticatorAttachment: "platform",
|
|
1783
|
+
userVerification: "required",
|
|
1784
|
+
residentKey: "preferred"
|
|
1785
|
+
},
|
|
1786
|
+
attestation: "none"
|
|
1787
|
+
};
|
|
1788
|
+
const credential = await navigator.credentials.create({
|
|
1789
|
+
publicKey: createOptions
|
|
1790
|
+
});
|
|
1791
|
+
if (!credential) {
|
|
1792
|
+
throw new Error("Failed to create WebAuthn credential");
|
|
1793
|
+
}
|
|
1794
|
+
const response = credential.response;
|
|
1795
|
+
return {
|
|
1796
|
+
credentialId: arrayBufferToBase64Url(credential.rawId),
|
|
1797
|
+
publicKey: arrayBufferToBase64Url(response.getPublicKey?.() ?? new ArrayBuffer(0)),
|
|
1798
|
+
transports: response.getTransports?.()
|
|
1799
|
+
};
|
|
1800
|
+
}
|
|
1801
|
+
async function getWebAuthnCredential(credentialInfo) {
|
|
1802
|
+
if (!isWebAuthnSupported()) {
|
|
1803
|
+
throw new Error("WebAuthn is not supported in this browser");
|
|
1804
|
+
}
|
|
1805
|
+
const credentialId = base64UrlToArrayBuffer(credentialInfo.credentialId);
|
|
1806
|
+
const getOptions = {
|
|
1807
|
+
challenge: STATIC_CHALLENGE,
|
|
1808
|
+
rpId: getRpId(),
|
|
1809
|
+
timeout: 6e4,
|
|
1810
|
+
userVerification: "required",
|
|
1811
|
+
allowCredentials: [
|
|
1812
|
+
{
|
|
1813
|
+
type: "public-key",
|
|
1814
|
+
id: credentialId,
|
|
1815
|
+
transports: credentialInfo.transports
|
|
1816
|
+
}
|
|
1817
|
+
]
|
|
1818
|
+
};
|
|
1819
|
+
const assertion = await navigator.credentials.get({
|
|
1820
|
+
publicKey: getOptions
|
|
1821
|
+
});
|
|
1822
|
+
if (!assertion) {
|
|
1823
|
+
throw new Error("WebAuthn authentication failed");
|
|
1824
|
+
}
|
|
1825
|
+
const response = assertion.response;
|
|
1826
|
+
return response.signature;
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1829
|
+
// src/signless/storage.ts
|
|
1830
|
+
var DEFAULT_STORAGE_KEY = "tonpay_signless_vault";
|
|
1831
|
+
var SignlessStorage = class {
|
|
1832
|
+
storageKey;
|
|
1833
|
+
constructor(storageKey = DEFAULT_STORAGE_KEY) {
|
|
1834
|
+
this.storageKey = storageKey;
|
|
1835
|
+
}
|
|
1836
|
+
getStorage() {
|
|
1837
|
+
if (typeof window === "undefined") return null;
|
|
1838
|
+
return window.localStorage;
|
|
1839
|
+
}
|
|
1840
|
+
async saveVault(walletAddress, vault, authMethod, webauthnCredential) {
|
|
1841
|
+
const storage = this.getStorage();
|
|
1842
|
+
if (!storage) return;
|
|
1843
|
+
const data = {
|
|
1844
|
+
vault,
|
|
1845
|
+
authMethod,
|
|
1846
|
+
walletAddress,
|
|
1847
|
+
webauthnCredential,
|
|
1848
|
+
createdAt: Date.now(),
|
|
1849
|
+
updatedAt: Date.now()
|
|
1850
|
+
};
|
|
1851
|
+
const key = this.getStorageKeyForWallet(walletAddress);
|
|
1852
|
+
storage.setItem(key, JSON.stringify(data));
|
|
1853
|
+
}
|
|
1854
|
+
async loadVault(walletAddress) {
|
|
1855
|
+
const storage = this.getStorage();
|
|
1856
|
+
if (!storage) return null;
|
|
1857
|
+
const key = this.getStorageKeyForWallet(walletAddress);
|
|
1858
|
+
const data = storage.getItem(key);
|
|
1859
|
+
if (!data) return null;
|
|
1860
|
+
try {
|
|
1861
|
+
return JSON.parse(data);
|
|
1862
|
+
} catch {
|
|
1863
|
+
return null;
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
async deleteVault(walletAddress) {
|
|
1867
|
+
const storage = this.getStorage();
|
|
1868
|
+
if (!storage) return;
|
|
1869
|
+
const key = this.getStorageKeyForWallet(walletAddress);
|
|
1870
|
+
storage.removeItem(key);
|
|
1871
|
+
}
|
|
1872
|
+
async hasVault(walletAddress) {
|
|
1873
|
+
const vault = await this.loadVault(walletAddress);
|
|
1874
|
+
return vault !== null;
|
|
1875
|
+
}
|
|
1876
|
+
async updateVaultTimestamp(walletAddress) {
|
|
1877
|
+
const storage = this.getStorage();
|
|
1878
|
+
if (!storage) return;
|
|
1879
|
+
const vault = await this.loadVault(walletAddress);
|
|
1880
|
+
if (!vault) return;
|
|
1881
|
+
vault.updatedAt = Date.now();
|
|
1882
|
+
const key = this.getStorageKeyForWallet(walletAddress);
|
|
1883
|
+
storage.setItem(key, JSON.stringify(vault));
|
|
1884
|
+
}
|
|
1885
|
+
async getAllWalletsWithVaults() {
|
|
1886
|
+
const storage = this.getStorage();
|
|
1887
|
+
if (!storage) return [];
|
|
1888
|
+
const wallets = [];
|
|
1889
|
+
const prefix = `${this.storageKey}_`;
|
|
1890
|
+
for (let i = 0; i < storage.length; i++) {
|
|
1891
|
+
const key = storage.key(i);
|
|
1892
|
+
if (key?.startsWith(prefix)) {
|
|
1893
|
+
const walletAddress = key.slice(prefix.length);
|
|
1894
|
+
wallets.push(walletAddress);
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
return wallets;
|
|
1898
|
+
}
|
|
1899
|
+
getStorageKeyForWallet(walletAddress) {
|
|
1900
|
+
return `${this.storageKey}_${walletAddress}`;
|
|
1901
|
+
}
|
|
1902
|
+
};
|
|
1903
|
+
var signlessStorage = new SignlessStorage();
|
|
1904
|
+
|
|
1905
|
+
// src/signless/context.tsx
|
|
1906
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
1907
|
+
var DEFAULT_CONFIG = {
|
|
1908
|
+
enabled: false,
|
|
1909
|
+
authMethod: "none",
|
|
1910
|
+
autoLockTimeout: 5 * 60 * 1e3,
|
|
1911
|
+
storageKey: "tonpay_signless_vault"
|
|
1912
|
+
};
|
|
1913
|
+
var DEFAULT_STATE = {
|
|
1914
|
+
status: "disabled",
|
|
1915
|
+
isEnabled: false,
|
|
1916
|
+
isSetup: false,
|
|
1917
|
+
isUnlocked: false,
|
|
1918
|
+
authMethod: "none",
|
|
1919
|
+
publicKey: null,
|
|
1920
|
+
walletAddress: null
|
|
1921
|
+
};
|
|
1922
|
+
var SignlessContext = React9.createContext(null);
|
|
1923
|
+
function SignlessProvider({ children, config }) {
|
|
1924
|
+
const walletAddress = useTonAddress3(true);
|
|
1925
|
+
const [state, setState] = React9.useState(DEFAULT_STATE);
|
|
1926
|
+
const [currentConfig, setCurrentConfig] = React9.useState({
|
|
1927
|
+
...DEFAULT_CONFIG,
|
|
1928
|
+
...config
|
|
1929
|
+
});
|
|
1930
|
+
const storageRef = React9.useRef(
|
|
1931
|
+
new SignlessStorage(currentConfig.storageKey)
|
|
1932
|
+
);
|
|
1933
|
+
const privateKeyRef = React9.useRef(null);
|
|
1934
|
+
const lockTimeoutRef = React9.useRef(
|
|
1935
|
+
null
|
|
1936
|
+
);
|
|
1937
|
+
const webauthnCredentialRef = React9.useRef(
|
|
1938
|
+
null
|
|
1939
|
+
);
|
|
1940
|
+
const [isBiometricAvailable, setIsBiometricAvailable] = React9.useState(false);
|
|
1941
|
+
React9.useEffect(() => {
|
|
1942
|
+
async function checkBiometric() {
|
|
1943
|
+
if (!isWebAuthnSupported()) {
|
|
1944
|
+
setIsBiometricAvailable(false);
|
|
1945
|
+
return;
|
|
1946
|
+
}
|
|
1947
|
+
try {
|
|
1948
|
+
const available = await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
|
|
1949
|
+
setIsBiometricAvailable(available);
|
|
1950
|
+
} catch {
|
|
1951
|
+
setIsBiometricAvailable(false);
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
checkBiometric();
|
|
1955
|
+
}, []);
|
|
1956
|
+
React9.useEffect(() => {
|
|
1957
|
+
async function loadVaultState() {
|
|
1958
|
+
if (!walletAddress) {
|
|
1959
|
+
setState({ ...DEFAULT_STATE });
|
|
1960
|
+
return;
|
|
1961
|
+
}
|
|
1962
|
+
if (!currentConfig.enabled) {
|
|
1963
|
+
setState({
|
|
1964
|
+
...DEFAULT_STATE,
|
|
1965
|
+
status: "disabled",
|
|
1966
|
+
walletAddress
|
|
1967
|
+
});
|
|
1968
|
+
return;
|
|
1969
|
+
}
|
|
1970
|
+
const vaultData = await storageRef.current.loadVault(walletAddress);
|
|
1971
|
+
if (!vaultData) {
|
|
1972
|
+
setState({
|
|
1973
|
+
...DEFAULT_STATE,
|
|
1974
|
+
status: "not_setup",
|
|
1975
|
+
isEnabled: true,
|
|
1976
|
+
walletAddress
|
|
1977
|
+
});
|
|
1978
|
+
return;
|
|
1979
|
+
}
|
|
1980
|
+
webauthnCredentialRef.current = vaultData.webauthnCredential || null;
|
|
1981
|
+
setState({
|
|
1982
|
+
status: "locked",
|
|
1983
|
+
isEnabled: true,
|
|
1984
|
+
isSetup: true,
|
|
1985
|
+
isUnlocked: false,
|
|
1986
|
+
authMethod: vaultData.authMethod,
|
|
1987
|
+
publicKey: vaultData.vault.publicKey,
|
|
1988
|
+
walletAddress
|
|
1989
|
+
});
|
|
1990
|
+
}
|
|
1991
|
+
loadVaultState();
|
|
1992
|
+
}, [walletAddress, currentConfig.enabled]);
|
|
1993
|
+
const resetLockTimeout = React9.useCallback(() => {
|
|
1994
|
+
if (lockTimeoutRef.current) {
|
|
1995
|
+
clearTimeout(lockTimeoutRef.current);
|
|
1996
|
+
}
|
|
1997
|
+
if (currentConfig.autoLockTimeout && currentConfig.autoLockTimeout > 0) {
|
|
1998
|
+
lockTimeoutRef.current = setTimeout(() => {
|
|
1999
|
+
if (privateKeyRef.current) {
|
|
2000
|
+
privateKeyRef.current.fill(0);
|
|
2001
|
+
privateKeyRef.current = null;
|
|
2002
|
+
}
|
|
2003
|
+
setState((prev) => ({
|
|
2004
|
+
...prev,
|
|
2005
|
+
status: "locked",
|
|
2006
|
+
isUnlocked: false
|
|
2007
|
+
}));
|
|
2008
|
+
}, currentConfig.autoLockTimeout);
|
|
2009
|
+
}
|
|
2010
|
+
}, [currentConfig.autoLockTimeout]);
|
|
2011
|
+
const setup = React9.useCallback(
|
|
2012
|
+
async (params) => {
|
|
2013
|
+
if (!walletAddress) {
|
|
2014
|
+
throw new Error("Wallet not connected");
|
|
2015
|
+
}
|
|
2016
|
+
setState((prev) => ({ ...prev, status: "setting_up" }));
|
|
363
2017
|
try {
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
2018
|
+
const keyPair = await generateKeyPair();
|
|
2019
|
+
let webauthnCredential;
|
|
2020
|
+
if (params.authMethod === "biometric") {
|
|
2021
|
+
webauthnCredential = await createWebAuthnCredential(walletAddress);
|
|
2022
|
+
webauthnCredentialRef.current = webauthnCredential;
|
|
2023
|
+
const signature = await getWebAuthnCredential(webauthnCredential);
|
|
2024
|
+
const signatureBytes = new Uint8Array(signature);
|
|
2025
|
+
const biometricPin = Array.from(signatureBytes.slice(0, 32)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
2026
|
+
const vault = await encryptPrivateKey(
|
|
2027
|
+
keyPair.privateKey,
|
|
2028
|
+
keyPair.publicKey,
|
|
2029
|
+
biometricPin
|
|
2030
|
+
);
|
|
2031
|
+
await storageRef.current.saveVault(
|
|
2032
|
+
walletAddress,
|
|
2033
|
+
vault,
|
|
2034
|
+
"biometric",
|
|
2035
|
+
webauthnCredential
|
|
2036
|
+
);
|
|
2037
|
+
privateKeyRef.current = keyPair.privateKey;
|
|
2038
|
+
} else if (params.authMethod === "pin") {
|
|
2039
|
+
if (!params.pin) {
|
|
2040
|
+
throw new Error("PIN is required for PIN authentication");
|
|
2041
|
+
}
|
|
2042
|
+
const vault = await encryptPrivateKey(
|
|
2043
|
+
keyPair.privateKey,
|
|
2044
|
+
keyPair.publicKey,
|
|
2045
|
+
params.pin
|
|
2046
|
+
);
|
|
2047
|
+
await storageRef.current.saveVault(walletAddress, vault, "pin");
|
|
2048
|
+
privateKeyRef.current = keyPair.privateKey;
|
|
2049
|
+
} else {
|
|
2050
|
+
throw new Error("Invalid authentication method");
|
|
2051
|
+
}
|
|
2052
|
+
setState({
|
|
2053
|
+
status: "unlocked",
|
|
2054
|
+
isEnabled: true,
|
|
2055
|
+
isSetup: true,
|
|
2056
|
+
isUnlocked: true,
|
|
2057
|
+
authMethod: params.authMethod,
|
|
2058
|
+
publicKey: arrayBufferToBase642(keyPair.publicKey.buffer),
|
|
2059
|
+
walletAddress
|
|
371
2060
|
});
|
|
372
|
-
|
|
2061
|
+
resetLockTimeout();
|
|
373
2062
|
} catch (error) {
|
|
374
|
-
|
|
2063
|
+
setState((prev) => ({
|
|
2064
|
+
...prev,
|
|
2065
|
+
status: prev.isSetup ? "locked" : "not_setup"
|
|
2066
|
+
}));
|
|
375
2067
|
throw error;
|
|
376
2068
|
}
|
|
377
2069
|
},
|
|
378
|
-
[
|
|
2070
|
+
[walletAddress, resetLockTimeout]
|
|
379
2071
|
);
|
|
380
|
-
|
|
2072
|
+
const unlock = React9.useCallback(
|
|
2073
|
+
async (params) => {
|
|
2074
|
+
if (!walletAddress) {
|
|
2075
|
+
throw new Error("Wallet not connected");
|
|
2076
|
+
}
|
|
2077
|
+
const vaultData = await storageRef.current.loadVault(walletAddress);
|
|
2078
|
+
if (!vaultData) {
|
|
2079
|
+
throw new Error("No signless vault found");
|
|
2080
|
+
}
|
|
2081
|
+
try {
|
|
2082
|
+
let pin;
|
|
2083
|
+
if (vaultData.authMethod === "biometric") {
|
|
2084
|
+
if (!vaultData.webauthnCredential) {
|
|
2085
|
+
throw new Error("WebAuthn credential not found");
|
|
2086
|
+
}
|
|
2087
|
+
const signature = await getWebAuthnCredential(
|
|
2088
|
+
vaultData.webauthnCredential
|
|
2089
|
+
);
|
|
2090
|
+
const signatureBytes = new Uint8Array(signature);
|
|
2091
|
+
pin = Array.from(signatureBytes.slice(0, 32)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
2092
|
+
} else if (vaultData.authMethod === "pin") {
|
|
2093
|
+
if (!params.pin) {
|
|
2094
|
+
throw new Error("PIN is required");
|
|
2095
|
+
}
|
|
2096
|
+
pin = params.pin;
|
|
2097
|
+
} else {
|
|
2098
|
+
throw new Error("Invalid authentication method");
|
|
2099
|
+
}
|
|
2100
|
+
const privateKey = await decryptPrivateKey(vaultData.vault, pin);
|
|
2101
|
+
privateKeyRef.current = privateKey;
|
|
2102
|
+
setState((prev) => ({
|
|
2103
|
+
...prev,
|
|
2104
|
+
status: "unlocked",
|
|
2105
|
+
isUnlocked: true
|
|
2106
|
+
}));
|
|
2107
|
+
resetLockTimeout();
|
|
2108
|
+
} catch (error) {
|
|
2109
|
+
throw new Error("Failed to unlock vault");
|
|
2110
|
+
}
|
|
2111
|
+
},
|
|
2112
|
+
[walletAddress, resetLockTimeout]
|
|
2113
|
+
);
|
|
2114
|
+
const lock = React9.useCallback(() => {
|
|
2115
|
+
if (lockTimeoutRef.current) {
|
|
2116
|
+
clearTimeout(lockTimeoutRef.current);
|
|
2117
|
+
}
|
|
2118
|
+
if (privateKeyRef.current) {
|
|
2119
|
+
privateKeyRef.current.fill(0);
|
|
2120
|
+
privateKeyRef.current = null;
|
|
2121
|
+
}
|
|
2122
|
+
setState((prev) => ({
|
|
2123
|
+
...prev,
|
|
2124
|
+
status: "locked",
|
|
2125
|
+
isUnlocked: false
|
|
2126
|
+
}));
|
|
2127
|
+
}, []);
|
|
2128
|
+
const reset = React9.useCallback(async () => {
|
|
2129
|
+
if (!walletAddress) return;
|
|
2130
|
+
if (lockTimeoutRef.current) {
|
|
2131
|
+
clearTimeout(lockTimeoutRef.current);
|
|
2132
|
+
}
|
|
2133
|
+
if (privateKeyRef.current) {
|
|
2134
|
+
privateKeyRef.current.fill(0);
|
|
2135
|
+
privateKeyRef.current = null;
|
|
2136
|
+
}
|
|
2137
|
+
webauthnCredentialRef.current = null;
|
|
2138
|
+
await storageRef.current.deleteVault(walletAddress);
|
|
2139
|
+
setState({
|
|
2140
|
+
...DEFAULT_STATE,
|
|
2141
|
+
status: currentConfig.enabled ? "not_setup" : "disabled",
|
|
2142
|
+
isEnabled: currentConfig.enabled,
|
|
2143
|
+
walletAddress
|
|
2144
|
+
});
|
|
2145
|
+
}, [walletAddress, currentConfig.enabled]);
|
|
2146
|
+
const signPayload = React9.useCallback(
|
|
2147
|
+
async (params) => {
|
|
2148
|
+
if (!privateKeyRef.current) {
|
|
2149
|
+
throw new Error("Signless is locked. Please unlock first.");
|
|
2150
|
+
}
|
|
2151
|
+
if (!state.publicKey) {
|
|
2152
|
+
throw new Error("Public key not available");
|
|
2153
|
+
}
|
|
2154
|
+
resetLockTimeout();
|
|
2155
|
+
const reference = params.reference || generateReference();
|
|
2156
|
+
const validUntil = params.validUntil || Math.floor(Date.now() / 1e3) + 300;
|
|
2157
|
+
const payloadData = {
|
|
2158
|
+
recipient: params.recipient,
|
|
2159
|
+
amount: params.amount,
|
|
2160
|
+
token: params.token || "TON",
|
|
2161
|
+
payload: params.payload || "",
|
|
2162
|
+
reference,
|
|
2163
|
+
validUntil
|
|
2164
|
+
};
|
|
2165
|
+
const encoder = new TextEncoder();
|
|
2166
|
+
const payloadBytes = encoder.encode(JSON.stringify(payloadData));
|
|
2167
|
+
const signature = await signMessage(privateKeyRef.current, payloadBytes);
|
|
2168
|
+
return {
|
|
2169
|
+
payload: payloadBytes,
|
|
2170
|
+
signature,
|
|
2171
|
+
publicKey: state.publicKey,
|
|
2172
|
+
reference,
|
|
2173
|
+
validUntil
|
|
2174
|
+
};
|
|
2175
|
+
},
|
|
2176
|
+
[state.publicKey, resetLockTimeout]
|
|
2177
|
+
);
|
|
2178
|
+
const updateConfig = React9.useCallback(
|
|
2179
|
+
(newConfig) => {
|
|
2180
|
+
setCurrentConfig((prev) => ({ ...prev, ...newConfig }));
|
|
2181
|
+
},
|
|
2182
|
+
[]
|
|
2183
|
+
);
|
|
2184
|
+
React9.useEffect(() => {
|
|
2185
|
+
return () => {
|
|
2186
|
+
if (lockTimeoutRef.current) {
|
|
2187
|
+
clearTimeout(lockTimeoutRef.current);
|
|
2188
|
+
}
|
|
2189
|
+
if (privateKeyRef.current) {
|
|
2190
|
+
privateKeyRef.current.fill(0);
|
|
2191
|
+
privateKeyRef.current = null;
|
|
2192
|
+
}
|
|
2193
|
+
};
|
|
2194
|
+
}, []);
|
|
2195
|
+
const contextValue = {
|
|
2196
|
+
state,
|
|
2197
|
+
config: currentConfig,
|
|
2198
|
+
setup,
|
|
2199
|
+
unlock,
|
|
2200
|
+
lock,
|
|
2201
|
+
reset,
|
|
2202
|
+
signPayload,
|
|
2203
|
+
updateConfig,
|
|
2204
|
+
isBiometricAvailable
|
|
2205
|
+
};
|
|
2206
|
+
return /* @__PURE__ */ jsx7(SignlessContext.Provider, { value: contextValue, children });
|
|
2207
|
+
}
|
|
2208
|
+
function useSignless() {
|
|
2209
|
+
const context = React9.useContext(SignlessContext);
|
|
2210
|
+
if (!context) {
|
|
2211
|
+
throw new Error("useSignless must be used within a SignlessProvider");
|
|
2212
|
+
}
|
|
2213
|
+
return context;
|
|
2214
|
+
}
|
|
2215
|
+
function generateReference() {
|
|
2216
|
+
const bytes = new Uint8Array(16);
|
|
2217
|
+
crypto.getRandomValues(bytes);
|
|
2218
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
2219
|
+
}
|
|
2220
|
+
function arrayBufferToBase642(buffer) {
|
|
2221
|
+
const bytes = new Uint8Array(buffer);
|
|
2222
|
+
let binary = "";
|
|
2223
|
+
for (let i = 0; i < bytes.byteLength; i++) {
|
|
2224
|
+
binary += String.fromCharCode(bytes[i]);
|
|
2225
|
+
}
|
|
2226
|
+
return btoa(binary);
|
|
2227
|
+
}
|
|
2228
|
+
|
|
2229
|
+
// src/signless/hooks/useTonPaySignless.ts
|
|
2230
|
+
import * as React10 from "react";
|
|
2231
|
+
function useTonPaySignless() {
|
|
2232
|
+
const { pay, address } = useTonPay();
|
|
2233
|
+
const signlessContext = useSignless();
|
|
2234
|
+
const {
|
|
2235
|
+
state,
|
|
2236
|
+
config,
|
|
2237
|
+
setup,
|
|
2238
|
+
unlock,
|
|
2239
|
+
lock,
|
|
2240
|
+
reset,
|
|
2241
|
+
signPayload,
|
|
2242
|
+
updateConfig,
|
|
2243
|
+
isBiometricAvailable
|
|
2244
|
+
} = signlessContext;
|
|
2245
|
+
const paySignless = React10.useCallback(
|
|
2246
|
+
async (params) => {
|
|
2247
|
+
if (!config.enabled) {
|
|
2248
|
+
throw new Error("Signless is not enabled");
|
|
2249
|
+
}
|
|
2250
|
+
if (!state.isSetup) {
|
|
2251
|
+
throw new Error("Signless is not set up. Please complete setup first.");
|
|
2252
|
+
}
|
|
2253
|
+
if (!state.isUnlocked) {
|
|
2254
|
+
throw new Error("Signless is locked. Please unlock first.");
|
|
2255
|
+
}
|
|
2256
|
+
return signPayload(params);
|
|
2257
|
+
},
|
|
2258
|
+
[config.enabled, state.isSetup, state.isUnlocked, signPayload]
|
|
2259
|
+
);
|
|
2260
|
+
const requiresUnlock = React10.useMemo(() => {
|
|
2261
|
+
return config.enabled && state.isSetup && !state.isUnlocked;
|
|
2262
|
+
}, [config.enabled, state.isSetup, state.isUnlocked]);
|
|
2263
|
+
return {
|
|
2264
|
+
pay,
|
|
2265
|
+
paySignless,
|
|
2266
|
+
address,
|
|
2267
|
+
signless: {
|
|
2268
|
+
state,
|
|
2269
|
+
config,
|
|
2270
|
+
setup,
|
|
2271
|
+
unlock,
|
|
2272
|
+
lock,
|
|
2273
|
+
reset,
|
|
2274
|
+
updateConfig,
|
|
2275
|
+
isBiometricAvailable,
|
|
2276
|
+
isEnabled: config.enabled,
|
|
2277
|
+
isSetup: state.isSetup,
|
|
2278
|
+
isUnlocked: state.isUnlocked,
|
|
2279
|
+
requiresUnlock
|
|
2280
|
+
}
|
|
2281
|
+
};
|
|
2282
|
+
}
|
|
2283
|
+
|
|
2284
|
+
// src/signless/hooks/useSignlessModal.ts
|
|
2285
|
+
import * as React11 from "react";
|
|
2286
|
+
function useSignlessModal() {
|
|
2287
|
+
const { state, config } = useSignless();
|
|
2288
|
+
const [modalType, setModalType] = React11.useState(null);
|
|
2289
|
+
const openSetup = React11.useCallback(() => {
|
|
2290
|
+
if (!config.enabled) {
|
|
2291
|
+
console.warn("Signless is not enabled");
|
|
2292
|
+
return;
|
|
2293
|
+
}
|
|
2294
|
+
setModalType("setup");
|
|
2295
|
+
}, [config.enabled]);
|
|
2296
|
+
const openUnlock = React11.useCallback(() => {
|
|
2297
|
+
if (!config.enabled) {
|
|
2298
|
+
console.warn("Signless is not enabled");
|
|
2299
|
+
return;
|
|
2300
|
+
}
|
|
2301
|
+
if (!state.isSetup) {
|
|
2302
|
+
console.warn("Signless is not set up");
|
|
2303
|
+
return;
|
|
2304
|
+
}
|
|
2305
|
+
setModalType("unlock");
|
|
2306
|
+
}, [config.enabled, state.isSetup]);
|
|
2307
|
+
const close = React11.useCallback(() => {
|
|
2308
|
+
setModalType(null);
|
|
2309
|
+
}, []);
|
|
2310
|
+
const onSetupComplete = React11.useCallback(() => {
|
|
2311
|
+
setModalType(null);
|
|
2312
|
+
}, []);
|
|
2313
|
+
const onUnlockComplete = React11.useCallback(() => {
|
|
2314
|
+
setModalType(null);
|
|
2315
|
+
}, []);
|
|
2316
|
+
return {
|
|
2317
|
+
modalType,
|
|
2318
|
+
isOpen: modalType !== null,
|
|
2319
|
+
openSetup,
|
|
2320
|
+
openUnlock,
|
|
2321
|
+
close,
|
|
2322
|
+
onSetupComplete,
|
|
2323
|
+
onUnlockComplete
|
|
2324
|
+
};
|
|
2325
|
+
}
|
|
2326
|
+
|
|
2327
|
+
// src/signless/components/PinInput.tsx
|
|
2328
|
+
import * as React12 from "react";
|
|
2329
|
+
|
|
2330
|
+
// src/signless/components/styles.ts
|
|
2331
|
+
var pinInputStyles = {
|
|
2332
|
+
container: {
|
|
2333
|
+
display: "flex",
|
|
2334
|
+
flexDirection: "column",
|
|
2335
|
+
alignItems: "center",
|
|
2336
|
+
padding: "24px",
|
|
2337
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
2338
|
+
color: "#ffffff",
|
|
2339
|
+
minWidth: "280px"
|
|
2340
|
+
},
|
|
2341
|
+
header: {
|
|
2342
|
+
textAlign: "center",
|
|
2343
|
+
marginBottom: "32px"
|
|
2344
|
+
},
|
|
2345
|
+
title: {
|
|
2346
|
+
fontSize: "20px",
|
|
2347
|
+
fontWeight: 600,
|
|
2348
|
+
margin: "0 0 8px 0",
|
|
2349
|
+
color: "#ffffff"
|
|
2350
|
+
},
|
|
2351
|
+
subtitle: {
|
|
2352
|
+
fontSize: "14px",
|
|
2353
|
+
color: "rgba(255, 255, 255, 0.6)",
|
|
2354
|
+
margin: 0
|
|
2355
|
+
},
|
|
2356
|
+
hiddenInput: {
|
|
2357
|
+
position: "absolute",
|
|
2358
|
+
opacity: 0,
|
|
2359
|
+
pointerEvents: "none"
|
|
2360
|
+
},
|
|
2361
|
+
dotsContainer: {
|
|
2362
|
+
display: "flex",
|
|
2363
|
+
gap: "16px",
|
|
2364
|
+
marginBottom: "32px",
|
|
2365
|
+
cursor: "pointer"
|
|
2366
|
+
},
|
|
2367
|
+
dotsContainerShake: {
|
|
2368
|
+
animation: "tonpay-pin-shake 0.5s ease-in-out"
|
|
2369
|
+
},
|
|
2370
|
+
dot: {
|
|
2371
|
+
width: "14px",
|
|
2372
|
+
height: "14px",
|
|
2373
|
+
borderRadius: "50%",
|
|
2374
|
+
backgroundColor: "rgba(255, 255, 255, 0.2)",
|
|
2375
|
+
border: "2px solid rgba(255, 255, 255, 0.3)",
|
|
2376
|
+
transition: "all 0.15s ease"
|
|
2377
|
+
},
|
|
2378
|
+
dotFilled: {
|
|
2379
|
+
backgroundColor: "#0098EA",
|
|
2380
|
+
borderColor: "#0098EA",
|
|
2381
|
+
transform: "scale(1.1)"
|
|
2382
|
+
},
|
|
2383
|
+
dotLoading: {
|
|
2384
|
+
animation: "tonpay-pin-pulse 1s ease-in-out infinite"
|
|
2385
|
+
},
|
|
2386
|
+
error: {
|
|
2387
|
+
fontSize: "14px",
|
|
2388
|
+
color: "#FF5252",
|
|
2389
|
+
margin: "-16px 0 24px 0",
|
|
2390
|
+
textAlign: "center"
|
|
2391
|
+
},
|
|
2392
|
+
keypad: {
|
|
2393
|
+
display: "grid",
|
|
2394
|
+
gridTemplateColumns: "repeat(3, 1fr)",
|
|
2395
|
+
gap: "8px",
|
|
2396
|
+
width: "100%",
|
|
2397
|
+
maxWidth: "260px"
|
|
2398
|
+
},
|
|
2399
|
+
keypadButton: {
|
|
2400
|
+
width: "72px",
|
|
2401
|
+
height: "56px",
|
|
2402
|
+
fontSize: "24px",
|
|
2403
|
+
fontWeight: 500,
|
|
2404
|
+
color: "#ffffff",
|
|
2405
|
+
backgroundColor: "transparent",
|
|
2406
|
+
border: "none",
|
|
2407
|
+
borderRadius: "12px",
|
|
2408
|
+
cursor: "pointer",
|
|
2409
|
+
transition: "background-color 0.15s ease",
|
|
2410
|
+
display: "flex",
|
|
2411
|
+
alignItems: "center",
|
|
2412
|
+
justifyContent: "center"
|
|
2413
|
+
},
|
|
2414
|
+
keypadButtonEmpty: {
|
|
2415
|
+
width: "72px",
|
|
2416
|
+
height: "56px",
|
|
2417
|
+
display: "flex",
|
|
2418
|
+
alignItems: "center",
|
|
2419
|
+
justifyContent: "center"
|
|
2420
|
+
},
|
|
2421
|
+
biometricButton: {
|
|
2422
|
+
width: "56px",
|
|
2423
|
+
height: "56px",
|
|
2424
|
+
fontSize: "24px",
|
|
2425
|
+
color: "#0098EA",
|
|
2426
|
+
backgroundColor: "transparent",
|
|
2427
|
+
border: "none",
|
|
2428
|
+
borderRadius: "12px",
|
|
2429
|
+
cursor: "pointer",
|
|
2430
|
+
transition: "background-color 0.15s ease",
|
|
2431
|
+
display: "flex",
|
|
2432
|
+
alignItems: "center",
|
|
2433
|
+
justifyContent: "center"
|
|
2434
|
+
},
|
|
2435
|
+
backspaceButton: {
|
|
2436
|
+
width: "72px",
|
|
2437
|
+
height: "56px",
|
|
2438
|
+
fontSize: "24px",
|
|
2439
|
+
color: "rgba(255, 255, 255, 0.6)",
|
|
2440
|
+
backgroundColor: "transparent",
|
|
2441
|
+
border: "none",
|
|
2442
|
+
borderRadius: "12px",
|
|
2443
|
+
cursor: "pointer",
|
|
2444
|
+
transition: "all 0.15s ease",
|
|
2445
|
+
display: "flex",
|
|
2446
|
+
alignItems: "center",
|
|
2447
|
+
justifyContent: "center"
|
|
2448
|
+
},
|
|
2449
|
+
cancelButton: {
|
|
2450
|
+
marginTop: "24px",
|
|
2451
|
+
padding: "12px 32px",
|
|
2452
|
+
fontSize: "16px",
|
|
2453
|
+
fontWeight: 500,
|
|
2454
|
+
color: "rgba(255, 255, 255, 0.6)",
|
|
2455
|
+
backgroundColor: "transparent",
|
|
2456
|
+
border: "none",
|
|
2457
|
+
borderRadius: "8px",
|
|
2458
|
+
cursor: "pointer",
|
|
2459
|
+
transition: "color 0.15s ease"
|
|
2460
|
+
}
|
|
2461
|
+
};
|
|
2462
|
+
var setupModalStyles = {
|
|
2463
|
+
container: {
|
|
2464
|
+
display: "flex",
|
|
2465
|
+
flexDirection: "column",
|
|
2466
|
+
alignItems: "center",
|
|
2467
|
+
padding: "24px",
|
|
2468
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
2469
|
+
color: "#ffffff"
|
|
2470
|
+
},
|
|
2471
|
+
header: {
|
|
2472
|
+
textAlign: "center",
|
|
2473
|
+
marginBottom: "24px"
|
|
2474
|
+
},
|
|
2475
|
+
title: {
|
|
2476
|
+
fontSize: "22px",
|
|
2477
|
+
fontWeight: 600,
|
|
2478
|
+
margin: "0 0 8px 0",
|
|
2479
|
+
color: "#ffffff"
|
|
2480
|
+
},
|
|
2481
|
+
subtitle: {
|
|
2482
|
+
fontSize: "14px",
|
|
2483
|
+
color: "rgba(255, 255, 255, 0.6)",
|
|
2484
|
+
margin: 0,
|
|
2485
|
+
lineHeight: 1.5,
|
|
2486
|
+
maxWidth: "280px"
|
|
2487
|
+
},
|
|
2488
|
+
methodSelector: {
|
|
2489
|
+
display: "flex",
|
|
2490
|
+
flexDirection: "column",
|
|
2491
|
+
gap: "12px",
|
|
2492
|
+
width: "100%",
|
|
2493
|
+
marginBottom: "24px"
|
|
2494
|
+
},
|
|
2495
|
+
methodButton: {
|
|
2496
|
+
display: "flex",
|
|
2497
|
+
alignItems: "center",
|
|
2498
|
+
gap: "16px",
|
|
2499
|
+
padding: "16px",
|
|
2500
|
+
backgroundColor: "rgba(255, 255, 255, 0.05)",
|
|
2501
|
+
border: "1px solid rgba(255, 255, 255, 0.1)",
|
|
2502
|
+
borderRadius: "12px",
|
|
2503
|
+
cursor: "pointer",
|
|
2504
|
+
transition: "all 0.15s ease",
|
|
2505
|
+
textAlign: "left",
|
|
2506
|
+
width: "100%"
|
|
2507
|
+
},
|
|
2508
|
+
methodButtonSelected: {
|
|
2509
|
+
backgroundColor: "rgba(0, 152, 234, 0.15)",
|
|
2510
|
+
borderColor: "#0098EA"
|
|
2511
|
+
},
|
|
2512
|
+
methodIcon: {
|
|
2513
|
+
width: "48px",
|
|
2514
|
+
height: "48px",
|
|
2515
|
+
borderRadius: "12px",
|
|
2516
|
+
backgroundColor: "rgba(255, 255, 255, 0.1)",
|
|
2517
|
+
display: "flex",
|
|
2518
|
+
alignItems: "center",
|
|
2519
|
+
justifyContent: "center",
|
|
2520
|
+
color: "#0098EA",
|
|
2521
|
+
flexShrink: 0
|
|
2522
|
+
},
|
|
2523
|
+
methodContent: {
|
|
2524
|
+
flex: 1,
|
|
2525
|
+
minWidth: 0
|
|
2526
|
+
},
|
|
2527
|
+
methodTitle: {
|
|
2528
|
+
fontSize: "16px",
|
|
2529
|
+
fontWeight: 600,
|
|
2530
|
+
color: "#ffffff",
|
|
2531
|
+
margin: "0 0 4px 0"
|
|
2532
|
+
},
|
|
2533
|
+
methodDescription: {
|
|
2534
|
+
fontSize: "13px",
|
|
2535
|
+
color: "rgba(255, 255, 255, 0.5)",
|
|
2536
|
+
margin: 0,
|
|
2537
|
+
lineHeight: 1.4
|
|
2538
|
+
},
|
|
2539
|
+
continueButton: {
|
|
2540
|
+
width: "100%",
|
|
2541
|
+
padding: "14px 24px",
|
|
2542
|
+
fontSize: "16px",
|
|
2543
|
+
fontWeight: 600,
|
|
2544
|
+
color: "#ffffff",
|
|
2545
|
+
backgroundColor: "#0098EA",
|
|
2546
|
+
border: "none",
|
|
2547
|
+
borderRadius: "12px",
|
|
2548
|
+
cursor: "pointer",
|
|
2549
|
+
transition: "opacity 0.15s ease"
|
|
2550
|
+
},
|
|
2551
|
+
continueButtonDisabled: {
|
|
2552
|
+
opacity: 0.5,
|
|
2553
|
+
cursor: "not-allowed"
|
|
2554
|
+
},
|
|
2555
|
+
stepIndicator: {
|
|
2556
|
+
display: "flex",
|
|
2557
|
+
gap: "8px",
|
|
2558
|
+
marginBottom: "24px"
|
|
2559
|
+
},
|
|
2560
|
+
stepDot: {
|
|
2561
|
+
width: "8px",
|
|
2562
|
+
height: "8px",
|
|
2563
|
+
borderRadius: "50%",
|
|
2564
|
+
backgroundColor: "rgba(255, 255, 255, 0.2)"
|
|
2565
|
+
},
|
|
2566
|
+
stepDotActive: {
|
|
2567
|
+
backgroundColor: "#0098EA"
|
|
2568
|
+
}
|
|
2569
|
+
};
|
|
2570
|
+
var unlockModalStyles = {
|
|
2571
|
+
container: {
|
|
2572
|
+
display: "flex",
|
|
2573
|
+
flexDirection: "column",
|
|
2574
|
+
alignItems: "center",
|
|
2575
|
+
padding: "24px",
|
|
2576
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
2577
|
+
color: "#ffffff"
|
|
2578
|
+
},
|
|
2579
|
+
biometricPrompt: {
|
|
2580
|
+
display: "flex",
|
|
2581
|
+
flexDirection: "column",
|
|
2582
|
+
alignItems: "center",
|
|
2583
|
+
padding: "32px",
|
|
2584
|
+
textAlign: "center"
|
|
2585
|
+
},
|
|
2586
|
+
biometricIcon: {
|
|
2587
|
+
width: "80px",
|
|
2588
|
+
height: "80px",
|
|
2589
|
+
borderRadius: "20px",
|
|
2590
|
+
backgroundColor: "rgba(0, 152, 234, 0.15)",
|
|
2591
|
+
display: "flex",
|
|
2592
|
+
alignItems: "center",
|
|
2593
|
+
justifyContent: "center",
|
|
2594
|
+
color: "#0098EA",
|
|
2595
|
+
marginBottom: "24px"
|
|
2596
|
+
},
|
|
2597
|
+
biometricTitle: {
|
|
2598
|
+
fontSize: "20px",
|
|
2599
|
+
fontWeight: 600,
|
|
2600
|
+
margin: "0 0 8px 0",
|
|
2601
|
+
color: "#ffffff"
|
|
2602
|
+
},
|
|
2603
|
+
biometricSubtitle: {
|
|
2604
|
+
fontSize: "14px",
|
|
2605
|
+
color: "rgba(255, 255, 255, 0.6)",
|
|
2606
|
+
margin: "0 0 24px 0"
|
|
2607
|
+
},
|
|
2608
|
+
usePinButton: {
|
|
2609
|
+
padding: "12px 24px",
|
|
2610
|
+
fontSize: "14px",
|
|
2611
|
+
fontWeight: 500,
|
|
2612
|
+
color: "#0098EA",
|
|
2613
|
+
backgroundColor: "transparent",
|
|
2614
|
+
border: "none",
|
|
2615
|
+
cursor: "pointer",
|
|
2616
|
+
transition: "opacity 0.15s ease"
|
|
2617
|
+
}
|
|
2618
|
+
};
|
|
2619
|
+
|
|
2620
|
+
// src/signless/components/PinInput.tsx
|
|
2621
|
+
import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
2622
|
+
var PIN_LENGTH = 6;
|
|
2623
|
+
var PinInput = ({
|
|
2624
|
+
length = PIN_LENGTH,
|
|
2625
|
+
onComplete,
|
|
2626
|
+
onCancel,
|
|
2627
|
+
title = "Enter PIN",
|
|
2628
|
+
subtitle,
|
|
2629
|
+
error,
|
|
2630
|
+
isLoading = false,
|
|
2631
|
+
showBiometric = false,
|
|
2632
|
+
onBiometricPress
|
|
2633
|
+
}) => {
|
|
2634
|
+
const [pin, setPin] = React12.useState("");
|
|
2635
|
+
const [shake, setShake] = React12.useState(false);
|
|
2636
|
+
const inputRef = React12.useRef(null);
|
|
2637
|
+
React12.useEffect(() => {
|
|
2638
|
+
if (error) {
|
|
2639
|
+
setShake(true);
|
|
2640
|
+
setPin("");
|
|
2641
|
+
const timer = setTimeout(() => setShake(false), 500);
|
|
2642
|
+
return () => clearTimeout(timer);
|
|
2643
|
+
}
|
|
2644
|
+
}, [error]);
|
|
2645
|
+
React12.useEffect(() => {
|
|
2646
|
+
inputRef.current?.focus();
|
|
2647
|
+
}, []);
|
|
2648
|
+
const handleChange = (e) => {
|
|
2649
|
+
const value = e.target.value.replace(/\D/g, "").slice(0, length);
|
|
2650
|
+
setPin(value);
|
|
2651
|
+
if (value.length === length) {
|
|
2652
|
+
onComplete(value);
|
|
2653
|
+
}
|
|
2654
|
+
};
|
|
2655
|
+
const handleKeyDown = (e) => {
|
|
2656
|
+
if (e.key === "Escape" && onCancel) {
|
|
2657
|
+
onCancel();
|
|
2658
|
+
}
|
|
2659
|
+
};
|
|
2660
|
+
const handleDotClick = () => {
|
|
2661
|
+
inputRef.current?.focus();
|
|
2662
|
+
};
|
|
2663
|
+
const handleKeyPress = (digit) => {
|
|
2664
|
+
if (pin.length >= length || isLoading) return;
|
|
2665
|
+
const newPin = pin + digit;
|
|
2666
|
+
setPin(newPin);
|
|
2667
|
+
if (newPin.length === length) {
|
|
2668
|
+
onComplete(newPin);
|
|
2669
|
+
}
|
|
2670
|
+
};
|
|
2671
|
+
const handleBackspace = () => {
|
|
2672
|
+
if (isLoading) return;
|
|
2673
|
+
setPin((prev) => prev.slice(0, -1));
|
|
2674
|
+
};
|
|
2675
|
+
return /* @__PURE__ */ jsxs6("div", { style: pinInputStyles.container, children: [
|
|
2676
|
+
/* @__PURE__ */ jsx8("style", { children: keyframes }),
|
|
2677
|
+
/* @__PURE__ */ jsxs6("div", { style: pinInputStyles.header, children: [
|
|
2678
|
+
/* @__PURE__ */ jsx8("h2", { style: pinInputStyles.title, children: title }),
|
|
2679
|
+
subtitle && /* @__PURE__ */ jsx8("p", { style: pinInputStyles.subtitle, children: subtitle })
|
|
2680
|
+
] }),
|
|
2681
|
+
/* @__PURE__ */ jsx8(
|
|
2682
|
+
"input",
|
|
2683
|
+
{
|
|
2684
|
+
ref: inputRef,
|
|
2685
|
+
type: "text",
|
|
2686
|
+
inputMode: "numeric",
|
|
2687
|
+
pattern: "[0-9]*",
|
|
2688
|
+
value: pin,
|
|
2689
|
+
onChange: handleChange,
|
|
2690
|
+
onKeyDown: handleKeyDown,
|
|
2691
|
+
maxLength: length,
|
|
2692
|
+
autoComplete: "off",
|
|
2693
|
+
style: pinInputStyles.hiddenInput,
|
|
2694
|
+
disabled: isLoading
|
|
2695
|
+
}
|
|
2696
|
+
),
|
|
2697
|
+
/* @__PURE__ */ jsx8(
|
|
2698
|
+
"div",
|
|
2699
|
+
{
|
|
2700
|
+
style: {
|
|
2701
|
+
...pinInputStyles.dotsContainer,
|
|
2702
|
+
...shake ? pinInputStyles.dotsContainerShake : {}
|
|
2703
|
+
},
|
|
2704
|
+
onClick: handleDotClick,
|
|
2705
|
+
children: Array.from({ length }, (_, i) => /* @__PURE__ */ jsx8(
|
|
2706
|
+
"div",
|
|
2707
|
+
{
|
|
2708
|
+
style: {
|
|
2709
|
+
...pinInputStyles.dot,
|
|
2710
|
+
...i < pin.length ? pinInputStyles.dotFilled : {},
|
|
2711
|
+
...isLoading ? pinInputStyles.dotLoading : {}
|
|
2712
|
+
}
|
|
2713
|
+
},
|
|
2714
|
+
i
|
|
2715
|
+
))
|
|
2716
|
+
}
|
|
2717
|
+
),
|
|
2718
|
+
error && /* @__PURE__ */ jsx8("p", { style: pinInputStyles.error, children: error }),
|
|
2719
|
+
/* @__PURE__ */ jsxs6("div", { style: pinInputStyles.keypad, children: [
|
|
2720
|
+
[1, 2, 3, 4, 5, 6, 7, 8, 9].map((digit) => /* @__PURE__ */ jsx8(
|
|
2721
|
+
"button",
|
|
2722
|
+
{
|
|
2723
|
+
type: "button",
|
|
2724
|
+
style: pinInputStyles.keypadButton,
|
|
2725
|
+
onClick: () => handleKeyPress(String(digit)),
|
|
2726
|
+
disabled: isLoading,
|
|
2727
|
+
onMouseEnter: (e) => {
|
|
2728
|
+
e.target.style.backgroundColor = "rgba(255, 255, 255, 0.1)";
|
|
2729
|
+
},
|
|
2730
|
+
onMouseLeave: (e) => {
|
|
2731
|
+
e.target.style.backgroundColor = "transparent";
|
|
2732
|
+
},
|
|
2733
|
+
children: digit
|
|
2734
|
+
},
|
|
2735
|
+
digit
|
|
2736
|
+
)),
|
|
2737
|
+
/* @__PURE__ */ jsx8("div", { style: pinInputStyles.keypadButtonEmpty, children: showBiometric && onBiometricPress && /* @__PURE__ */ jsx8(
|
|
2738
|
+
"button",
|
|
2739
|
+
{
|
|
2740
|
+
type: "button",
|
|
2741
|
+
style: pinInputStyles.biometricButton,
|
|
2742
|
+
onClick: onBiometricPress,
|
|
2743
|
+
disabled: isLoading,
|
|
2744
|
+
"aria-label": "Use biometric authentication",
|
|
2745
|
+
children: /* @__PURE__ */ jsx8(BiometricIcon, {})
|
|
2746
|
+
}
|
|
2747
|
+
) }),
|
|
2748
|
+
/* @__PURE__ */ jsx8(
|
|
2749
|
+
"button",
|
|
2750
|
+
{
|
|
2751
|
+
type: "button",
|
|
2752
|
+
style: pinInputStyles.keypadButton,
|
|
2753
|
+
onClick: () => handleKeyPress("0"),
|
|
2754
|
+
disabled: isLoading,
|
|
2755
|
+
onMouseEnter: (e) => {
|
|
2756
|
+
e.target.style.backgroundColor = "rgba(255, 255, 255, 0.1)";
|
|
2757
|
+
},
|
|
2758
|
+
onMouseLeave: (e) => {
|
|
2759
|
+
e.target.style.backgroundColor = "transparent";
|
|
2760
|
+
},
|
|
2761
|
+
children: "0"
|
|
2762
|
+
}
|
|
2763
|
+
),
|
|
2764
|
+
/* @__PURE__ */ jsx8(
|
|
2765
|
+
"button",
|
|
2766
|
+
{
|
|
2767
|
+
type: "button",
|
|
2768
|
+
style: pinInputStyles.backspaceButton,
|
|
2769
|
+
onClick: handleBackspace,
|
|
2770
|
+
disabled: isLoading || pin.length === 0,
|
|
2771
|
+
"aria-label": "Backspace",
|
|
2772
|
+
children: /* @__PURE__ */ jsx8(BackspaceIcon, {})
|
|
2773
|
+
}
|
|
2774
|
+
)
|
|
2775
|
+
] }),
|
|
2776
|
+
onCancel && /* @__PURE__ */ jsx8(
|
|
2777
|
+
"button",
|
|
2778
|
+
{
|
|
2779
|
+
type: "button",
|
|
2780
|
+
style: pinInputStyles.cancelButton,
|
|
2781
|
+
onClick: onCancel,
|
|
2782
|
+
disabled: isLoading,
|
|
2783
|
+
children: "Cancel"
|
|
2784
|
+
}
|
|
2785
|
+
)
|
|
2786
|
+
] });
|
|
2787
|
+
};
|
|
2788
|
+
var BiometricIcon = () => /* @__PURE__ */ jsxs6(
|
|
2789
|
+
"svg",
|
|
2790
|
+
{
|
|
2791
|
+
width: "28",
|
|
2792
|
+
height: "28",
|
|
2793
|
+
viewBox: "0 0 24 24",
|
|
2794
|
+
fill: "none",
|
|
2795
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
2796
|
+
children: [
|
|
2797
|
+
/* @__PURE__ */ jsx8(
|
|
2798
|
+
"path",
|
|
2799
|
+
{
|
|
2800
|
+
d: "M12 14.5V16.5M7 10.5C7 7.73858 9.23858 5.5 12 5.5C14.7614 5.5 17 7.73858 17 10.5V11.5M12 11.5C10.6193 11.5 9.5 12.6193 9.5 14C9.5 15.3807 10.6193 16.5 12 16.5C13.3807 16.5 14.5 15.3807 14.5 14C14.5 12.6193 13.3807 11.5 12 11.5Z",
|
|
2801
|
+
stroke: "currentColor",
|
|
2802
|
+
strokeWidth: "1.5",
|
|
2803
|
+
strokeLinecap: "round",
|
|
2804
|
+
strokeLinejoin: "round"
|
|
2805
|
+
}
|
|
2806
|
+
),
|
|
2807
|
+
/* @__PURE__ */ jsx8(
|
|
2808
|
+
"path",
|
|
2809
|
+
{
|
|
2810
|
+
d: "M3 7V5C3 3.89543 3.89543 3 5 3H7M17 3H19C20.1046 3 21 3.89543 21 5V7M21 17V19C21 20.1046 20.1046 21 19 21H17M7 21H5C3.89543 21 3 20.1046 3 19V17",
|
|
2811
|
+
stroke: "currentColor",
|
|
2812
|
+
strokeWidth: "1.5",
|
|
2813
|
+
strokeLinecap: "round",
|
|
2814
|
+
strokeLinejoin: "round"
|
|
2815
|
+
}
|
|
2816
|
+
)
|
|
2817
|
+
]
|
|
2818
|
+
}
|
|
2819
|
+
);
|
|
2820
|
+
var BackspaceIcon = () => /* @__PURE__ */ jsxs6(
|
|
2821
|
+
"svg",
|
|
2822
|
+
{
|
|
2823
|
+
width: "24",
|
|
2824
|
+
height: "24",
|
|
2825
|
+
viewBox: "0 0 24 24",
|
|
2826
|
+
fill: "none",
|
|
2827
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
2828
|
+
children: [
|
|
2829
|
+
/* @__PURE__ */ jsx8(
|
|
2830
|
+
"path",
|
|
2831
|
+
{
|
|
2832
|
+
d: "M9.00195 7L4.00195 12L9.00195 17M19.002 7H8.00195L4.00195 12L8.00195 17H19.002C19.5325 17 20.0412 16.7893 20.4163 16.4142C20.7914 16.0391 21.002 15.5304 21.002 15V9C21.002 8.46957 20.7914 7.96086 20.4163 7.58579C20.0412 7.21071 19.5325 7 19.002 7Z",
|
|
2833
|
+
stroke: "currentColor",
|
|
2834
|
+
strokeWidth: "1.5",
|
|
2835
|
+
strokeLinecap: "round",
|
|
2836
|
+
strokeLinejoin: "round"
|
|
2837
|
+
}
|
|
2838
|
+
),
|
|
2839
|
+
/* @__PURE__ */ jsx8(
|
|
2840
|
+
"path",
|
|
2841
|
+
{
|
|
2842
|
+
d: "M14 10L10 14M10 10L14 14",
|
|
2843
|
+
stroke: "currentColor",
|
|
2844
|
+
strokeWidth: "1.5",
|
|
2845
|
+
strokeLinecap: "round",
|
|
2846
|
+
strokeLinejoin: "round"
|
|
2847
|
+
}
|
|
2848
|
+
)
|
|
2849
|
+
]
|
|
2850
|
+
}
|
|
2851
|
+
);
|
|
2852
|
+
var keyframes = `
|
|
2853
|
+
@keyframes tonpay-pin-shake {
|
|
2854
|
+
0%, 100% { transform: translateX(0); }
|
|
2855
|
+
10%, 30%, 50%, 70%, 90% { transform: translateX(-4px); }
|
|
2856
|
+
20%, 40%, 60%, 80% { transform: translateX(4px); }
|
|
2857
|
+
}
|
|
2858
|
+
@keyframes tonpay-pin-pulse {
|
|
2859
|
+
0%, 100% { opacity: 1; }
|
|
2860
|
+
50% { opacity: 0.5; }
|
|
2861
|
+
}
|
|
2862
|
+
`;
|
|
2863
|
+
|
|
2864
|
+
// src/signless/components/SignlessSetupModal.tsx
|
|
2865
|
+
import * as React13 from "react";
|
|
2866
|
+
import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2867
|
+
var SignlessSetupModal = ({
|
|
2868
|
+
isOpen,
|
|
2869
|
+
onClose,
|
|
2870
|
+
onComplete,
|
|
2871
|
+
showBiometric = true
|
|
2872
|
+
}) => {
|
|
2873
|
+
const { setup, isBiometricAvailable, state } = useSignless();
|
|
2874
|
+
const [step, setStep] = React13.useState("select_method");
|
|
2875
|
+
const [selectedMethod, setSelectedMethod] = React13.useState(null);
|
|
2876
|
+
const [pin, setPin] = React13.useState("");
|
|
2877
|
+
const [error, setError] = React13.useState(null);
|
|
2878
|
+
const [isLoading, setIsLoading] = React13.useState(false);
|
|
2879
|
+
React13.useEffect(() => {
|
|
2880
|
+
if (isOpen) {
|
|
2881
|
+
setStep("select_method");
|
|
2882
|
+
setSelectedMethod(null);
|
|
2883
|
+
setPin("");
|
|
2884
|
+
setError(null);
|
|
2885
|
+
setIsLoading(false);
|
|
2886
|
+
}
|
|
2887
|
+
}, [isOpen]);
|
|
2888
|
+
const handleMethodSelect = (method) => {
|
|
2889
|
+
setSelectedMethod(method);
|
|
2890
|
+
};
|
|
2891
|
+
const handleContinue = async () => {
|
|
2892
|
+
if (!selectedMethod) return;
|
|
2893
|
+
if (selectedMethod === "biometric") {
|
|
2894
|
+
setIsLoading(true);
|
|
2895
|
+
setError(null);
|
|
2896
|
+
try {
|
|
2897
|
+
await setup({ authMethod: "biometric" });
|
|
2898
|
+
onComplete();
|
|
2899
|
+
onClose();
|
|
2900
|
+
} catch (err) {
|
|
2901
|
+
setError(
|
|
2902
|
+
err instanceof Error ? err.message : "Biometric setup failed"
|
|
2903
|
+
);
|
|
2904
|
+
} finally {
|
|
2905
|
+
setIsLoading(false);
|
|
2906
|
+
}
|
|
2907
|
+
} else if (selectedMethod === "pin") {
|
|
2908
|
+
setStep("create_pin");
|
|
2909
|
+
}
|
|
2910
|
+
};
|
|
2911
|
+
const handlePinCreate = (newPin) => {
|
|
2912
|
+
setPin(newPin);
|
|
2913
|
+
setError(null);
|
|
2914
|
+
setStep("confirm_pin");
|
|
2915
|
+
};
|
|
2916
|
+
const handlePinConfirm = async (confirmPin) => {
|
|
2917
|
+
if (confirmPin !== pin) {
|
|
2918
|
+
setError("PINs do not match. Please try again.");
|
|
2919
|
+
setStep("create_pin");
|
|
2920
|
+
setPin("");
|
|
2921
|
+
return;
|
|
2922
|
+
}
|
|
2923
|
+
setIsLoading(true);
|
|
2924
|
+
setError(null);
|
|
2925
|
+
try {
|
|
2926
|
+
await setup({ authMethod: "pin", pin });
|
|
2927
|
+
onComplete();
|
|
2928
|
+
onClose();
|
|
2929
|
+
} catch (err) {
|
|
2930
|
+
setError(err instanceof Error ? err.message : "Setup failed");
|
|
2931
|
+
setStep("create_pin");
|
|
2932
|
+
setPin("");
|
|
2933
|
+
} finally {
|
|
2934
|
+
setIsLoading(false);
|
|
2935
|
+
}
|
|
2936
|
+
};
|
|
2937
|
+
const handleBack = () => {
|
|
2938
|
+
if (step === "confirm_pin") {
|
|
2939
|
+
setStep("create_pin");
|
|
2940
|
+
setError(null);
|
|
2941
|
+
} else if (step === "create_pin") {
|
|
2942
|
+
setStep("select_method");
|
|
2943
|
+
setPin("");
|
|
2944
|
+
setError(null);
|
|
2945
|
+
}
|
|
2946
|
+
};
|
|
2947
|
+
if (!isOpen) return null;
|
|
2948
|
+
const renderStepIndicator = () => {
|
|
2949
|
+
const steps = selectedMethod === "pin" ? 3 : 2;
|
|
2950
|
+
const currentStep = step === "select_method" ? 1 : step === "create_pin" ? 2 : 3;
|
|
2951
|
+
return /* @__PURE__ */ jsx9("div", { style: setupModalStyles.stepIndicator, children: Array.from({ length: steps }, (_, i) => /* @__PURE__ */ jsx9(
|
|
2952
|
+
"div",
|
|
2953
|
+
{
|
|
2954
|
+
style: {
|
|
2955
|
+
...setupModalStyles.stepDot,
|
|
2956
|
+
...i < currentStep ? setupModalStyles.stepDotActive : {}
|
|
2957
|
+
}
|
|
2958
|
+
},
|
|
2959
|
+
i
|
|
2960
|
+
)) });
|
|
2961
|
+
};
|
|
2962
|
+
const renderMethodSelection = () => /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
2963
|
+
/* @__PURE__ */ jsxs7("div", { style: setupModalStyles.header, children: [
|
|
2964
|
+
/* @__PURE__ */ jsx9("h2", { style: setupModalStyles.title, children: "Setup Signless" }),
|
|
2965
|
+
/* @__PURE__ */ jsx9("p", { style: setupModalStyles.subtitle, children: "Enable fast payments without wallet confirmations. Choose your preferred authentication method." })
|
|
2966
|
+
] }),
|
|
2967
|
+
renderStepIndicator(),
|
|
2968
|
+
/* @__PURE__ */ jsxs7("div", { style: setupModalStyles.methodSelector, children: [
|
|
2969
|
+
showBiometric && isBiometricAvailable && /* @__PURE__ */ jsxs7(
|
|
2970
|
+
"button",
|
|
2971
|
+
{
|
|
2972
|
+
type: "button",
|
|
2973
|
+
style: {
|
|
2974
|
+
...setupModalStyles.methodButton,
|
|
2975
|
+
...selectedMethod === "biometric" ? setupModalStyles.methodButtonSelected : {}
|
|
2976
|
+
},
|
|
2977
|
+
onClick: () => handleMethodSelect("biometric"),
|
|
2978
|
+
children: [
|
|
2979
|
+
/* @__PURE__ */ jsx9("div", { style: setupModalStyles.methodIcon, children: /* @__PURE__ */ jsx9(FaceIdIcon, {}) }),
|
|
2980
|
+
/* @__PURE__ */ jsxs7("div", { style: setupModalStyles.methodContent, children: [
|
|
2981
|
+
/* @__PURE__ */ jsx9("h3", { style: setupModalStyles.methodTitle, children: "Face ID / Touch ID" }),
|
|
2982
|
+
/* @__PURE__ */ jsx9("p", { style: setupModalStyles.methodDescription, children: "Use biometric authentication for quick and secure access" })
|
|
2983
|
+
] })
|
|
2984
|
+
]
|
|
2985
|
+
}
|
|
2986
|
+
),
|
|
2987
|
+
/* @__PURE__ */ jsxs7(
|
|
2988
|
+
"button",
|
|
2989
|
+
{
|
|
2990
|
+
type: "button",
|
|
2991
|
+
style: {
|
|
2992
|
+
...setupModalStyles.methodButton,
|
|
2993
|
+
...selectedMethod === "pin" ? setupModalStyles.methodButtonSelected : {}
|
|
2994
|
+
},
|
|
2995
|
+
onClick: () => handleMethodSelect("pin"),
|
|
2996
|
+
children: [
|
|
2997
|
+
/* @__PURE__ */ jsx9("div", { style: setupModalStyles.methodIcon, children: /* @__PURE__ */ jsx9(PinIcon, {}) }),
|
|
2998
|
+
/* @__PURE__ */ jsxs7("div", { style: setupModalStyles.methodContent, children: [
|
|
2999
|
+
/* @__PURE__ */ jsx9("h3", { style: setupModalStyles.methodTitle, children: "PIN Code" }),
|
|
3000
|
+
/* @__PURE__ */ jsx9("p", { style: setupModalStyles.methodDescription, children: "Create a 6-digit PIN for payment authorization" })
|
|
3001
|
+
] })
|
|
3002
|
+
]
|
|
3003
|
+
}
|
|
3004
|
+
)
|
|
3005
|
+
] }),
|
|
3006
|
+
error && /* @__PURE__ */ jsx9("p", { style: { color: "#FF5252", fontSize: "14px", marginBottom: "16px" }, children: error }),
|
|
3007
|
+
/* @__PURE__ */ jsx9(
|
|
3008
|
+
"button",
|
|
3009
|
+
{
|
|
3010
|
+
type: "button",
|
|
3011
|
+
style: {
|
|
3012
|
+
...setupModalStyles.continueButton,
|
|
3013
|
+
...!selectedMethod ? setupModalStyles.continueButtonDisabled : {}
|
|
3014
|
+
},
|
|
3015
|
+
onClick: handleContinue,
|
|
3016
|
+
disabled: !selectedMethod || isLoading,
|
|
3017
|
+
children: isLoading ? "Setting up..." : "Continue"
|
|
3018
|
+
}
|
|
3019
|
+
)
|
|
3020
|
+
] });
|
|
3021
|
+
const renderPinCreation = () => /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
3022
|
+
renderStepIndicator(),
|
|
3023
|
+
/* @__PURE__ */ jsx9(
|
|
3024
|
+
PinInput,
|
|
3025
|
+
{
|
|
3026
|
+
title: "Create PIN",
|
|
3027
|
+
subtitle: "Enter a 6-digit PIN to secure your signless payments",
|
|
3028
|
+
onComplete: handlePinCreate,
|
|
3029
|
+
onCancel: handleBack,
|
|
3030
|
+
error,
|
|
3031
|
+
isLoading
|
|
3032
|
+
}
|
|
3033
|
+
)
|
|
3034
|
+
] });
|
|
3035
|
+
const renderPinConfirmation = () => /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
3036
|
+
renderStepIndicator(),
|
|
3037
|
+
/* @__PURE__ */ jsx9(
|
|
3038
|
+
PinInput,
|
|
3039
|
+
{
|
|
3040
|
+
title: "Confirm PIN",
|
|
3041
|
+
subtitle: "Re-enter your PIN to confirm",
|
|
3042
|
+
onComplete: handlePinConfirm,
|
|
3043
|
+
onCancel: handleBack,
|
|
3044
|
+
error,
|
|
3045
|
+
isLoading
|
|
3046
|
+
}
|
|
3047
|
+
)
|
|
3048
|
+
] });
|
|
3049
|
+
return /* @__PURE__ */ jsxs7("div", { style: setupModalStyles.container, children: [
|
|
3050
|
+
step === "select_method" && renderMethodSelection(),
|
|
3051
|
+
step === "create_pin" && renderPinCreation(),
|
|
3052
|
+
step === "confirm_pin" && renderPinConfirmation()
|
|
3053
|
+
] });
|
|
3054
|
+
};
|
|
3055
|
+
var FaceIdIcon = () => /* @__PURE__ */ jsxs7(
|
|
3056
|
+
"svg",
|
|
3057
|
+
{
|
|
3058
|
+
width: "28",
|
|
3059
|
+
height: "28",
|
|
3060
|
+
viewBox: "0 0 24 24",
|
|
3061
|
+
fill: "none",
|
|
3062
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
3063
|
+
children: [
|
|
3064
|
+
/* @__PURE__ */ jsx9(
|
|
3065
|
+
"path",
|
|
3066
|
+
{
|
|
3067
|
+
d: "M12 14.5V16.5M7 10.5C7 7.73858 9.23858 5.5 12 5.5C14.7614 5.5 17 7.73858 17 10.5V11.5M12 11.5C10.6193 11.5 9.5 12.6193 9.5 14C9.5 15.3807 10.6193 16.5 12 16.5C13.3807 16.5 14.5 15.3807 14.5 14C14.5 12.6193 13.3807 11.5 12 11.5Z",
|
|
3068
|
+
stroke: "currentColor",
|
|
3069
|
+
strokeWidth: "1.5",
|
|
3070
|
+
strokeLinecap: "round",
|
|
3071
|
+
strokeLinejoin: "round"
|
|
3072
|
+
}
|
|
3073
|
+
),
|
|
3074
|
+
/* @__PURE__ */ jsx9(
|
|
3075
|
+
"path",
|
|
3076
|
+
{
|
|
3077
|
+
d: "M3 7V5C3 3.89543 3.89543 3 5 3H7M17 3H19C20.1046 3 21 3.89543 21 5V7M21 17V19C21 20.1046 20.1046 21 19 21H17M7 21H5C3.89543 21 3 20.1046 3 19V17",
|
|
3078
|
+
stroke: "currentColor",
|
|
3079
|
+
strokeWidth: "1.5",
|
|
3080
|
+
strokeLinecap: "round",
|
|
3081
|
+
strokeLinejoin: "round"
|
|
3082
|
+
}
|
|
3083
|
+
)
|
|
3084
|
+
]
|
|
3085
|
+
}
|
|
3086
|
+
);
|
|
3087
|
+
var PinIcon = () => /* @__PURE__ */ jsxs7(
|
|
3088
|
+
"svg",
|
|
3089
|
+
{
|
|
3090
|
+
width: "28",
|
|
3091
|
+
height: "28",
|
|
3092
|
+
viewBox: "0 0 24 24",
|
|
3093
|
+
fill: "none",
|
|
3094
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
3095
|
+
children: [
|
|
3096
|
+
/* @__PURE__ */ jsx9(
|
|
3097
|
+
"rect",
|
|
3098
|
+
{
|
|
3099
|
+
x: "3",
|
|
3100
|
+
y: "6",
|
|
3101
|
+
width: "18",
|
|
3102
|
+
height: "12",
|
|
3103
|
+
rx: "2",
|
|
3104
|
+
stroke: "currentColor",
|
|
3105
|
+
strokeWidth: "1.5"
|
|
3106
|
+
}
|
|
3107
|
+
),
|
|
3108
|
+
/* @__PURE__ */ jsx9("circle", { cx: "7", cy: "12", r: "1.5", fill: "currentColor" }),
|
|
3109
|
+
/* @__PURE__ */ jsx9("circle", { cx: "12", cy: "12", r: "1.5", fill: "currentColor" }),
|
|
3110
|
+
/* @__PURE__ */ jsx9("circle", { cx: "17", cy: "12", r: "1.5", fill: "currentColor" })
|
|
3111
|
+
]
|
|
3112
|
+
}
|
|
3113
|
+
);
|
|
3114
|
+
|
|
3115
|
+
// src/signless/components/SignlessUnlockModal.tsx
|
|
3116
|
+
import * as React14 from "react";
|
|
3117
|
+
import { Fragment as Fragment3, jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
3118
|
+
var SignlessUnlockModal = ({
|
|
3119
|
+
isOpen,
|
|
3120
|
+
onClose,
|
|
3121
|
+
onUnlock,
|
|
3122
|
+
showBiometric = true
|
|
3123
|
+
}) => {
|
|
3124
|
+
const { unlock, state, isBiometricAvailable } = useSignless();
|
|
3125
|
+
const [view, setView] = React14.useState("biometric");
|
|
3126
|
+
const [error, setError] = React14.useState(null);
|
|
3127
|
+
const [isLoading, setIsLoading] = React14.useState(false);
|
|
3128
|
+
const shouldShowBiometric = showBiometric && isBiometricAvailable && state.authMethod === "biometric";
|
|
3129
|
+
React14.useEffect(() => {
|
|
3130
|
+
if (isOpen) {
|
|
3131
|
+
setError(null);
|
|
3132
|
+
setIsLoading(false);
|
|
3133
|
+
setView(shouldShowBiometric ? "biometric" : "pin");
|
|
3134
|
+
}
|
|
3135
|
+
}, [isOpen, shouldShowBiometric]);
|
|
3136
|
+
React14.useEffect(() => {
|
|
3137
|
+
if (isOpen && view === "biometric" && shouldShowBiometric) {
|
|
3138
|
+
handleBiometricAuth();
|
|
3139
|
+
}
|
|
3140
|
+
}, [isOpen, view]);
|
|
3141
|
+
const handleBiometricAuth = async () => {
|
|
3142
|
+
setIsLoading(true);
|
|
3143
|
+
setError(null);
|
|
3144
|
+
try {
|
|
3145
|
+
await unlock({});
|
|
3146
|
+
onUnlock();
|
|
3147
|
+
onClose();
|
|
3148
|
+
} catch (err) {
|
|
3149
|
+
setError(
|
|
3150
|
+
err instanceof Error ? err.message : "Biometric authentication failed"
|
|
3151
|
+
);
|
|
3152
|
+
setView("pin");
|
|
3153
|
+
} finally {
|
|
3154
|
+
setIsLoading(false);
|
|
3155
|
+
}
|
|
3156
|
+
};
|
|
3157
|
+
const handlePinSubmit = async (pin) => {
|
|
3158
|
+
setIsLoading(true);
|
|
3159
|
+
setError(null);
|
|
3160
|
+
try {
|
|
3161
|
+
await unlock({ pin });
|
|
3162
|
+
onUnlock();
|
|
3163
|
+
onClose();
|
|
3164
|
+
} catch (err) {
|
|
3165
|
+
setError("Invalid PIN. Please try again.");
|
|
3166
|
+
} finally {
|
|
3167
|
+
setIsLoading(false);
|
|
3168
|
+
}
|
|
3169
|
+
};
|
|
3170
|
+
const handleUsePinInstead = () => {
|
|
3171
|
+
setView("pin");
|
|
3172
|
+
setError(null);
|
|
3173
|
+
};
|
|
3174
|
+
if (!isOpen) return null;
|
|
3175
|
+
const renderBiometricPrompt = () => /* @__PURE__ */ jsxs8("div", { style: unlockModalStyles.biometricPrompt, children: [
|
|
3176
|
+
/* @__PURE__ */ jsx10("div", { style: unlockModalStyles.biometricIcon, children: /* @__PURE__ */ jsx10(BiometricLargeIcon, {}) }),
|
|
3177
|
+
/* @__PURE__ */ jsx10("h2", { style: unlockModalStyles.biometricTitle, children: "Unlock Signless" }),
|
|
3178
|
+
/* @__PURE__ */ jsx10("p", { style: unlockModalStyles.biometricSubtitle, children: isLoading ? "Authenticating..." : "Use Face ID or Touch ID to continue" }),
|
|
3179
|
+
error && /* @__PURE__ */ jsx10("p", { style: { color: "#FF5252", fontSize: "14px", marginBottom: "16px" }, children: error }),
|
|
3180
|
+
!isLoading && /* @__PURE__ */ jsxs8(Fragment3, { children: [
|
|
3181
|
+
/* @__PURE__ */ jsx10(
|
|
3182
|
+
"button",
|
|
3183
|
+
{
|
|
3184
|
+
type: "button",
|
|
3185
|
+
style: {
|
|
3186
|
+
padding: "14px 32px",
|
|
3187
|
+
fontSize: "16px",
|
|
3188
|
+
fontWeight: 600,
|
|
3189
|
+
color: "#ffffff",
|
|
3190
|
+
backgroundColor: "#0098EA",
|
|
3191
|
+
border: "none",
|
|
3192
|
+
borderRadius: "12px",
|
|
3193
|
+
cursor: "pointer",
|
|
3194
|
+
marginBottom: "16px"
|
|
3195
|
+
},
|
|
3196
|
+
onClick: handleBiometricAuth,
|
|
3197
|
+
children: "Try Again"
|
|
3198
|
+
}
|
|
3199
|
+
),
|
|
3200
|
+
state.authMethod === "biometric" && /* @__PURE__ */ jsx10(
|
|
3201
|
+
"button",
|
|
3202
|
+
{
|
|
3203
|
+
type: "button",
|
|
3204
|
+
style: unlockModalStyles.usePinButton,
|
|
3205
|
+
onClick: handleUsePinInstead,
|
|
3206
|
+
children: "Use PIN instead"
|
|
3207
|
+
}
|
|
3208
|
+
)
|
|
3209
|
+
] })
|
|
3210
|
+
] });
|
|
3211
|
+
const renderPinInput = () => /* @__PURE__ */ jsx10(
|
|
3212
|
+
PinInput,
|
|
3213
|
+
{
|
|
3214
|
+
title: "Enter PIN",
|
|
3215
|
+
subtitle: "Enter your PIN to unlock signless payments",
|
|
3216
|
+
onComplete: handlePinSubmit,
|
|
3217
|
+
onCancel: onClose,
|
|
3218
|
+
error,
|
|
3219
|
+
isLoading,
|
|
3220
|
+
showBiometric: shouldShowBiometric,
|
|
3221
|
+
onBiometricPress: () => {
|
|
3222
|
+
setView("biometric");
|
|
3223
|
+
setError(null);
|
|
3224
|
+
}
|
|
3225
|
+
}
|
|
3226
|
+
);
|
|
3227
|
+
return /* @__PURE__ */ jsx10("div", { style: unlockModalStyles.container, children: view === "biometric" && shouldShowBiometric ? renderBiometricPrompt() : renderPinInput() });
|
|
381
3228
|
};
|
|
3229
|
+
var BiometricLargeIcon = () => /* @__PURE__ */ jsxs8(
|
|
3230
|
+
"svg",
|
|
3231
|
+
{
|
|
3232
|
+
width: "48",
|
|
3233
|
+
height: "48",
|
|
3234
|
+
viewBox: "0 0 24 24",
|
|
3235
|
+
fill: "none",
|
|
3236
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
3237
|
+
children: [
|
|
3238
|
+
/* @__PURE__ */ jsx10(
|
|
3239
|
+
"path",
|
|
3240
|
+
{
|
|
3241
|
+
d: "M12 14.5V16.5M7 10.5C7 7.73858 9.23858 5.5 12 5.5C14.7614 5.5 17 7.73858 17 10.5V11.5M12 11.5C10.6193 11.5 9.5 12.6193 9.5 14C9.5 15.3807 10.6193 16.5 12 16.5C13.3807 16.5 14.5 15.3807 14.5 14C14.5 12.6193 13.3807 11.5 12 11.5Z",
|
|
3242
|
+
stroke: "currentColor",
|
|
3243
|
+
strokeWidth: "1.5",
|
|
3244
|
+
strokeLinecap: "round",
|
|
3245
|
+
strokeLinejoin: "round"
|
|
3246
|
+
}
|
|
3247
|
+
),
|
|
3248
|
+
/* @__PURE__ */ jsx10(
|
|
3249
|
+
"path",
|
|
3250
|
+
{
|
|
3251
|
+
d: "M3 7V5C3 3.89543 3.89543 3 5 3H7M17 3H19C20.1046 3 21 3.89543 21 5V7M21 17V19C21 20.1046 20.1046 21 19 21H17M7 21H5C3.89543 21 3 20.1046 3 19V17",
|
|
3252
|
+
stroke: "currentColor",
|
|
3253
|
+
strokeWidth: "1.5",
|
|
3254
|
+
strokeLinecap: "round",
|
|
3255
|
+
strokeLinejoin: "round"
|
|
3256
|
+
}
|
|
3257
|
+
)
|
|
3258
|
+
]
|
|
3259
|
+
}
|
|
3260
|
+
);
|
|
382
3261
|
export {
|
|
383
|
-
|
|
3262
|
+
BottomSheet_default as BottomSheet,
|
|
384
3263
|
ErrorTransactionNotification,
|
|
385
3264
|
NotificationCard,
|
|
386
3265
|
NotificationRoot,
|
|
3266
|
+
PaymentModal,
|
|
3267
|
+
PinInput,
|
|
3268
|
+
SignlessProvider,
|
|
3269
|
+
SignlessSetupModal,
|
|
3270
|
+
SignlessStorage,
|
|
3271
|
+
SignlessUnlockModal,
|
|
387
3272
|
TonPayButton,
|
|
388
|
-
|
|
3273
|
+
createWebAuthnCredential,
|
|
3274
|
+
decryptPrivateKey,
|
|
3275
|
+
encryptPrivateKey,
|
|
3276
|
+
generateKeyPair,
|
|
3277
|
+
getWebAuthnCredential,
|
|
3278
|
+
isWebAuthnSupported,
|
|
3279
|
+
signMessage,
|
|
3280
|
+
signlessStorage,
|
|
3281
|
+
useMoonPayIframe,
|
|
3282
|
+
useSignless,
|
|
3283
|
+
useSignlessModal,
|
|
3284
|
+
useTonPay,
|
|
3285
|
+
useTonPaySignless,
|
|
3286
|
+
verifySignlessSignature
|
|
389
3287
|
};
|
|
390
3288
|
//# sourceMappingURL=index.mjs.map
|