@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.js
CHANGED
|
@@ -1,28 +1,285 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }// src/components/ton-pay-
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }// src/components/ton-pay-button/TonPayButton.tsx
|
|
2
|
+
var _react = require('react'); var React6 = _interopRequireWildcard(_react); var React8 = _interopRequireWildcard(_react); var React9 = _interopRequireWildcard(_react); var React10 = _interopRequireWildcard(_react); var React11 = _interopRequireWildcard(_react); var React12 = _interopRequireWildcard(_react); var React13 = _interopRequireWildcard(_react); var React14 = _interopRequireWildcard(_react);
|
|
3
|
+
|
|
2
4
|
var _uireact = require('@tonconnect/ui-react');
|
|
3
|
-
var _ui = require('@tonconnect/ui');
|
|
4
|
-
var _react = require('react'); var _react2 = _interopRequireDefault(_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
|
|
|
8
27
|
var _jsxruntime = require('react/jsx-runtime');
|
|
28
|
+
var TonIcon = ({ size = 24, className }) => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
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__ */ _jsxruntime.jsxs.call(void 0, "g", { clipPath: "url(#ton-icon-clip)", children: [
|
|
40
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
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__ */ _jsxruntime.jsx.call(void 0,
|
|
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__ */ _jsxruntime.jsx.call(void 0,
|
|
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__ */ _jsxruntime.jsx.call(void 0, "defs", { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "clipPath", { id: "ton-icon-clip", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "rect", { width: "24", height: "24", fill: "white" }) }) })
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
var TonIconBlue = ({ size = 24, className }) => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
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__ */ _jsxruntime.jsx.call(void 0,
|
|
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__ */ _jsxruntime.jsx.call(void 0,
|
|
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__ */ _jsxruntime.jsxs.call(void 0,
|
|
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__ */ _jsxruntime.jsx.call(void 0,
|
|
110
|
+
"path",
|
|
111
|
+
{
|
|
112
|
+
d: "M18 6L6 18",
|
|
113
|
+
stroke: color,
|
|
114
|
+
strokeWidth: "2",
|
|
115
|
+
strokeLinecap: "round",
|
|
116
|
+
strokeLinejoin: "round"
|
|
117
|
+
}
|
|
118
|
+
),
|
|
119
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
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__ */ _jsxruntime.jsxs.call(void 0,
|
|
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__ */ _jsxruntime.jsx.call(void 0,
|
|
148
|
+
"path",
|
|
149
|
+
{
|
|
150
|
+
d: "M19 12H5",
|
|
151
|
+
stroke: color,
|
|
152
|
+
strokeWidth: "2",
|
|
153
|
+
strokeLinecap: "round",
|
|
154
|
+
strokeLinejoin: "round"
|
|
155
|
+
}
|
|
156
|
+
),
|
|
157
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
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__ */ _jsxruntime.jsx.call(void 0,
|
|
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__ */ _jsxruntime.jsx.call(void 0,
|
|
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__ */ _jsxruntime.jsxs.call(void 0,
|
|
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__ */ _jsxruntime.jsx.call(void 0, "circle", { cx: "12", cy: "12", r: "10", stroke: color, strokeWidth: "2" }),
|
|
212
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { d: "M12 8V12", stroke: color, strokeWidth: "2", strokeLinecap: "round" }),
|
|
213
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "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__ */ _jsxruntime.jsxs.call(void 0,
|
|
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__ */ _jsxruntime.jsx.call(void 0, "circle", { cx: "12", cy: "12", r: "2", fill: color }),
|
|
233
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "circle", { cx: "19", cy: "12", r: "2", fill: color }),
|
|
234
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "circle", { cx: "5", cy: "12", r: "2", fill: color })
|
|
235
|
+
]
|
|
236
|
+
}
|
|
237
|
+
);
|
|
238
|
+
var ErrorDotIcon = ({
|
|
239
|
+
color = "#FF5252"
|
|
240
|
+
}) => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
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__ */ _jsxruntime.jsx.call(void 0, "circle", { cx: "12", cy: "12", r: "11", fill: color }),
|
|
251
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
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
|
+
|
|
264
|
+
|
|
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,21 +287,26 @@ var NotificationCard = ({
|
|
|
30
287
|
className,
|
|
31
288
|
style
|
|
32
289
|
}) => {
|
|
33
|
-
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
290
|
+
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
291
|
+
"div",
|
|
292
|
+
{
|
|
293
|
+
className: ["tp-noti-card", className].filter(Boolean).join(" "),
|
|
294
|
+
style,
|
|
295
|
+
children: [
|
|
296
|
+
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "tp-noti-content", children: [
|
|
297
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h3", { className: "tp-noti-title", children: title }),
|
|
298
|
+
text && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "tp-noti-text", children: text })
|
|
299
|
+
] }),
|
|
300
|
+
icon && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "tp-noti-icon", children: icon })
|
|
301
|
+
]
|
|
302
|
+
}
|
|
303
|
+
);
|
|
40
304
|
};
|
|
41
|
-
var NotificationRoot = ({
|
|
305
|
+
var NotificationRoot = ({
|
|
306
|
+
children
|
|
307
|
+
}) => {
|
|
42
308
|
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "tp-noti-root", children });
|
|
43
309
|
};
|
|
44
|
-
var ErrorDotIcon = ({ color = "#FF5252" }) => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "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__ */ _jsxruntime.jsx.call(void 0, "circle", { cx: "12", cy: "12", r: "11", fill: color }),
|
|
46
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "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
|
|
|
@@ -62,323 +324,2959 @@ var ErrorTransactionNotification = ({ text, className, style }) => {
|
|
|
62
324
|
);
|
|
63
325
|
};
|
|
64
326
|
|
|
65
|
-
// src/components/
|
|
327
|
+
// src/components/payment-modal/PaymentModal.tsx
|
|
66
328
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
.
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
.tp-arrow:hover:not(:disabled){filter:brightness(0.92)}
|
|
94
|
-
.tp-arrow:active:not(:disabled){filter:brightness(0.85);transform:translateY(1px)}
|
|
95
|
-
.tp-arrow:disabled{cursor:not-allowed;opacity:.85;transition:none;filter:none;transform:none}
|
|
96
|
-
.tp-menu{position:absolute;right:0;top:calc(100% + 8px);width:256px;background:var(--tp-menu-bg,#ffffff);color:var(--tp-menu-text,#111827);border:1px solid rgba(0,0,0,.08);border-radius:var(--tp-menu-radius,16px);padding:8px;box-shadow:0 8px 24px rgba(0,0,0,.12);z-index:1000;animation:tp-fade-in .15s ease}
|
|
97
|
-
.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,#ffffff) transparent;filter:drop-shadow(0 -1px 1px rgba(0,0,0,.08))}
|
|
98
|
-
.tp-menu-address{padding:.5rem .75rem;font-size:.85rem;color:var(--tp-menu-muted,#6b7280);cursor:default;user-select:text}
|
|
99
|
-
.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,#111827);transition:background-color .15s ease, transform .1s ease-in-out;border-radius:8px;margin:2px}
|
|
100
|
-
.tp-menu-item:hover:not(:disabled){background:var(--tp-menu-hover,rgba(0,0,0,.06))}
|
|
101
|
-
.tp-menu-item:active{transform:scale(0.96)}
|
|
102
|
-
.tp-menu-item.danger{color:#e74c3c}
|
|
103
|
-
.tp-menu-item.danger:hover:not(:disabled){background:rgba(231,76,60,.12);color:#c0392b}
|
|
104
|
-
.tp-menu-item:disabled{cursor:default;opacity:1;color:var(--tp-menu-muted,#6b7280)}
|
|
105
|
-
.tp-menu-item:disabled:hover{background:transparent}
|
|
106
|
-
.tp-menu-icon{width:24px;height:24px;display:flex;align-items:center;justify-content:center;color:currentColor}
|
|
107
|
-
.tp-menu-item:disabled .tp-menu-icon{opacity:.5}
|
|
108
|
-
.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
|
-
`;
|
|
110
|
-
if (typeof document !== "undefined") {
|
|
111
|
-
const style = document.createElement("style");
|
|
112
|
-
style.textContent = stylesAndAnimations;
|
|
113
|
-
document.head.appendChild(style);
|
|
114
|
-
}
|
|
115
|
-
var TonGlyph = () => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": true, children: [
|
|
116
|
-
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "g", { clipPath: "url(#clip0_144_4719)", children: [
|
|
117
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { 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", fill: "#0098EA" }),
|
|
118
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { 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", fill: "white" }),
|
|
119
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "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__ */ _jsxruntime.jsx.call(void 0, "defs", { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "clipPath", { id: "clip0_144_4719", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "rect", { width: "24", height: "24", fill: "white" }) }) })
|
|
122
|
-
] });
|
|
123
|
-
var CopyGlyph = () => /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": true, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
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__ */ _jsxruntime.jsxs.call(void 0, "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__ */ _jsxruntime.jsx.call(void 0,
|
|
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__ */ _jsxruntime.jsx.call(void 0,
|
|
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
|
-
}
|
|
157
|
-
var TonPayButton = ({
|
|
158
|
-
handlePay,
|
|
159
|
-
isLoading = false,
|
|
160
|
-
variant = "long",
|
|
161
|
-
preset,
|
|
162
|
-
bgColor,
|
|
163
|
-
textColor,
|
|
164
|
-
borderRadius = 8,
|
|
165
|
-
fontFamily = "inherit",
|
|
166
|
-
width = 300,
|
|
167
|
-
height = 44,
|
|
168
|
-
text,
|
|
169
|
-
loadingText = "Processing...",
|
|
170
|
-
style,
|
|
171
|
-
className,
|
|
172
|
-
showMenu = true,
|
|
173
|
-
disabled = false,
|
|
174
|
-
onError,
|
|
175
|
-
showErrorNotification = true
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
// src/components/bottom-sheet/BottomSheet.tsx
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
// src/components/bottom-sheet/BottomSheet.css
|
|
336
|
+
var _styleinject = require('#style-inject'); var _styleinject2 = _interopRequireDefault(_styleinject);
|
|
337
|
+
_styleinject2.default.call(void 0, ".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
|
+
|
|
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"
|
|
176
355
|
}) => {
|
|
177
|
-
const
|
|
178
|
-
const
|
|
179
|
-
const [
|
|
180
|
-
const
|
|
181
|
-
const
|
|
182
|
-
const [
|
|
183
|
-
const [
|
|
184
|
-
const [
|
|
356
|
+
const sheetRef = _react.useRef.call(void 0, null);
|
|
357
|
+
const contentRef = _react.useRef.call(void 0, null);
|
|
358
|
+
const [currentDetent, setCurrentDetent] = _react.useState.call(void 0, initialDetent);
|
|
359
|
+
const [isDragging, setIsDragging] = _react.useState.call(void 0, false);
|
|
360
|
+
const [startY, setStartY] = _react.useState.call(void 0, 0);
|
|
361
|
+
const [currentY, setCurrentY] = _react.useState.call(void 0, 0);
|
|
362
|
+
const [isScrolling, setIsScrolling] = _react.useState.call(void 0, false);
|
|
363
|
+
const [sheetHeight, setSheetHeight] = _react.useState.call(void 0, 0);
|
|
364
|
+
const [isClosing, setIsClosing] = _react.useState.call(void 0, false);
|
|
365
|
+
const touchStartYRef = _react.useRef.call(void 0, 0);
|
|
366
|
+
const touchStartTimeRef = _react.useRef.call(void 0, 0);
|
|
367
|
+
const mouseStartTimeRef = _react.useRef.call(void 0, 0);
|
|
368
|
+
const scrollTopRef = _react.useRef.call(void 0, 0);
|
|
369
|
+
const canDragRef = _react.useRef.call(void 0, true);
|
|
370
|
+
const closeTimeoutRef = _react.useRef.call(void 0, null);
|
|
371
|
+
const openTimeoutRef = _react.useRef.call(void 0, null);
|
|
372
|
+
const isClosingRef = _react.useRef.call(void 0, false);
|
|
373
|
+
const isSnappingRef = _react.useRef.call(void 0, false);
|
|
374
|
+
const targetDetentRef = _react.useRef.call(void 0, null);
|
|
375
|
+
const getDetentValue = _react.useCallback.call(void 0,
|
|
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 = _react.useCallback.call(void 0, () => {
|
|
383
|
+
if (!sheetRef.current) return 0;
|
|
384
|
+
const viewportHeight = window.innerHeight;
|
|
385
|
+
const detentValue = getDetentValue(currentDetent);
|
|
386
|
+
return viewportHeight * detentValue;
|
|
387
|
+
}, [currentDetent, getDetentValue]);
|
|
185
388
|
_react.useEffect.call(void 0, () => {
|
|
186
|
-
|
|
187
|
-
if (
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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);
|
|
192
411
|
}
|
|
193
|
-
}, [
|
|
194
|
-
_react.
|
|
195
|
-
if (
|
|
196
|
-
|
|
412
|
+
}, [isOpen, initialDetent, getDetentValue]);
|
|
413
|
+
const handleClose = _react.useCallback.call(void 0, () => {
|
|
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);
|
|
197
421
|
}
|
|
198
|
-
|
|
422
|
+
closeTimeoutRef.current = setTimeout(() => {
|
|
423
|
+
isClosingRef.current = false;
|
|
424
|
+
setIsClosing(false);
|
|
425
|
+
onClose();
|
|
426
|
+
}, 200);
|
|
427
|
+
}, [isClosing, onClose, initialDetent]);
|
|
199
428
|
_react.useEffect.call(void 0, () => {
|
|
200
|
-
if (!
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
e.preventDefault();
|
|
206
|
-
e.stopPropagation();
|
|
207
|
-
if (isLoading || disabled || !address) return;
|
|
208
|
-
setShowContextMenu(!showContextMenu);
|
|
209
|
-
};
|
|
210
|
-
const handleDisconnect = () => {
|
|
211
|
-
disconnect();
|
|
212
|
-
setShowContextMenu(false);
|
|
213
|
-
setIsCopiedShown(false);
|
|
214
|
-
};
|
|
215
|
-
const handleCopyAddress = async () => {
|
|
216
|
-
const rawAddress = _optionalChain([wallet, 'optionalAccess', _ => _.account, 'optionalAccess', _2 => _2.address]);
|
|
217
|
-
const chain = _optionalChain([wallet, 'optionalAccess', _3 => _3.account, 'optionalAccess', _4 => _4.chain]);
|
|
218
|
-
const toCopy = rawAddress ? _ui.toUserFriendlyAddress.call(void 0, rawAddress, chain === _ui.CHAIN.TESTNET) : address;
|
|
219
|
-
if (toCopy) {
|
|
220
|
-
try {
|
|
221
|
-
await navigator.clipboard.writeText(toCopy);
|
|
222
|
-
setIsCopiedShown(true);
|
|
223
|
-
setTimeout(() => setIsCopiedShown(false), 1e3);
|
|
224
|
-
} catch (e2) {
|
|
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);
|
|
225
434
|
}
|
|
226
435
|
}
|
|
227
|
-
};
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
436
|
+
}, [isOpen, isDragging, currentDetent, sheetHeight, calculateSheetHeight]);
|
|
437
|
+
const findNearestDetent = _react.useCallback.call(void 0,
|
|
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 = _react.useCallback.call(void 0,
|
|
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 = _react.useCallback.call(void 0,
|
|
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;
|
|
239
484
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
485
|
+
},
|
|
486
|
+
[isOpen, isClosing]
|
|
487
|
+
);
|
|
488
|
+
const calculateNewHeight = _react.useCallback.call(void 0,
|
|
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 = _react.useCallback.call(void 0,
|
|
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 = _nullishCoalesce(_optionalChain([contentRef, 'access', _2 => _2.current, 'optionalAccess', _3 => _3.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 = _react.useCallback.call(void 0,
|
|
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
|
+
}
|
|
244
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 = _react.useCallback.call(void 0,
|
|
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 = _react.useCallback.call(void 0,
|
|
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 = _react.useCallback.call(void 0, () => {
|
|
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
|
+
_react.useEffect.call(void 0, () => {
|
|
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 = _react.useCallback.call(void 0,
|
|
718
|
+
(e) => {
|
|
719
|
+
handleTouchEnd(e);
|
|
720
|
+
},
|
|
721
|
+
[handleTouchEnd]
|
|
722
|
+
);
|
|
723
|
+
_react.useEffect.call(void 0, () => {
|
|
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
|
+
_react.useEffect.call(void 0, () => {
|
|
738
|
+
if (isOpen) {
|
|
739
|
+
document.body.style.overflow = "hidden";
|
|
740
|
+
} else {
|
|
741
|
+
document.body.style.overflow = "";
|
|
245
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;
|
|
246
767
|
};
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "tp-btn-container", children: [
|
|
274
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
275
|
-
"button",
|
|
276
|
-
{
|
|
277
|
-
type: "button",
|
|
278
|
-
className: ["tp-btn", isLoading ? "loading" : "", !hasMenu ? "no-menu" : "with-menu"].filter(Boolean).join(" "),
|
|
279
|
-
onClick: isDisabled ? void 0 : onPayClick,
|
|
280
|
-
disabled: isDisabled,
|
|
281
|
-
children: isLoading ? /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "tp-btn-content", children: [
|
|
282
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "tp-spinner" }),
|
|
283
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { children: loadingText })
|
|
284
|
-
] }) : content
|
|
285
|
-
}
|
|
286
|
-
),
|
|
287
|
-
hasMenu && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
288
|
-
"button",
|
|
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__ */ _jsxruntime.jsx.call(void 0,
|
|
787
|
+
"div",
|
|
788
|
+
{
|
|
789
|
+
className: `bottom-sheet-backdrop ${backdropClassName} ${isClosing ? "closing" : ""}`,
|
|
790
|
+
onClick: enableBackdropClose ? handleClose : void 0,
|
|
791
|
+
style: backdropStyle,
|
|
792
|
+
children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
793
|
+
"div",
|
|
289
794
|
{
|
|
290
|
-
|
|
291
|
-
className:
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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__ */ _jsxruntime.jsx.call(void 0, "div", { className: "bottom-sheet-handle-container", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
807
|
+
"div",
|
|
808
|
+
{
|
|
809
|
+
className: `bottom-sheet-handle ${handleClassName}`,
|
|
810
|
+
onClick: (e) => {
|
|
811
|
+
e.stopPropagation();
|
|
812
|
+
handleClose();
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
) }),
|
|
816
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
817
|
+
"div",
|
|
818
|
+
{
|
|
819
|
+
ref: contentRef,
|
|
820
|
+
className: `bottom-sheet-content ${contentClassName} ${isScrolling ? "scrolling" : ""}`,
|
|
821
|
+
children
|
|
822
|
+
}
|
|
823
|
+
)
|
|
824
|
+
]
|
|
295
825
|
}
|
|
296
826
|
)
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "tp-menu-arrow" }),
|
|
300
|
-
address && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
|
|
301
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "tp-menu-address", children: shortenAddress(address) }),
|
|
302
|
-
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "button", { onClick: handleCopyAddress, className: "tp-menu-item", disabled: isCopiedShown, children: [
|
|
303
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "tp-menu-icon", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, CopyGlyph, {}) }),
|
|
304
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { children: isCopiedShown ? "Address copied!" : "Copy address" })
|
|
305
|
-
] }),
|
|
306
|
-
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "button", { onClick: handleDisconnect, className: "tp-menu-item danger", children: [
|
|
307
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "tp-menu-icon", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, DisconnectGlyph, {}) }),
|
|
308
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { children: "Disconnect" })
|
|
309
|
-
] })
|
|
310
|
-
] })
|
|
311
|
-
] }),
|
|
312
|
-
errorMessage && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, NotificationRoot, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, ErrorTransactionNotification, { text: errorMessage }) })
|
|
313
|
-
] });
|
|
827
|
+
}
|
|
828
|
+
);
|
|
314
829
|
};
|
|
830
|
+
var BottomSheet_default = BottomSheet;
|
|
315
831
|
|
|
316
|
-
// src/
|
|
317
|
-
|
|
318
|
-
|
|
832
|
+
// src/components/payment-modal/PaymentModal.css
|
|
319
833
|
|
|
834
|
+
_styleinject2.default.call(void 0, '.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');
|
|
320
835
|
|
|
836
|
+
// src/components/payment-modal/PaymentModal.tsx
|
|
321
837
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
var
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
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] = _react.useState.call(void 0, "main");
|
|
854
|
+
const [isMobile, setIsMobile] = _react.useState.call(void 0, false);
|
|
855
|
+
const [showMenu, setShowMenu] = _react.useState.call(void 0, false);
|
|
856
|
+
const [onRampLink, setOnRampLink] = _react.useState.call(void 0, null);
|
|
857
|
+
const [onRampError, setOnRampError] = _react.useState.call(void 0, null);
|
|
858
|
+
const [isOnRampLoading, setIsOnRampLoading] = _react.useState.call(void 0, false);
|
|
859
|
+
const [iframeLoaded, setIframeLoaded] = _react.useState.call(void 0, false);
|
|
860
|
+
const [iframeError, setIframeError] = _react.useState.call(void 0, null);
|
|
861
|
+
const [sheetDetent, setSheetDetent] = _react.useState.call(void 0, [0.55]);
|
|
862
|
+
const iframeTimeoutRef = _react.useRef.call(void 0, null);
|
|
863
|
+
const fetchStartedRef = _react.useRef.call(void 0, false);
|
|
864
|
+
const contentRef = _react.useRef.call(void 0, null);
|
|
865
|
+
const handlePaymentSuccess = _react.useCallback.call(void 0, () => {
|
|
866
|
+
if (iframeTimeoutRef.current) {
|
|
867
|
+
clearTimeout(iframeTimeoutRef.current);
|
|
331
868
|
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
869
|
+
setView("success");
|
|
870
|
+
_optionalChain([onPaymentSuccess, 'optionalCall', _4 => _4()]);
|
|
871
|
+
setTimeout(() => {
|
|
872
|
+
onClose();
|
|
873
|
+
}, 2e3);
|
|
874
|
+
}, [onClose, onPaymentSuccess]);
|
|
875
|
+
const handlePaymentError = _react.useCallback.call(void 0, (errorMessage) => {
|
|
876
|
+
if (iframeTimeoutRef.current) {
|
|
877
|
+
clearTimeout(iframeTimeoutRef.current);
|
|
878
|
+
}
|
|
879
|
+
setIframeError(errorMessage);
|
|
880
|
+
setView("error");
|
|
881
|
+
}, []);
|
|
882
|
+
const handleRetry = _react.useCallback.call(void 0, () => {
|
|
883
|
+
setOnRampLink(null);
|
|
884
|
+
setOnRampError(null);
|
|
885
|
+
setIframeError(null);
|
|
886
|
+
setIframeLoaded(false);
|
|
887
|
+
fetchStartedRef.current = false;
|
|
888
|
+
setView("card");
|
|
889
|
+
}, []);
|
|
890
|
+
_react.useEffect.call(void 0, () => {
|
|
891
|
+
const handleMessage = (event) => {
|
|
892
|
+
if (_optionalChain([event, 'access', _5 => _5.data, 'optionalAccess', _6 => _6.type]) === "TONPAY_PAYMENT_SUCCESS") {
|
|
893
|
+
handlePaymentSuccess();
|
|
338
894
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
895
|
+
if (_optionalChain([event, 'access', _7 => _7.data, 'optionalAccess', _8 => _8.type]) === "TONPAY_PAYMENT_ERROR") {
|
|
896
|
+
const payload = event.data.payload;
|
|
897
|
+
handlePaymentError(_optionalChain([payload, 'optionalAccess', _9 => _9.message]) || "Payment failed");
|
|
898
|
+
}
|
|
899
|
+
if (_optionalChain([event, 'access', _10 => _10.data, 'optionalAccess', _11 => _11.type]) === "TONPAY_IFRAME_LOADED") {
|
|
900
|
+
setIframeLoaded(true);
|
|
901
|
+
if (iframeTimeoutRef.current) {
|
|
902
|
+
clearTimeout(iframeTimeoutRef.current);
|
|
345
903
|
}
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
reject(new Error("Wallet connection modal closed"));
|
|
904
|
+
}
|
|
905
|
+
if (_optionalChain([event, 'access', _12 => _12.data, 'optionalAccess', _13 => _13.type]) === "TONPAY_MOONPAY_EVENT") {
|
|
906
|
+
const payload = event.data.payload;
|
|
907
|
+
if (_optionalChain([payload, 'optionalAccess', _14 => _14.type]) === "onTransactionCompleted" || _optionalChain([payload, 'optionalAccess', _15 => _15.eventName]) === "transactionCompleted") {
|
|
908
|
+
handlePaymentSuccess();
|
|
352
909
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
}, [
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
910
|
+
if (_optionalChain([payload, 'optionalAccess', _16 => _16.type]) === "onTransactionFailed" || _optionalChain([payload, 'optionalAccess', _17 => _17.eventName]) === "transactionFailed") {
|
|
911
|
+
handlePaymentError(_optionalChain([payload, 'optionalAccess', _18 => _18.message]) || "Transaction failed");
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
};
|
|
915
|
+
window.addEventListener("message", handleMessage);
|
|
916
|
+
return () => window.removeEventListener("message", handleMessage);
|
|
917
|
+
}, [handlePaymentSuccess, handlePaymentError]);
|
|
918
|
+
_react.useEffect.call(void 0, () => {
|
|
919
|
+
if (view !== "card") {
|
|
920
|
+
fetchStartedRef.current = false;
|
|
921
|
+
}
|
|
922
|
+
}, [view]);
|
|
923
|
+
_react.useEffect.call(void 0, () => {
|
|
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 = _optionalChain([err, 'optionalAccess', _19 => _19.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
|
+
_react.useEffect.call(void 0, () => {
|
|
952
|
+
const checkMobile = () => {
|
|
953
|
+
setIsMobile(window.innerWidth < 768);
|
|
954
|
+
};
|
|
955
|
+
checkMobile();
|
|
956
|
+
window.addEventListener("resize", checkMobile);
|
|
957
|
+
return () => window.removeEventListener("resize", checkMobile);
|
|
958
|
+
}, []);
|
|
959
|
+
_react.useEffect.call(void 0, () => {
|
|
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
|
+
_react.useEffect.call(void 0, () => {
|
|
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__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "pm-header", children: [
|
|
1000
|
+
view !== "main" ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "button", { className: "pm-back-btn", onClick: handleBack, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, BackIcon, {}) }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { style: { width: 32 } }),
|
|
1001
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "pm-title", children: "New Purchase" }),
|
|
1002
|
+
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { style: { display: "flex", gap: 8 }, children: [
|
|
1003
|
+
walletAddress && view === "main" && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { style: { position: "relative" }, children: [
|
|
1004
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "button", { className: "pm-close-btn", onClick: () => setShowMenu(!showMenu), children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, MenuIcon, {}) }),
|
|
1005
|
+
showMenu && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "pm-menu-dropdown", children: [
|
|
1006
|
+
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "pm-menu-item disabled", children: [
|
|
1007
|
+
walletAddress.slice(0, 4),
|
|
1008
|
+
"...",
|
|
1009
|
+
walletAddress.slice(-4)
|
|
1010
|
+
] }),
|
|
1011
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
1012
|
+
"div",
|
|
1013
|
+
{
|
|
1014
|
+
className: "pm-menu-item danger",
|
|
1015
|
+
onClick: () => {
|
|
1016
|
+
_optionalChain([onDisconnect, 'optionalCall', _20 => _20()]);
|
|
1017
|
+
setShowMenu(false);
|
|
1018
|
+
},
|
|
1019
|
+
children: "Disconnect"
|
|
1020
|
+
}
|
|
1021
|
+
)
|
|
1022
|
+
] })
|
|
1023
|
+
] }),
|
|
1024
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "button", { className: "pm-close-btn", onClick: onClose, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, CloseIcon, {}) })
|
|
1025
|
+
] })
|
|
1026
|
+
] });
|
|
1027
|
+
const renderMainView = () => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "pm-body-main", children: [
|
|
1028
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "pm-order-info", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "pm-order-text", children: itemTitle }) }),
|
|
1029
|
+
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "pm-amount-container", children: [
|
|
1030
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "pm-amount-label", children: "Amount" }),
|
|
1031
|
+
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "pm-amount-value", children: [
|
|
1032
|
+
amount,
|
|
1033
|
+
" ",
|
|
1034
|
+
currency,
|
|
1035
|
+
" ",
|
|
1036
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, TonIconBlue, {})
|
|
1037
|
+
] })
|
|
1038
|
+
] }),
|
|
1039
|
+
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "pm-actions", children: [
|
|
1040
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "button", { className: "pm-btn pm-btn-primary", onClick: onPayWithCrypto, children: "Pay with Crypto" }),
|
|
1041
|
+
onRampAvailable && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "button", { className: "pm-btn pm-btn-black", onClick: () => setView("card"), children: "Pay with Card" })
|
|
1042
|
+
] }),
|
|
1043
|
+
onRampAvailable && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "pm-footer", children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { className: "pm-footer-text", children: [
|
|
1044
|
+
"Processed by ",
|
|
1045
|
+
PROVIDER.name
|
|
1046
|
+
] }) })
|
|
1047
|
+
] });
|
|
1048
|
+
const renderCardView = () => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "pm-iframe-container", children: [
|
|
1049
|
+
isOnRampLoading && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "pm-loading-container", children: [
|
|
1050
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "pm-spinner" }),
|
|
1051
|
+
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "p", { children: [
|
|
1052
|
+
"Loading ",
|
|
1053
|
+
PROVIDER.name,
|
|
1054
|
+
"..."
|
|
1055
|
+
] })
|
|
1056
|
+
] }),
|
|
1057
|
+
onRampError && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "pm-error-inline", children: [
|
|
1058
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, ErrorIcon, {}),
|
|
1059
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { children: onRampError }),
|
|
1060
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "button", { className: "pm-btn pm-btn-outline", onClick: handleRetry, children: "Try Again" })
|
|
1061
|
+
] }),
|
|
1062
|
+
onRampLink && !isOnRampLoading && !onRampError && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
|
|
1063
|
+
!iframeLoaded && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "pm-loading-overlay", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "pm-spinner" }) }),
|
|
1064
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
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__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "pm-success-container", children: [
|
|
1081
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "pm-success-icon", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, CheckIcon, {}) }),
|
|
1082
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h2", { className: "pm-success-title", children: "Payment Successful" }),
|
|
1083
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "pm-success-text", children: "Your purchase is being processed" })
|
|
1084
|
+
] });
|
|
1085
|
+
const renderErrorView = () => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "pm-error-container", children: [
|
|
1086
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "pm-error-icon", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, ErrorIcon, {}) }),
|
|
1087
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h2", { className: "pm-error-title", children: "Payment Failed" }),
|
|
1088
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "pm-error-text", children: iframeError || "Something went wrong" }),
|
|
1089
|
+
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "pm-error-actions", children: [
|
|
1090
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "button", { className: "pm-btn pm-btn-primary", onClick: handleRetry, children: "Try Again" }),
|
|
1091
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "button", { className: "pm-btn pm-btn-outline", onClick: onPayWithCrypto, children: "Pay with Crypto Instead" })
|
|
1092
|
+
] })
|
|
1093
|
+
] });
|
|
1094
|
+
const renderContent = () => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "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__ */ _jsxruntime.jsx.call(void 0,
|
|
1103
|
+
BottomSheet_default,
|
|
1104
|
+
{
|
|
1105
|
+
isOpen,
|
|
1106
|
+
onClose,
|
|
1107
|
+
detents: sheetDetent,
|
|
1108
|
+
initialDetent: 0,
|
|
1109
|
+
enableSwipeToClose: view === "main",
|
|
1110
|
+
children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { ref: contentRef, children: renderContent() })
|
|
1111
|
+
}
|
|
1112
|
+
);
|
|
1113
|
+
}
|
|
1114
|
+
if (!isOpen) return null;
|
|
1115
|
+
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "pm-desktop-overlay", onClick: onClose, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "pm-desktop-modal", onClick: (e) => e.stopPropagation(), children: renderContent() }) });
|
|
1116
|
+
};
|
|
1117
|
+
|
|
1118
|
+
// src/hooks/useMoonPayIframe.ts
|
|
1119
|
+
|
|
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 (!_optionalChain([options, 'optionalAccess', _21 => _21.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(_optionalChain([options, 'optionalAccess', _22 => _22.chain]));
|
|
1170
|
+
const headers = {
|
|
1171
|
+
"Content-Type": "application/json",
|
|
1172
|
+
..._optionalChain([options, 'optionalAccess', _23 => _23.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(_optionalChain([options, 'optionalAccess', _24 => _24.chain]));
|
|
1193
|
+
const headers = {
|
|
1194
|
+
"Content-Type": "application/json",
|
|
1195
|
+
..._optionalChain([options, 'optionalAccess', _25 => _25.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
|
+
var _cryptojs = require('crypto-js'); var CryptoJS = _interopRequireWildcard(_cryptojs);
|
|
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 = _optionalChain([limitRes, 'optionalAccess', _26 => _26.limits]) || limitRes;
|
|
1243
|
+
if (!_optionalChain([limitsData, 'optionalAccess', _27 => _27.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 (e2) {
|
|
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
|
|
1290
|
+
var PRESETS = {
|
|
1291
|
+
default: {
|
|
1292
|
+
bgColor: "#0098EA",
|
|
1293
|
+
textColor: "#FFFFFF"
|
|
1294
|
+
},
|
|
1295
|
+
gradient: {
|
|
1296
|
+
bgColor: "linear-gradient(91.69deg, #2A82EB 8.9%, #0355CF 158.29%)",
|
|
1297
|
+
textColor: "#FFFFFF"
|
|
1298
|
+
}
|
|
1299
|
+
};
|
|
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}
|
|
1337
|
+
`;
|
|
1338
|
+
var stylesInjected2 = false;
|
|
1339
|
+
function injectStyles() {
|
|
1340
|
+
if (typeof document === "undefined" || stylesInjected2) return;
|
|
1341
|
+
const style = document.createElement("style");
|
|
1342
|
+
style.id = "tonpay-button-styles";
|
|
1343
|
+
style.textContent = buttonStyles;
|
|
1344
|
+
document.head.appendChild(style);
|
|
1345
|
+
stylesInjected2 = true;
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
// src/components/ton-pay-button/TonPayButton.tsx
|
|
1349
|
+
|
|
1350
|
+
injectStyles();
|
|
1351
|
+
var TonPayButton = ({
|
|
1352
|
+
handlePay,
|
|
1353
|
+
isLoading = false,
|
|
1354
|
+
variant = "long",
|
|
1355
|
+
preset,
|
|
1356
|
+
bgColor,
|
|
1357
|
+
textColor,
|
|
1358
|
+
borderRadius = 8,
|
|
1359
|
+
fontFamily = "inherit",
|
|
1360
|
+
width = 300,
|
|
1361
|
+
height = 44,
|
|
1362
|
+
text,
|
|
1363
|
+
loadingText = "Processing...",
|
|
1364
|
+
style,
|
|
1365
|
+
className,
|
|
1366
|
+
disabled = false,
|
|
1367
|
+
onError,
|
|
1368
|
+
showErrorNotification = true,
|
|
1369
|
+
amount,
|
|
1370
|
+
currency,
|
|
1371
|
+
apiKey,
|
|
1372
|
+
isOnRampAvailable = false,
|
|
1373
|
+
onCardPaymentSuccess,
|
|
1374
|
+
itemTitle
|
|
1375
|
+
}) => {
|
|
1376
|
+
const address = _uireact.useTonAddress.call(void 0, true);
|
|
1377
|
+
const [tonConnectUI] = _uireact.useTonConnectUI.call(void 0, );
|
|
1378
|
+
const [isModalOpen, setIsModalOpen] = _react.useState.call(void 0, false);
|
|
1379
|
+
const [onRampAvailable, setOnRampAvailable] = _react.useState.call(void 0, false);
|
|
1380
|
+
const [errorMessage, setErrorMessage] = _react.useState.call(void 0, null);
|
|
1381
|
+
const [internalLoading, setInternalLoading] = _react.useState.call(void 0, false);
|
|
1382
|
+
const [userIp, setUserIp] = _react.useState.call(void 0, "");
|
|
1383
|
+
const { checkAvailability, fetchOnRampLink } = useMoonPayIframe({
|
|
1384
|
+
apiKey,
|
|
1385
|
+
chain: "mainnet"
|
|
1386
|
+
});
|
|
1387
|
+
_react.useEffect.call(void 0, () => {
|
|
1388
|
+
getUserIp().then(setUserIp);
|
|
1389
|
+
}, []);
|
|
1390
|
+
_react.useEffect.call(void 0, () => {
|
|
1391
|
+
if (!errorMessage) return;
|
|
1392
|
+
const timerId = setTimeout(() => setErrorMessage(null), 3e3);
|
|
1393
|
+
return () => clearTimeout(timerId);
|
|
1394
|
+
}, [errorMessage]);
|
|
1395
|
+
_react.useEffect.call(void 0, () => {
|
|
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 (e3) {
|
|
1409
|
+
if (isActive) setOnRampAvailable(false);
|
|
1410
|
+
} finally {
|
|
1411
|
+
if (isActive) setInternalLoading(false);
|
|
1412
|
+
}
|
|
1413
|
+
} else {
|
|
1414
|
+
if (isActive) setOnRampAvailable(false);
|
|
1415
|
+
}
|
|
1416
|
+
};
|
|
1417
|
+
check();
|
|
1418
|
+
return () => {
|
|
1419
|
+
isActive = false;
|
|
1420
|
+
};
|
|
1421
|
+
}, [apiKey, amount, currency, isOnRampAvailable, checkAvailability, userIp]);
|
|
1422
|
+
const handleDisconnect = _react.useCallback.call(void 0, () => {
|
|
1423
|
+
tonConnectUI.disconnect();
|
|
1424
|
+
}, [tonConnectUI]);
|
|
1425
|
+
const handlePayWithCrypto = _react.useCallback.call(void 0, async () => {
|
|
1426
|
+
setIsModalOpen(false);
|
|
1427
|
+
try {
|
|
1428
|
+
await handlePay();
|
|
1429
|
+
} catch (err) {
|
|
1430
|
+
_optionalChain([onError, 'optionalCall', _28 => _28(err)]);
|
|
1431
|
+
if (showErrorNotification) {
|
|
1432
|
+
const raw = typeof err === "object" && err && "message" in err ? String(err.message) : String(_nullishCoalesce(err, () => ( "")));
|
|
1433
|
+
setErrorMessage(raw || "Wallet connection modal closed");
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
}, [handlePay, onError, showErrorNotification]);
|
|
1437
|
+
const onPayClick = _react.useCallback.call(void 0, async () => {
|
|
1438
|
+
if (onRampAvailable) {
|
|
1439
|
+
setIsModalOpen(true);
|
|
1440
|
+
} else {
|
|
1441
|
+
handlePayWithCrypto();
|
|
1442
|
+
}
|
|
1443
|
+
}, [onRampAvailable, handlePayWithCrypto]);
|
|
1444
|
+
const handleFetchOnRampLink = _react.useCallback.call(void 0,
|
|
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
|
+
);
|
|
1456
|
+
const presetConfig = preset ? PRESETS[preset] : null;
|
|
1457
|
+
const finalBgColor = _nullishCoalesce(_nullishCoalesce(bgColor, () => ( _optionalChain([presetConfig, 'optionalAccess', _29 => _29.bgColor]))), () => ( PRESETS.default.bgColor));
|
|
1458
|
+
const finalTextColor = _nullishCoalesce(_nullishCoalesce(textColor, () => ( _optionalChain([presetConfig, 'optionalAccess', _30 => _30.textColor]))), () => ( PRESETS.default.textColor));
|
|
1459
|
+
const cssVars = {
|
|
1460
|
+
"--tp-bg": finalBgColor,
|
|
1461
|
+
"--tp-text": finalTextColor,
|
|
1462
|
+
"--tp-radius": typeof borderRadius === "number" ? `${borderRadius}px` : borderRadius,
|
|
1463
|
+
"--tp-font": fontFamily,
|
|
1464
|
+
"--tp-width": toCssSize(width),
|
|
1465
|
+
"--tp-height": toCssSize(height)
|
|
1466
|
+
};
|
|
1467
|
+
const isDisabled = isLoading || disabled || internalLoading;
|
|
1468
|
+
const renderContent = () => {
|
|
1469
|
+
if (text) return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { children: text });
|
|
1470
|
+
if (variant === "short") {
|
|
1471
|
+
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "tp-btn-content", children: [
|
|
1472
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, TonIcon, {}),
|
|
1473
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { children: "Pay" })
|
|
1474
|
+
] });
|
|
1475
|
+
}
|
|
1476
|
+
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "tp-btn-content", children: [
|
|
1477
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { children: "Pay with" }),
|
|
1478
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, TonIcon, {}),
|
|
1479
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { children: "Pay" })
|
|
1480
|
+
] });
|
|
1481
|
+
};
|
|
1482
|
+
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
1483
|
+
"div",
|
|
1484
|
+
{
|
|
1485
|
+
style: { ...cssVars, ...style },
|
|
1486
|
+
className: classNames("tp-wrap", className),
|
|
1487
|
+
children: [
|
|
1488
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "tp-btn-container", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
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__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "tp-btn-content", children: [
|
|
1496
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "tp-spinner" }),
|
|
1497
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { children: loadingText })
|
|
1498
|
+
] }) : renderContent()
|
|
1499
|
+
}
|
|
1500
|
+
) }),
|
|
1501
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
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__ */ _jsxruntime.jsx.call(void 0, NotificationRoot, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, ErrorTransactionNotification, { text: errorMessage }) })
|
|
1518
|
+
]
|
|
1519
|
+
}
|
|
1520
|
+
);
|
|
1521
|
+
};
|
|
1522
|
+
|
|
1523
|
+
// src/hooks/useTonPay.ts
|
|
1524
|
+
|
|
1525
|
+
|
|
1526
|
+
|
|
1527
|
+
|
|
1528
|
+
|
|
1529
|
+
|
|
1530
|
+
var WALLET_CONNECTION_TIMEOUT = 5 * 60 * 1e3;
|
|
1531
|
+
var TX_VALID_DURATION = 5 * 60;
|
|
1532
|
+
function useTonPay() {
|
|
1533
|
+
const address = _uireact.useTonAddress.call(void 0, true);
|
|
1534
|
+
const modal = _uireact.useTonConnectModal.call(void 0, );
|
|
1535
|
+
const [tonConnectUI] = _uireact.useTonConnectUI.call(void 0, );
|
|
1536
|
+
const waitForWalletConnection = React8.useCallback(() => {
|
|
1537
|
+
return new Promise((resolve, reject) => {
|
|
1538
|
+
if (address) {
|
|
1539
|
+
resolve(address);
|
|
1540
|
+
return;
|
|
376
1541
|
}
|
|
1542
|
+
modal.open();
|
|
1543
|
+
const unsubscribe = tonConnectUI.onStatusChange((wallet) => {
|
|
1544
|
+
if (_optionalChain([wallet, 'optionalAccess', _31 => _31.account])) {
|
|
1545
|
+
unsubscribe();
|
|
1546
|
+
unsubscribeModal();
|
|
1547
|
+
resolve(wallet.account.address);
|
|
1548
|
+
}
|
|
1549
|
+
});
|
|
1550
|
+
const unsubscribeModal = tonConnectUI.onModalStateChange((state) => {
|
|
1551
|
+
if (state.status === "closed") {
|
|
1552
|
+
unsubscribe();
|
|
1553
|
+
unsubscribeModal();
|
|
1554
|
+
reject(new Error("Wallet connection modal closed"));
|
|
1555
|
+
}
|
|
1556
|
+
});
|
|
1557
|
+
setTimeout(() => {
|
|
1558
|
+
unsubscribe();
|
|
1559
|
+
unsubscribeModal();
|
|
1560
|
+
reject(new Error("Wallet connection timeout"));
|
|
1561
|
+
}, WALLET_CONNECTION_TIMEOUT);
|
|
1562
|
+
});
|
|
1563
|
+
}, [address, modal, tonConnectUI]);
|
|
1564
|
+
const pay = React8.useCallback(
|
|
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 };
|
|
377
1575
|
},
|
|
378
1576
|
[waitForWalletConnection, tonConnectUI]
|
|
379
1577
|
);
|
|
380
1578
|
return { pay, address };
|
|
381
|
-
}
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
// src/signless/context.tsx
|
|
1582
|
+
|
|
1583
|
+
|
|
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 (e4) {
|
|
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(_nullishCoalesce(_optionalChain([response, 'access', _32 => _32.getPublicKey, 'optionalCall', _33 => _33()]), () => ( new ArrayBuffer(0)))),
|
|
1798
|
+
transports: _optionalChain([response, 'access', _34 => _34.getTransports, 'optionalCall', _35 => _35()])
|
|
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
|
+
|
|
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 (e5) {
|
|
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 (_optionalChain([key, 'optionalAccess', _36 => _36.startsWith, 'call', _37 => _37(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
|
+
|
|
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 = _uireact.useTonAddress.call(void 0, 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 (e6) {
|
|
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" }));
|
|
2017
|
+
try {
|
|
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
|
|
2060
|
+
});
|
|
2061
|
+
resetLockTimeout();
|
|
2062
|
+
} catch (error) {
|
|
2063
|
+
setState((prev) => ({
|
|
2064
|
+
...prev,
|
|
2065
|
+
status: prev.isSetup ? "locked" : "not_setup"
|
|
2066
|
+
}));
|
|
2067
|
+
throw error;
|
|
2068
|
+
}
|
|
2069
|
+
},
|
|
2070
|
+
[walletAddress, resetLockTimeout]
|
|
2071
|
+
);
|
|
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__ */ _jsxruntime.jsx.call(void 0, 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
|
+
|
|
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
|
+
|
|
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
|
+
|
|
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
|
+
|
|
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
|
+
_optionalChain([inputRef, 'access', _38 => _38.current, 'optionalAccess', _39 => _39.focus, 'call', _40 => _40()]);
|
|
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
|
+
_optionalChain([inputRef, 'access', _41 => _41.current, 'optionalAccess', _42 => _42.focus, 'call', _43 => _43()]);
|
|
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__ */ _jsxruntime.jsxs.call(void 0, "div", { style: pinInputStyles.container, children: [
|
|
2676
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "style", { children: keyframes }),
|
|
2677
|
+
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { style: pinInputStyles.header, children: [
|
|
2678
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h2", { style: pinInputStyles.title, children: title }),
|
|
2679
|
+
subtitle && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { style: pinInputStyles.subtitle, children: subtitle })
|
|
2680
|
+
] }),
|
|
2681
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
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__ */ _jsxruntime.jsx.call(void 0,
|
|
2698
|
+
"div",
|
|
2699
|
+
{
|
|
2700
|
+
style: {
|
|
2701
|
+
...pinInputStyles.dotsContainer,
|
|
2702
|
+
...shake ? pinInputStyles.dotsContainerShake : {}
|
|
2703
|
+
},
|
|
2704
|
+
onClick: handleDotClick,
|
|
2705
|
+
children: Array.from({ length }, (_, i) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
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__ */ _jsxruntime.jsx.call(void 0, "p", { style: pinInputStyles.error, children: error }),
|
|
2719
|
+
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { style: pinInputStyles.keypad, children: [
|
|
2720
|
+
[1, 2, 3, 4, 5, 6, 7, 8, 9].map((digit) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
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__ */ _jsxruntime.jsx.call(void 0, "div", { style: pinInputStyles.keypadButtonEmpty, children: showBiometric && onBiometricPress && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
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__ */ _jsxruntime.jsx.call(void 0, BiometricIcon, {})
|
|
2746
|
+
}
|
|
2747
|
+
) }),
|
|
2748
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
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__ */ _jsxruntime.jsx.call(void 0,
|
|
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__ */ _jsxruntime.jsx.call(void 0, BackspaceIcon, {})
|
|
2773
|
+
}
|
|
2774
|
+
)
|
|
2775
|
+
] }),
|
|
2776
|
+
onCancel && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
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__ */ _jsxruntime.jsxs.call(void 0,
|
|
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__ */ _jsxruntime.jsx.call(void 0,
|
|
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__ */ _jsxruntime.jsx.call(void 0,
|
|
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__ */ _jsxruntime.jsxs.call(void 0,
|
|
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__ */ _jsxruntime.jsx.call(void 0,
|
|
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__ */ _jsxruntime.jsx.call(void 0,
|
|
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
|
+
|
|
2866
|
+
|
|
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__ */ _jsxruntime.jsx.call(void 0, "div", { style: setupModalStyles.stepIndicator, children: Array.from({ length: steps }, (_, i) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
2952
|
+
"div",
|
|
2953
|
+
{
|
|
2954
|
+
style: {
|
|
2955
|
+
...setupModalStyles.stepDot,
|
|
2956
|
+
...i < currentStep ? setupModalStyles.stepDotActive : {}
|
|
2957
|
+
}
|
|
2958
|
+
},
|
|
2959
|
+
i
|
|
2960
|
+
)) });
|
|
2961
|
+
};
|
|
2962
|
+
const renderMethodSelection = () => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
|
|
2963
|
+
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { style: setupModalStyles.header, children: [
|
|
2964
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h2", { style: setupModalStyles.title, children: "Setup Signless" }),
|
|
2965
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { style: setupModalStyles.subtitle, children: "Enable fast payments without wallet confirmations. Choose your preferred authentication method." })
|
|
2966
|
+
] }),
|
|
2967
|
+
renderStepIndicator(),
|
|
2968
|
+
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { style: setupModalStyles.methodSelector, children: [
|
|
2969
|
+
showBiometric && isBiometricAvailable && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
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__ */ _jsxruntime.jsx.call(void 0, "div", { style: setupModalStyles.methodIcon, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, FaceIdIcon, {}) }),
|
|
2980
|
+
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { style: setupModalStyles.methodContent, children: [
|
|
2981
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h3", { style: setupModalStyles.methodTitle, children: "Face ID / Touch ID" }),
|
|
2982
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { style: setupModalStyles.methodDescription, children: "Use biometric authentication for quick and secure access" })
|
|
2983
|
+
] })
|
|
2984
|
+
]
|
|
2985
|
+
}
|
|
2986
|
+
),
|
|
2987
|
+
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
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__ */ _jsxruntime.jsx.call(void 0, "div", { style: setupModalStyles.methodIcon, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, PinIcon, {}) }),
|
|
2998
|
+
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { style: setupModalStyles.methodContent, children: [
|
|
2999
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h3", { style: setupModalStyles.methodTitle, children: "PIN Code" }),
|
|
3000
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { style: setupModalStyles.methodDescription, children: "Create a 6-digit PIN for payment authorization" })
|
|
3001
|
+
] })
|
|
3002
|
+
]
|
|
3003
|
+
}
|
|
3004
|
+
)
|
|
3005
|
+
] }),
|
|
3006
|
+
error && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { style: { color: "#FF5252", fontSize: "14px", marginBottom: "16px" }, children: error }),
|
|
3007
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
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__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
|
|
3022
|
+
renderStepIndicator(),
|
|
3023
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
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__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
|
|
3036
|
+
renderStepIndicator(),
|
|
3037
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
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__ */ _jsxruntime.jsxs.call(void 0, "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__ */ _jsxruntime.jsxs.call(void 0,
|
|
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__ */ _jsxruntime.jsx.call(void 0,
|
|
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__ */ _jsxruntime.jsx.call(void 0,
|
|
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__ */ _jsxruntime.jsxs.call(void 0,
|
|
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__ */ _jsxruntime.jsx.call(void 0,
|
|
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__ */ _jsxruntime.jsx.call(void 0, "circle", { cx: "7", cy: "12", r: "1.5", fill: "currentColor" }),
|
|
3109
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "circle", { cx: "12", cy: "12", r: "1.5", fill: "currentColor" }),
|
|
3110
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "circle", { cx: "17", cy: "12", r: "1.5", fill: "currentColor" })
|
|
3111
|
+
]
|
|
3112
|
+
}
|
|
3113
|
+
);
|
|
3114
|
+
|
|
3115
|
+
// src/signless/components/SignlessUnlockModal.tsx
|
|
3116
|
+
|
|
3117
|
+
|
|
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__ */ _jsxruntime.jsxs.call(void 0, "div", { style: unlockModalStyles.biometricPrompt, children: [
|
|
3176
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { style: unlockModalStyles.biometricIcon, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, BiometricLargeIcon, {}) }),
|
|
3177
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h2", { style: unlockModalStyles.biometricTitle, children: "Unlock Signless" }),
|
|
3178
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { style: unlockModalStyles.biometricSubtitle, children: isLoading ? "Authenticating..." : "Use Face ID or Touch ID to continue" }),
|
|
3179
|
+
error && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { style: { color: "#FF5252", fontSize: "14px", marginBottom: "16px" }, children: error }),
|
|
3180
|
+
!isLoading && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
|
|
3181
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
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__ */ _jsxruntime.jsx.call(void 0,
|
|
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__ */ _jsxruntime.jsx.call(void 0,
|
|
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__ */ _jsxruntime.jsx.call(void 0, "div", { style: unlockModalStyles.container, children: view === "biometric" && shouldShowBiometric ? renderBiometricPrompt() : renderPinInput() });
|
|
3228
|
+
};
|
|
3229
|
+
var BiometricLargeIcon = () => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
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__ */ _jsxruntime.jsx.call(void 0,
|
|
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__ */ _jsxruntime.jsx.call(void 0,
|
|
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
|
+
);
|
|
3261
|
+
|
|
3262
|
+
|
|
3263
|
+
|
|
3264
|
+
|
|
3265
|
+
|
|
3266
|
+
|
|
3267
|
+
|
|
3268
|
+
|
|
3269
|
+
|
|
3270
|
+
|
|
3271
|
+
|
|
3272
|
+
|
|
3273
|
+
|
|
3274
|
+
|
|
3275
|
+
|
|
3276
|
+
|
|
3277
|
+
|
|
3278
|
+
|
|
3279
|
+
|
|
382
3280
|
|
|
383
3281
|
|
|
384
3282
|
|
|
@@ -386,5 +3284,5 @@ var useTonPay = () => {
|
|
|
386
3284
|
|
|
387
3285
|
|
|
388
3286
|
|
|
389
|
-
exports.
|
|
3287
|
+
exports.BottomSheet = BottomSheet_default; exports.ErrorTransactionNotification = ErrorTransactionNotification; exports.NotificationCard = NotificationCard; exports.NotificationRoot = NotificationRoot; exports.PaymentModal = PaymentModal; exports.PinInput = PinInput; exports.SignlessProvider = SignlessProvider; exports.SignlessSetupModal = SignlessSetupModal; exports.SignlessStorage = SignlessStorage; exports.SignlessUnlockModal = SignlessUnlockModal; exports.TonPayButton = TonPayButton; exports.createWebAuthnCredential = createWebAuthnCredential; exports.decryptPrivateKey = decryptPrivateKey; exports.encryptPrivateKey = encryptPrivateKey; exports.generateKeyPair = generateKeyPair; exports.getWebAuthnCredential = getWebAuthnCredential; exports.isWebAuthnSupported = isWebAuthnSupported; exports.signMessage = signMessage; exports.signlessStorage = signlessStorage; exports.useMoonPayIframe = useMoonPayIframe; exports.useSignless = useSignless; exports.useSignlessModal = useSignlessModal; exports.useTonPay = useTonPay; exports.useTonPaySignless = useTonPaySignless; exports.verifySignlessSignature = verifySignlessSignature;
|
|
390
3288
|
//# sourceMappingURL=index.js.map
|