@forge-connect/react 0.1.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +3786 -531
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +459 -37
- package/dist/index.d.ts +459 -37
- package/dist/index.js +3675 -390
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1010 -147
- package/package.json +20 -8
package/dist/index.js
CHANGED
|
@@ -1,12 +1,5 @@
|
|
|
1
|
-
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
-
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
-
}) : x)(function(x) {
|
|
4
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
-
});
|
|
7
|
-
|
|
8
1
|
// src/provider.tsx
|
|
9
|
-
import { useState as
|
|
2
|
+
import { useState as useState16, useCallback as useCallback8, useEffect as useEffect12, useRef as useRef8, useMemo as useMemo3 } from "react";
|
|
10
3
|
|
|
11
4
|
// src/context.ts
|
|
12
5
|
import { createContext } from "react";
|
|
@@ -25,7 +18,22 @@ var ForgeConnectApiError = class extends Error {
|
|
|
25
18
|
};
|
|
26
19
|
function createApiClient(apiUrl) {
|
|
27
20
|
const base = apiUrl.replace(/\/+$/, "");
|
|
21
|
+
const inflightRequests = /* @__PURE__ */ new Map();
|
|
28
22
|
async function request(path, options = {}) {
|
|
23
|
+
const { method = "GET", body, token } = options;
|
|
24
|
+
if (method === "GET" && token) {
|
|
25
|
+
const dedupKey = `${path}:${token}`;
|
|
26
|
+
const inflight = inflightRequests.get(dedupKey);
|
|
27
|
+
if (inflight) return inflight;
|
|
28
|
+
const promise = doRequest(path, options).finally(() => {
|
|
29
|
+
inflightRequests.delete(dedupKey);
|
|
30
|
+
});
|
|
31
|
+
inflightRequests.set(dedupKey, promise);
|
|
32
|
+
return promise;
|
|
33
|
+
}
|
|
34
|
+
return doRequest(path, options);
|
|
35
|
+
}
|
|
36
|
+
async function doRequest(path, options = {}) {
|
|
29
37
|
const { method = "GET", body, token } = options;
|
|
30
38
|
const headers = {
|
|
31
39
|
"Content-Type": "application/json"
|
|
@@ -50,6 +58,28 @@ function createApiClient(apiUrl) {
|
|
|
50
58
|
}
|
|
51
59
|
return res.json();
|
|
52
60
|
}
|
|
61
|
+
async function serviceRequest(path, options) {
|
|
62
|
+
const { method = "GET", body, serviceKey } = options;
|
|
63
|
+
const headers = {
|
|
64
|
+
"Content-Type": "application/json",
|
|
65
|
+
"x-service-key": serviceKey
|
|
66
|
+
};
|
|
67
|
+
const res = await fetch(`${base}${path}`, {
|
|
68
|
+
method,
|
|
69
|
+
headers,
|
|
70
|
+
body: body ? JSON.stringify(body) : void 0
|
|
71
|
+
});
|
|
72
|
+
if (!res.ok) {
|
|
73
|
+
let errorBody;
|
|
74
|
+
try {
|
|
75
|
+
errorBody = await res.json();
|
|
76
|
+
} catch {
|
|
77
|
+
throw new ForgeConnectApiError(res.status, "UNKNOWN", res.statusText);
|
|
78
|
+
}
|
|
79
|
+
throw new ForgeConnectApiError(res.status, errorBody.error.code, errorBody.error.message);
|
|
80
|
+
}
|
|
81
|
+
return res.json();
|
|
82
|
+
}
|
|
53
83
|
return {
|
|
54
84
|
// ── Auth: Email ──
|
|
55
85
|
register(email, password, displayName) {
|
|
@@ -89,6 +119,31 @@ function createApiClient(apiUrl) {
|
|
|
89
119
|
body: { challengeId, signature, walletAddress }
|
|
90
120
|
});
|
|
91
121
|
},
|
|
122
|
+
walletChallengeTx(walletAddress, chain = "solana") {
|
|
123
|
+
return request("/auth/wallet/challenge-tx", {
|
|
124
|
+
method: "POST",
|
|
125
|
+
body: { walletAddress, chain }
|
|
126
|
+
});
|
|
127
|
+
},
|
|
128
|
+
walletVerifyTx(challengeId, signedTransaction, walletAddress) {
|
|
129
|
+
return request("/auth/wallet/verify-tx", {
|
|
130
|
+
method: "POST",
|
|
131
|
+
body: { challengeId, signedTransaction, walletAddress }
|
|
132
|
+
});
|
|
133
|
+
},
|
|
134
|
+
// ── Auth: OAuth ──
|
|
135
|
+
exchangeOAuthCode(code) {
|
|
136
|
+
return request("/auth/oauth/exchange", {
|
|
137
|
+
method: "POST",
|
|
138
|
+
body: { code }
|
|
139
|
+
});
|
|
140
|
+
},
|
|
141
|
+
createLinkIntent(token) {
|
|
142
|
+
return request("/auth/oauth/link-intent", {
|
|
143
|
+
method: "POST",
|
|
144
|
+
token
|
|
145
|
+
});
|
|
146
|
+
},
|
|
92
147
|
// ── Auth: Session ──
|
|
93
148
|
refresh() {
|
|
94
149
|
return request("/auth/refresh", { method: "POST" });
|
|
@@ -112,6 +167,13 @@ function createApiClient(apiUrl) {
|
|
|
112
167
|
{ method: "PATCH", token, body: data }
|
|
113
168
|
);
|
|
114
169
|
},
|
|
170
|
+
setPassword(token, newPassword, currentPassword) {
|
|
171
|
+
return request("/users/me/password", {
|
|
172
|
+
method: "POST",
|
|
173
|
+
token,
|
|
174
|
+
body: { newPassword, ...currentPassword ? { currentPassword } : {} }
|
|
175
|
+
});
|
|
176
|
+
},
|
|
115
177
|
getAuthMethods(token) {
|
|
116
178
|
return request(
|
|
117
179
|
"/users/me/auth-methods",
|
|
@@ -125,6 +187,20 @@ function createApiClient(apiUrl) {
|
|
|
125
187
|
body: data
|
|
126
188
|
});
|
|
127
189
|
},
|
|
190
|
+
linkOtpSend(token, email) {
|
|
191
|
+
return request("/users/me/auth-methods/otp/send", {
|
|
192
|
+
method: "POST",
|
|
193
|
+
token,
|
|
194
|
+
body: { email }
|
|
195
|
+
});
|
|
196
|
+
},
|
|
197
|
+
linkOtpVerify(token, email, code) {
|
|
198
|
+
return request("/users/me/auth-methods/otp/verify", {
|
|
199
|
+
method: "POST",
|
|
200
|
+
token,
|
|
201
|
+
body: { email, code }
|
|
202
|
+
});
|
|
203
|
+
},
|
|
128
204
|
unlinkAuthMethod(token, id) {
|
|
129
205
|
return request(`/users/me/auth-methods/${id}`, {
|
|
130
206
|
method: "DELETE",
|
|
@@ -154,6 +230,173 @@ function createApiClient(apiUrl) {
|
|
|
154
230
|
method: "DELETE",
|
|
155
231
|
token
|
|
156
232
|
});
|
|
233
|
+
},
|
|
234
|
+
// ── Auth: Email Verification & Password Reset ──
|
|
235
|
+
verifyEmailToken(token) {
|
|
236
|
+
return request("/auth/email/verify", {
|
|
237
|
+
method: "POST",
|
|
238
|
+
body: { token }
|
|
239
|
+
});
|
|
240
|
+
},
|
|
241
|
+
forgotPassword(email) {
|
|
242
|
+
return request("/auth/email/forgot-password", {
|
|
243
|
+
method: "POST",
|
|
244
|
+
body: { email }
|
|
245
|
+
});
|
|
246
|
+
},
|
|
247
|
+
resetPassword(token, password) {
|
|
248
|
+
return request("/auth/email/reset-password", {
|
|
249
|
+
method: "POST",
|
|
250
|
+
body: { token, password }
|
|
251
|
+
});
|
|
252
|
+
},
|
|
253
|
+
// ── 2FA ──
|
|
254
|
+
get2FAStatus(token) {
|
|
255
|
+
return request("/users/me/2fa/status", { token });
|
|
256
|
+
},
|
|
257
|
+
setup2FA(token) {
|
|
258
|
+
return request("/users/me/2fa/setup", { method: "POST", token });
|
|
259
|
+
},
|
|
260
|
+
enable2FA(token, code) {
|
|
261
|
+
return request("/users/me/2fa/enable", { method: "POST", token, body: { code } });
|
|
262
|
+
},
|
|
263
|
+
disable2FA(token, code) {
|
|
264
|
+
return request("/users/me/2fa", { method: "DELETE", token, body: { code } });
|
|
265
|
+
},
|
|
266
|
+
verify2FA(challengeToken, code) {
|
|
267
|
+
return request("/auth/2fa/verify", { method: "POST", body: { challengeToken, code } });
|
|
268
|
+
},
|
|
269
|
+
verifyRecoveryCode(challengeToken, code) {
|
|
270
|
+
return request("/auth/2fa/verify-recovery", { method: "POST", body: { challengeToken, code } });
|
|
271
|
+
},
|
|
272
|
+
regenerateRecoveryCodes(token, code) {
|
|
273
|
+
return request("/users/me/2fa/recovery-codes", { method: "POST", token, body: { code } });
|
|
274
|
+
},
|
|
275
|
+
// ── Passkeys ──
|
|
276
|
+
getPasskeys(token) {
|
|
277
|
+
return request("/users/me/passkeys", { token });
|
|
278
|
+
},
|
|
279
|
+
getPasskeyRegisterOptions(token, rpId, origin) {
|
|
280
|
+
return request("/auth/passkeys/register/options", { method: "POST", token, body: { rpId, origin } });
|
|
281
|
+
},
|
|
282
|
+
verifyPasskeyRegistration(token, challengeKey, response, name, rpId, origin) {
|
|
283
|
+
return request("/auth/passkeys/register/verify", { method: "POST", token, body: { challengeKey, response, name, rpId, origin } });
|
|
284
|
+
},
|
|
285
|
+
getPasskeyLoginOptions(rpId) {
|
|
286
|
+
return request("/auth/passkeys/login/options", { method: "POST", body: { rpId } });
|
|
287
|
+
},
|
|
288
|
+
verifyPasskeyLogin(challengeKey, response, rpId, origin) {
|
|
289
|
+
return request("/auth/passkeys/login/verify", { method: "POST", body: { challengeKey, response, rpId, origin } });
|
|
290
|
+
},
|
|
291
|
+
renamePasskey(token, id, name) {
|
|
292
|
+
return request(`/users/me/passkeys/${id}`, { method: "PATCH", token, body: { name } });
|
|
293
|
+
},
|
|
294
|
+
deletePasskey(token, id) {
|
|
295
|
+
return request(`/users/me/passkeys/${id}`, { method: "DELETE", token });
|
|
296
|
+
},
|
|
297
|
+
// ── Account Deletion ──
|
|
298
|
+
requestAccountDeletion(token) {
|
|
299
|
+
return request("/users/me/delete-request", {
|
|
300
|
+
method: "POST",
|
|
301
|
+
token
|
|
302
|
+
});
|
|
303
|
+
},
|
|
304
|
+
confirmAccountDeletion(token, code) {
|
|
305
|
+
return request("/users/me/delete-confirm", {
|
|
306
|
+
method: "POST",
|
|
307
|
+
token,
|
|
308
|
+
body: code ? { code } : {}
|
|
309
|
+
});
|
|
310
|
+
},
|
|
311
|
+
// ── Admin ──
|
|
312
|
+
adminListUsers(token, params) {
|
|
313
|
+
const query = new URLSearchParams();
|
|
314
|
+
if (params?.page) query.set("page", String(params.page));
|
|
315
|
+
if (params?.limit) query.set("limit", String(params.limit));
|
|
316
|
+
if (params?.search) query.set("search", params.search);
|
|
317
|
+
const qs = query.toString();
|
|
318
|
+
return request(`/admin/users${qs ? `?${qs}` : ""}`, { token });
|
|
319
|
+
},
|
|
320
|
+
adminGetUser(token, id) {
|
|
321
|
+
return request(`/admin/users/${id}`, { token });
|
|
322
|
+
},
|
|
323
|
+
adminUpdateUserStatus(token, id, status) {
|
|
324
|
+
return request(`/admin/users/${id}/status`, {
|
|
325
|
+
method: "PATCH",
|
|
326
|
+
token,
|
|
327
|
+
body: { status }
|
|
328
|
+
});
|
|
329
|
+
},
|
|
330
|
+
adminGetUserSessions(token, id) {
|
|
331
|
+
return request(`/admin/users/${id}/sessions`, { token });
|
|
332
|
+
},
|
|
333
|
+
adminRevokeUserSessions(token, id) {
|
|
334
|
+
return request(`/admin/users/${id}/sessions`, {
|
|
335
|
+
method: "DELETE",
|
|
336
|
+
token
|
|
337
|
+
});
|
|
338
|
+
},
|
|
339
|
+
// ── Roles ──
|
|
340
|
+
adminListRoles(token, tenantId) {
|
|
341
|
+
const query = new URLSearchParams();
|
|
342
|
+
if (tenantId) query.set("tenantId", tenantId);
|
|
343
|
+
const qs = query.toString();
|
|
344
|
+
return request(`/admin/roles${qs ? `?${qs}` : ""}`, { token });
|
|
345
|
+
},
|
|
346
|
+
adminGetRole(token, id) {
|
|
347
|
+
return request(`/admin/roles/${id}`, { token });
|
|
348
|
+
},
|
|
349
|
+
adminGetRoleUsers(token, id) {
|
|
350
|
+
return request(`/admin/roles/${id}/users`, { token });
|
|
351
|
+
},
|
|
352
|
+
adminCreateRole(token, data) {
|
|
353
|
+
return request("/admin/roles", { method: "POST", token, body: data });
|
|
354
|
+
},
|
|
355
|
+
adminUpdateRole(token, id, data) {
|
|
356
|
+
return request(`/admin/roles/${id}`, { method: "PATCH", token, body: data });
|
|
357
|
+
},
|
|
358
|
+
adminDeleteRole(token, id) {
|
|
359
|
+
return request(`/admin/roles/${id}`, { method: "DELETE", token });
|
|
360
|
+
},
|
|
361
|
+
adminGetPermissions(token) {
|
|
362
|
+
return request("/admin/roles/permissions", { token });
|
|
363
|
+
},
|
|
364
|
+
adminGetUserRoles(token, userId, tenantId) {
|
|
365
|
+
const query = new URLSearchParams();
|
|
366
|
+
if (tenantId) query.set("tenantId", tenantId);
|
|
367
|
+
const qs = query.toString();
|
|
368
|
+
return request(`/admin/users/${userId}/roles${qs ? `?${qs}` : ""}`, { token });
|
|
369
|
+
},
|
|
370
|
+
adminAssignRole(token, userId, roleId, tenantId) {
|
|
371
|
+
return request(`/admin/users/${userId}/roles`, {
|
|
372
|
+
method: "POST",
|
|
373
|
+
token,
|
|
374
|
+
body: { roleId, ...tenantId ? { tenantId } : {} }
|
|
375
|
+
});
|
|
376
|
+
},
|
|
377
|
+
adminRevokeRole(token, userId, roleId, tenantId) {
|
|
378
|
+
const query = new URLSearchParams();
|
|
379
|
+
if (tenantId) query.set("tenantId", tenantId);
|
|
380
|
+
const qs = query.toString();
|
|
381
|
+
return request(`/admin/users/${userId}/roles/${roleId}${qs ? `?${qs}` : ""}`, {
|
|
382
|
+
method: "DELETE",
|
|
383
|
+
token
|
|
384
|
+
});
|
|
385
|
+
},
|
|
386
|
+
// ── Service ──
|
|
387
|
+
serviceVerifyToken(serviceKey, accessToken, tenantId) {
|
|
388
|
+
return serviceRequest("/service/verify-token", {
|
|
389
|
+
method: "POST",
|
|
390
|
+
serviceKey,
|
|
391
|
+
body: { token: accessToken, ...tenantId ? { tenantId } : {} }
|
|
392
|
+
});
|
|
393
|
+
},
|
|
394
|
+
serviceUserByWallet(serviceKey, walletAddress, chain) {
|
|
395
|
+
return serviceRequest("/service/user-by-wallet", {
|
|
396
|
+
method: "POST",
|
|
397
|
+
serviceKey,
|
|
398
|
+
body: { walletAddress, chain }
|
|
399
|
+
});
|
|
157
400
|
}
|
|
158
401
|
};
|
|
159
402
|
}
|
|
@@ -198,6 +441,9 @@ function getRefreshDelay(token) {
|
|
|
198
441
|
return delay > 0 ? delay : 0;
|
|
199
442
|
}
|
|
200
443
|
|
|
444
|
+
// src/components/login-modal.tsx
|
|
445
|
+
import { useState as useState6 } from "react";
|
|
446
|
+
|
|
201
447
|
// src/hooks/use-forge-connect.ts
|
|
202
448
|
import { useContext } from "react";
|
|
203
449
|
function useForgeConnect() {
|
|
@@ -223,7 +469,9 @@ function ModalOverlay({ isOpen, onClose, children }) {
|
|
|
223
469
|
document.body.style.overflow = "hidden";
|
|
224
470
|
return () => {
|
|
225
471
|
document.removeEventListener("keydown", handleKeyDown);
|
|
226
|
-
document.
|
|
472
|
+
if (!document.querySelector(".fc-overlay")) {
|
|
473
|
+
document.body.style.overflow = "";
|
|
474
|
+
}
|
|
227
475
|
};
|
|
228
476
|
}, [isOpen, onClose]);
|
|
229
477
|
if (!isOpen) return null;
|
|
@@ -241,9 +489,66 @@ function ModalOverlay({ isOpen, onClose, children }) {
|
|
|
241
489
|
|
|
242
490
|
// src/components/tabs/email-password.tsx
|
|
243
491
|
import { useState } from "react";
|
|
492
|
+
|
|
493
|
+
// src/resolve-config.ts
|
|
494
|
+
var OAUTH_PROVIDERS = /* @__PURE__ */ new Set(["google", "discord", "twitter", "apple", "telegram"]);
|
|
495
|
+
function isOAuthMethod(method) {
|
|
496
|
+
return OAUTH_PROVIDERS.has(method);
|
|
497
|
+
}
|
|
498
|
+
function resolveLoginMethods(config) {
|
|
499
|
+
if (config.loginMethods && config.loginMethods.length > 0) {
|
|
500
|
+
return config.loginMethods;
|
|
501
|
+
}
|
|
502
|
+
const methods = [];
|
|
503
|
+
if (config.oauthProviders) {
|
|
504
|
+
for (const p of config.oauthProviders) {
|
|
505
|
+
methods.push(p);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
methods.push("email");
|
|
509
|
+
if (config.passwordlessLogin !== false) {
|
|
510
|
+
methods.push("otp");
|
|
511
|
+
}
|
|
512
|
+
if (config.walletLogin !== false) {
|
|
513
|
+
methods.push("wallet");
|
|
514
|
+
}
|
|
515
|
+
return methods;
|
|
516
|
+
}
|
|
517
|
+
function loginMethodToStep(method) {
|
|
518
|
+
switch (method) {
|
|
519
|
+
case "email":
|
|
520
|
+
return "email-login";
|
|
521
|
+
case "otp":
|
|
522
|
+
return "email-otp";
|
|
523
|
+
case "wallet":
|
|
524
|
+
return "wallet-connect";
|
|
525
|
+
case "passkey":
|
|
526
|
+
return "method-select";
|
|
527
|
+
// Passkey triggers browser API directly
|
|
528
|
+
default:
|
|
529
|
+
return "method-select";
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
function resolveInitialStep(config, methods) {
|
|
533
|
+
if (methods.length === 1 && !isOAuthMethod(methods[0])) {
|
|
534
|
+
return loginMethodToStep(methods[0]);
|
|
535
|
+
}
|
|
536
|
+
const defaultMethod = config.defaultLoginMethod;
|
|
537
|
+
if (!defaultMethod || !methods.includes(defaultMethod)) {
|
|
538
|
+
return "method-select";
|
|
539
|
+
}
|
|
540
|
+
if (isOAuthMethod(defaultMethod)) {
|
|
541
|
+
return "method-select";
|
|
542
|
+
}
|
|
543
|
+
return loginMethodToStep(defaultMethod);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// src/components/tabs/email-password.tsx
|
|
244
547
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
245
548
|
function EmailLoginForm() {
|
|
246
|
-
const { loginWithEmail, setModalStep } = useForgeConnect();
|
|
549
|
+
const { loginWithEmail, setModalStep, config } = useForgeConnect();
|
|
550
|
+
const methods = resolveLoginMethods(config);
|
|
551
|
+
const showBack = methods.length > 1;
|
|
247
552
|
const [email, setEmail] = useState("");
|
|
248
553
|
const [password, setPassword] = useState("");
|
|
249
554
|
const [error, setError] = useState("");
|
|
@@ -255,7 +560,7 @@ function EmailLoginForm() {
|
|
|
255
560
|
try {
|
|
256
561
|
await loginWithEmail(email, password);
|
|
257
562
|
} catch (err) {
|
|
258
|
-
setError(err instanceof Error ? err.message : "
|
|
563
|
+
setError(err instanceof Error ? err.message : "Something went wrong. Please try again.");
|
|
259
564
|
} finally {
|
|
260
565
|
setLoading(false);
|
|
261
566
|
}
|
|
@@ -297,11 +602,13 @@ function EmailLoginForm() {
|
|
|
297
602
|
error && /* @__PURE__ */ jsx2("p", { className: "fc-error", children: error }),
|
|
298
603
|
/* @__PURE__ */ jsx2("button", { type: "submit", className: "fc-btn fc-btn-primary", disabled: loading, children: loading ? "Signing in..." : "Sign in" })
|
|
299
604
|
] }),
|
|
605
|
+
/* @__PURE__ */ jsx2("p", { className: "fc-switch", children: /* @__PURE__ */ jsx2("button", { type: "button", className: "fc-link", onClick: () => setModalStep("forgot-password"), children: "Forgot password?" }) }),
|
|
300
606
|
/* @__PURE__ */ jsxs2("p", { className: "fc-switch", children: [
|
|
301
607
|
"Don't have an account?",
|
|
302
608
|
" ",
|
|
303
609
|
/* @__PURE__ */ jsx2("button", { type: "button", className: "fc-link", onClick: () => setModalStep("email-register"), children: "Sign up" })
|
|
304
|
-
] })
|
|
610
|
+
] }),
|
|
611
|
+
showBack && /* @__PURE__ */ jsx2("p", { className: "fc-switch", children: /* @__PURE__ */ jsx2("button", { type: "button", className: "fc-link", onClick: () => setModalStep("method-select"), children: "Back" }) })
|
|
305
612
|
] });
|
|
306
613
|
}
|
|
307
614
|
function EmailRegisterForm() {
|
|
@@ -320,7 +627,7 @@ function EmailRegisterForm() {
|
|
|
320
627
|
await register(email, password, displayName || void 0);
|
|
321
628
|
setSuccess(true);
|
|
322
629
|
} catch (err) {
|
|
323
|
-
setError(err instanceof Error ? err.message : "
|
|
630
|
+
setError(err instanceof Error ? err.message : "Something went wrong. Please try again.");
|
|
324
631
|
} finally {
|
|
325
632
|
setLoading(false);
|
|
326
633
|
}
|
|
@@ -399,7 +706,9 @@ function EmailRegisterForm() {
|
|
|
399
706
|
import { useState as useState2, useRef as useRef2 } from "react";
|
|
400
707
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
401
708
|
function EmailOtpForm() {
|
|
402
|
-
const { sendOtp, verifyOtp, setModalStep } = useForgeConnect();
|
|
709
|
+
const { sendOtp, verifyOtp, setModalStep, config } = useForgeConnect();
|
|
710
|
+
const methods = resolveLoginMethods(config);
|
|
711
|
+
const showBack = methods.length > 1;
|
|
403
712
|
const [email, setEmail] = useState2("");
|
|
404
713
|
const [code, setCode] = useState2(["", "", "", "", "", ""]);
|
|
405
714
|
const [step, setStep] = useState2("email");
|
|
@@ -414,7 +723,7 @@ function EmailOtpForm() {
|
|
|
414
723
|
await sendOtp(email);
|
|
415
724
|
setStep("code");
|
|
416
725
|
} catch (err) {
|
|
417
|
-
setError(err instanceof Error ? err.message : "
|
|
726
|
+
setError(err instanceof Error ? err.message : "Could not send the code. Please try again.");
|
|
418
727
|
} finally {
|
|
419
728
|
setLoading(false);
|
|
420
729
|
}
|
|
@@ -427,7 +736,7 @@ function EmailOtpForm() {
|
|
|
427
736
|
try {
|
|
428
737
|
await verifyOtp(email, codeStr);
|
|
429
738
|
} catch (err) {
|
|
430
|
-
setError(err instanceof Error ? err.message : "
|
|
739
|
+
setError(err instanceof Error ? err.message : "This code is incorrect. Please check and try again.");
|
|
431
740
|
setLoading(false);
|
|
432
741
|
}
|
|
433
742
|
};
|
|
@@ -484,7 +793,7 @@ function EmailOtpForm() {
|
|
|
484
793
|
error && /* @__PURE__ */ jsx3("p", { className: "fc-error", children: error }),
|
|
485
794
|
/* @__PURE__ */ jsx3("button", { type: "submit", className: "fc-btn fc-btn-primary", disabled: loading, children: loading ? "Sending..." : "Send code" })
|
|
486
795
|
] }),
|
|
487
|
-
/* @__PURE__ */ jsx3("p", { className: "fc-switch", children: /* @__PURE__ */ jsx3("button", { type: "button", className: "fc-link", onClick: () => setModalStep("method-select"), children: "Back" }) })
|
|
796
|
+
showBack && /* @__PURE__ */ jsx3("p", { className: "fc-switch", children: /* @__PURE__ */ jsx3("button", { type: "button", className: "fc-link", onClick: () => setModalStep("method-select"), children: "Back" }) })
|
|
488
797
|
] });
|
|
489
798
|
}
|
|
490
799
|
return /* @__PURE__ */ jsxs3("div", { className: "fc-tab", children: [
|
|
@@ -522,254 +831,2917 @@ function EmailOtpForm() {
|
|
|
522
831
|
}
|
|
523
832
|
|
|
524
833
|
// src/components/tabs/wallet-connect.tsx
|
|
525
|
-
import { useState as useState3 } from "react";
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
834
|
+
import { useState as useState3, useMemo, useRef as useRef3, useCallback } from "react";
|
|
835
|
+
|
|
836
|
+
// ../../node_modules/.pnpm/@simplewebauthn+browser@13.2.2/node_modules/@simplewebauthn/browser/esm/helpers/bufferToBase64URLString.js
|
|
837
|
+
function bufferToBase64URLString(buffer) {
|
|
838
|
+
const bytes = new Uint8Array(buffer);
|
|
839
|
+
let str = "";
|
|
840
|
+
for (const charCode of bytes) {
|
|
841
|
+
str += String.fromCharCode(charCode);
|
|
533
842
|
}
|
|
843
|
+
const base64String = btoa(str);
|
|
844
|
+
return base64String.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
534
845
|
}
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
846
|
+
|
|
847
|
+
// ../../node_modules/.pnpm/@simplewebauthn+browser@13.2.2/node_modules/@simplewebauthn/browser/esm/helpers/base64URLStringToBuffer.js
|
|
848
|
+
function base64URLStringToBuffer(base64URLString) {
|
|
849
|
+
const base64 = base64URLString.replace(/-/g, "+").replace(/_/g, "/");
|
|
850
|
+
const padLength = (4 - base64.length % 4) % 4;
|
|
851
|
+
const padded = base64.padEnd(base64.length + padLength, "=");
|
|
852
|
+
const binary = atob(padded);
|
|
853
|
+
const buffer = new ArrayBuffer(binary.length);
|
|
854
|
+
const bytes = new Uint8Array(buffer);
|
|
855
|
+
for (let i = 0; i < binary.length; i++) {
|
|
856
|
+
bytes[i] = binary.charCodeAt(i);
|
|
540
857
|
}
|
|
541
|
-
return
|
|
542
|
-
/* @__PURE__ */ jsx4("h3", { className: "fc-tab-title", children: "Connect wallet" }),
|
|
543
|
-
/* @__PURE__ */ jsxs4("p", { className: "fc-text", children: [
|
|
544
|
-
"Install a Solana wallet extension (Phantom, Solflare, etc.) and ensure",
|
|
545
|
-
" ",
|
|
546
|
-
/* @__PURE__ */ jsx4("code", { children: "@solana/wallet-adapter-react" }),
|
|
547
|
-
" is configured in your app."
|
|
548
|
-
] }),
|
|
549
|
-
/* @__PURE__ */ jsx4("p", { className: "fc-text fc-text-muted", children: "Wallet connection requires the Solana wallet adapter provider in your app." }),
|
|
550
|
-
/* @__PURE__ */ jsx4("p", { className: "fc-switch", children: /* @__PURE__ */ jsx4("button", { type: "button", className: "fc-link", onClick: () => setModalStep("method-select"), children: "Back" }) })
|
|
551
|
-
] });
|
|
858
|
+
return buffer;
|
|
552
859
|
}
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
setLoading(false);
|
|
575
|
-
}
|
|
860
|
+
|
|
861
|
+
// ../../node_modules/.pnpm/@simplewebauthn+browser@13.2.2/node_modules/@simplewebauthn/browser/esm/helpers/browserSupportsWebAuthn.js
|
|
862
|
+
function browserSupportsWebAuthn() {
|
|
863
|
+
return _browserSupportsWebAuthnInternals.stubThis(globalThis?.PublicKeyCredential !== void 0 && typeof globalThis.PublicKeyCredential === "function");
|
|
864
|
+
}
|
|
865
|
+
var _browserSupportsWebAuthnInternals = {
|
|
866
|
+
stubThis: (value) => value
|
|
867
|
+
};
|
|
868
|
+
|
|
869
|
+
// ../../node_modules/.pnpm/@simplewebauthn+browser@13.2.2/node_modules/@simplewebauthn/browser/esm/helpers/toPublicKeyCredentialDescriptor.js
|
|
870
|
+
function toPublicKeyCredentialDescriptor(descriptor) {
|
|
871
|
+
const { id } = descriptor;
|
|
872
|
+
return {
|
|
873
|
+
...descriptor,
|
|
874
|
+
id: base64URLStringToBuffer(id),
|
|
875
|
+
/**
|
|
876
|
+
* `descriptor.transports` is an array of our `AuthenticatorTransportFuture` that includes newer
|
|
877
|
+
* transports that TypeScript's DOM lib is ignorant of. Convince TS that our list of transports
|
|
878
|
+
* are fine to pass to WebAuthn since browsers will recognize the new value.
|
|
879
|
+
*/
|
|
880
|
+
transports: descriptor.transports
|
|
576
881
|
};
|
|
577
|
-
return /* @__PURE__ */ jsxs4("div", { className: "fc-tab", children: [
|
|
578
|
-
/* @__PURE__ */ jsx4("h3", { className: "fc-tab-title", children: "Connect wallet" }),
|
|
579
|
-
wallet.publicKey ? /* @__PURE__ */ jsxs4(Fragment, { children: [
|
|
580
|
-
/* @__PURE__ */ jsxs4("p", { className: "fc-text fc-wallet-address", children: [
|
|
581
|
-
wallet.publicKey.toBase58().slice(0, 4),
|
|
582
|
-
"...",
|
|
583
|
-
wallet.publicKey.toBase58().slice(-4)
|
|
584
|
-
] }),
|
|
585
|
-
error && /* @__PURE__ */ jsx4("p", { className: "fc-error", children: error }),
|
|
586
|
-
/* @__PURE__ */ jsx4(
|
|
587
|
-
"button",
|
|
588
|
-
{
|
|
589
|
-
type: "button",
|
|
590
|
-
className: "fc-btn fc-btn-primary",
|
|
591
|
-
onClick: handleConnect,
|
|
592
|
-
disabled: loading,
|
|
593
|
-
children: loading ? "Signing..." : "Sign to verify"
|
|
594
|
-
}
|
|
595
|
-
)
|
|
596
|
-
] }) : /* @__PURE__ */ jsxs4(Fragment, { children: [
|
|
597
|
-
/* @__PURE__ */ jsx4("div", { className: "fc-wallet-list", children: wallet.wallets.filter((w) => w.readyState === "Installed").map((w) => /* @__PURE__ */ jsxs4(
|
|
598
|
-
"button",
|
|
599
|
-
{
|
|
600
|
-
type: "button",
|
|
601
|
-
className: "fc-btn fc-btn-wallet",
|
|
602
|
-
onClick: async () => {
|
|
603
|
-
wallet.select(w.adapter.name);
|
|
604
|
-
try {
|
|
605
|
-
await wallet.connect();
|
|
606
|
-
} catch {
|
|
607
|
-
setError("Failed to connect wallet");
|
|
608
|
-
}
|
|
609
|
-
},
|
|
610
|
-
children: [
|
|
611
|
-
/* @__PURE__ */ jsx4("img", { src: w.adapter.icon, alt: "", className: "fc-wallet-icon" }),
|
|
612
|
-
w.adapter.name
|
|
613
|
-
]
|
|
614
|
-
},
|
|
615
|
-
w.adapter.name
|
|
616
|
-
)) }),
|
|
617
|
-
wallet.wallets.filter((w) => w.readyState === "Installed").length === 0 && /* @__PURE__ */ jsx4("p", { className: "fc-text", children: "No wallets detected. Install a Solana wallet extension." }),
|
|
618
|
-
error && /* @__PURE__ */ jsx4("p", { className: "fc-error", children: error })
|
|
619
|
-
] }),
|
|
620
|
-
/* @__PURE__ */ jsx4("p", { className: "fc-switch", children: /* @__PURE__ */ jsx4("button", { type: "button", className: "fc-link", onClick: () => setModalStep("method-select"), children: "Back" }) })
|
|
621
|
-
] });
|
|
622
882
|
}
|
|
623
883
|
|
|
624
|
-
//
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
884
|
+
// ../../node_modules/.pnpm/@simplewebauthn+browser@13.2.2/node_modules/@simplewebauthn/browser/esm/helpers/isValidDomain.js
|
|
885
|
+
function isValidDomain(hostname) {
|
|
886
|
+
return (
|
|
887
|
+
// Consider localhost valid as well since it's okay wrt Secure Contexts
|
|
888
|
+
hostname === "localhost" || /^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$/i.test(hostname)
|
|
889
|
+
);
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// ../../node_modules/.pnpm/@simplewebauthn+browser@13.2.2/node_modules/@simplewebauthn/browser/esm/helpers/webAuthnError.js
|
|
893
|
+
var WebAuthnError = class extends Error {
|
|
894
|
+
constructor({ message, code, cause, name }) {
|
|
895
|
+
super(message, { cause });
|
|
896
|
+
Object.defineProperty(this, "code", {
|
|
897
|
+
enumerable: true,
|
|
898
|
+
configurable: true,
|
|
899
|
+
writable: true,
|
|
900
|
+
value: void 0
|
|
901
|
+
});
|
|
902
|
+
this.name = name ?? cause.name;
|
|
903
|
+
this.code = code;
|
|
642
904
|
}
|
|
643
905
|
};
|
|
644
|
-
function OAuthButtons() {
|
|
645
|
-
const { config, loginWithOAuth } = useForgeConnect();
|
|
646
|
-
const providers = config.oauthProviders ?? [];
|
|
647
|
-
if (providers.length === 0) return null;
|
|
648
|
-
return /* @__PURE__ */ jsx5("div", { className: "fc-oauth-buttons", children: providers.map((provider) => {
|
|
649
|
-
const info = PROVIDER_INFO[provider];
|
|
650
|
-
return /* @__PURE__ */ jsxs5(
|
|
651
|
-
"button",
|
|
652
|
-
{
|
|
653
|
-
type: "button",
|
|
654
|
-
className: "fc-btn fc-btn-oauth",
|
|
655
|
-
onClick: () => loginWithOAuth(provider),
|
|
656
|
-
children: [
|
|
657
|
-
/* @__PURE__ */ jsx5("span", { className: "fc-oauth-icon", dangerouslySetInnerHTML: { __html: info.icon } }),
|
|
658
|
-
"Continue with ",
|
|
659
|
-
info.label
|
|
660
|
-
]
|
|
661
|
-
},
|
|
662
|
-
provider
|
|
663
|
-
);
|
|
664
|
-
}) });
|
|
665
|
-
}
|
|
666
906
|
|
|
667
|
-
//
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
return /* @__PURE__ */ jsx6(WalletConnectForm, {});
|
|
681
|
-
case "method-select":
|
|
682
|
-
default:
|
|
683
|
-
return /* @__PURE__ */ jsx6(MethodSelect, {});
|
|
907
|
+
// ../../node_modules/.pnpm/@simplewebauthn+browser@13.2.2/node_modules/@simplewebauthn/browser/esm/helpers/identifyRegistrationError.js
|
|
908
|
+
function identifyRegistrationError({ error, options }) {
|
|
909
|
+
const { publicKey } = options;
|
|
910
|
+
if (!publicKey) {
|
|
911
|
+
throw Error("options was missing required publicKey property");
|
|
912
|
+
}
|
|
913
|
+
if (error.name === "AbortError") {
|
|
914
|
+
if (options.signal instanceof AbortSignal) {
|
|
915
|
+
return new WebAuthnError({
|
|
916
|
+
message: "Registration ceremony was sent an abort signal",
|
|
917
|
+
code: "ERROR_CEREMONY_ABORTED",
|
|
918
|
+
cause: error
|
|
919
|
+
});
|
|
684
920
|
}
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
921
|
+
} else if (error.name === "ConstraintError") {
|
|
922
|
+
if (publicKey.authenticatorSelection?.requireResidentKey === true) {
|
|
923
|
+
return new WebAuthnError({
|
|
924
|
+
message: "Discoverable credentials were required but no available authenticator supported it",
|
|
925
|
+
code: "ERROR_AUTHENTICATOR_MISSING_DISCOVERABLE_CREDENTIAL_SUPPORT",
|
|
926
|
+
cause: error
|
|
927
|
+
});
|
|
928
|
+
} else if (
|
|
929
|
+
// @ts-ignore: `mediation` doesn't yet exist on CredentialCreationOptions but it's possible as of Sept 2024
|
|
930
|
+
options.mediation === "conditional" && publicKey.authenticatorSelection?.userVerification === "required"
|
|
931
|
+
) {
|
|
932
|
+
return new WebAuthnError({
|
|
933
|
+
message: "User verification was required during automatic registration but it could not be performed",
|
|
934
|
+
code: "ERROR_AUTO_REGISTER_USER_VERIFICATION_FAILURE",
|
|
935
|
+
cause: error
|
|
936
|
+
});
|
|
937
|
+
} else if (publicKey.authenticatorSelection?.userVerification === "required") {
|
|
938
|
+
return new WebAuthnError({
|
|
939
|
+
message: "User verification was required but no available authenticator supported it",
|
|
940
|
+
code: "ERROR_AUTHENTICATOR_MISSING_USER_VERIFICATION_SUPPORT",
|
|
941
|
+
cause: error
|
|
942
|
+
});
|
|
699
943
|
}
|
|
700
|
-
)
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
944
|
+
} else if (error.name === "InvalidStateError") {
|
|
945
|
+
return new WebAuthnError({
|
|
946
|
+
message: "The authenticator was previously registered",
|
|
947
|
+
code: "ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED",
|
|
948
|
+
cause: error
|
|
949
|
+
});
|
|
950
|
+
} else if (error.name === "NotAllowedError") {
|
|
951
|
+
return new WebAuthnError({
|
|
952
|
+
message: error.message,
|
|
953
|
+
code: "ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY",
|
|
954
|
+
cause: error
|
|
955
|
+
});
|
|
956
|
+
} else if (error.name === "NotSupportedError") {
|
|
957
|
+
const validPubKeyCredParams = publicKey.pubKeyCredParams.filter((param) => param.type === "public-key");
|
|
958
|
+
if (validPubKeyCredParams.length === 0) {
|
|
959
|
+
return new WebAuthnError({
|
|
960
|
+
message: 'No entry in pubKeyCredParams was of type "public-key"',
|
|
961
|
+
code: "ERROR_MALFORMED_PUBKEYCREDPARAMS",
|
|
962
|
+
cause: error
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
return new WebAuthnError({
|
|
966
|
+
message: "No available authenticator supported any of the specified pubKeyCredParams algorithms",
|
|
967
|
+
code: "ERROR_AUTHENTICATOR_NO_SUPPORTED_PUBKEYCREDPARAMS_ALG",
|
|
968
|
+
cause: error
|
|
969
|
+
});
|
|
970
|
+
} else if (error.name === "SecurityError") {
|
|
971
|
+
const effectiveDomain = globalThis.location.hostname;
|
|
972
|
+
if (!isValidDomain(effectiveDomain)) {
|
|
973
|
+
return new WebAuthnError({
|
|
974
|
+
message: `${globalThis.location.hostname} is an invalid domain`,
|
|
975
|
+
code: "ERROR_INVALID_DOMAIN",
|
|
976
|
+
cause: error
|
|
977
|
+
});
|
|
978
|
+
} else if (publicKey.rp.id !== effectiveDomain) {
|
|
979
|
+
return new WebAuthnError({
|
|
980
|
+
message: `The RP ID "${publicKey.rp.id}" is invalid for this domain`,
|
|
981
|
+
code: "ERROR_INVALID_RP_ID",
|
|
982
|
+
cause: error
|
|
983
|
+
});
|
|
984
|
+
}
|
|
985
|
+
} else if (error.name === "TypeError") {
|
|
986
|
+
if (publicKey.user.id.byteLength < 1 || publicKey.user.id.byteLength > 64) {
|
|
987
|
+
return new WebAuthnError({
|
|
988
|
+
message: "User ID was not between 1 and 64 characters",
|
|
989
|
+
code: "ERROR_INVALID_USER_ID_LENGTH",
|
|
990
|
+
cause: error
|
|
991
|
+
});
|
|
992
|
+
}
|
|
993
|
+
} else if (error.name === "UnknownError") {
|
|
994
|
+
return new WebAuthnError({
|
|
995
|
+
message: "The authenticator was unable to process the specified options, or could not create a new credential",
|
|
996
|
+
code: "ERROR_AUTHENTICATOR_GENERAL_ERROR",
|
|
997
|
+
cause: error
|
|
998
|
+
});
|
|
999
|
+
}
|
|
1000
|
+
return error;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
// ../../node_modules/.pnpm/@simplewebauthn+browser@13.2.2/node_modules/@simplewebauthn/browser/esm/helpers/webAuthnAbortService.js
|
|
1004
|
+
var BaseWebAuthnAbortService = class {
|
|
1005
|
+
constructor() {
|
|
1006
|
+
Object.defineProperty(this, "controller", {
|
|
1007
|
+
enumerable: true,
|
|
1008
|
+
configurable: true,
|
|
1009
|
+
writable: true,
|
|
1010
|
+
value: void 0
|
|
1011
|
+
});
|
|
1012
|
+
}
|
|
1013
|
+
createNewAbortSignal() {
|
|
1014
|
+
if (this.controller) {
|
|
1015
|
+
const abortError = new Error("Cancelling existing WebAuthn API call for new one");
|
|
1016
|
+
abortError.name = "AbortError";
|
|
1017
|
+
this.controller.abort(abortError);
|
|
1018
|
+
}
|
|
1019
|
+
const newController = new AbortController();
|
|
1020
|
+
this.controller = newController;
|
|
1021
|
+
return newController.signal;
|
|
1022
|
+
}
|
|
1023
|
+
cancelCeremony() {
|
|
1024
|
+
if (this.controller) {
|
|
1025
|
+
const abortError = new Error("Manually cancelling existing WebAuthn API call");
|
|
1026
|
+
abortError.name = "AbortError";
|
|
1027
|
+
this.controller.abort(abortError);
|
|
1028
|
+
this.controller = void 0;
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
};
|
|
1032
|
+
var WebAuthnAbortService = new BaseWebAuthnAbortService();
|
|
1033
|
+
|
|
1034
|
+
// ../../node_modules/.pnpm/@simplewebauthn+browser@13.2.2/node_modules/@simplewebauthn/browser/esm/helpers/toAuthenticatorAttachment.js
|
|
1035
|
+
var attachments = ["cross-platform", "platform"];
|
|
1036
|
+
function toAuthenticatorAttachment(attachment) {
|
|
1037
|
+
if (!attachment) {
|
|
1038
|
+
return;
|
|
1039
|
+
}
|
|
1040
|
+
if (attachments.indexOf(attachment) < 0) {
|
|
1041
|
+
return;
|
|
1042
|
+
}
|
|
1043
|
+
return attachment;
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
// ../../node_modules/.pnpm/@simplewebauthn+browser@13.2.2/node_modules/@simplewebauthn/browser/esm/methods/startRegistration.js
|
|
1047
|
+
async function startRegistration(options) {
|
|
1048
|
+
if (!options.optionsJSON && options.challenge) {
|
|
1049
|
+
console.warn("startRegistration() was not called correctly. It will try to continue with the provided options, but this call should be refactored to use the expected call structure instead. See https://simplewebauthn.dev/docs/packages/browser#typeerror-cannot-read-properties-of-undefined-reading-challenge for more information.");
|
|
1050
|
+
options = { optionsJSON: options };
|
|
1051
|
+
}
|
|
1052
|
+
const { optionsJSON, useAutoRegister = false } = options;
|
|
1053
|
+
if (!browserSupportsWebAuthn()) {
|
|
1054
|
+
throw new Error("WebAuthn is not supported in this browser");
|
|
1055
|
+
}
|
|
1056
|
+
const publicKey = {
|
|
1057
|
+
...optionsJSON,
|
|
1058
|
+
challenge: base64URLStringToBuffer(optionsJSON.challenge),
|
|
1059
|
+
user: {
|
|
1060
|
+
...optionsJSON.user,
|
|
1061
|
+
id: base64URLStringToBuffer(optionsJSON.user.id)
|
|
1062
|
+
},
|
|
1063
|
+
excludeCredentials: optionsJSON.excludeCredentials?.map(toPublicKeyCredentialDescriptor)
|
|
1064
|
+
};
|
|
1065
|
+
const createOptions = {};
|
|
1066
|
+
if (useAutoRegister) {
|
|
1067
|
+
createOptions.mediation = "conditional";
|
|
1068
|
+
}
|
|
1069
|
+
createOptions.publicKey = publicKey;
|
|
1070
|
+
createOptions.signal = WebAuthnAbortService.createNewAbortSignal();
|
|
1071
|
+
let credential;
|
|
1072
|
+
try {
|
|
1073
|
+
credential = await navigator.credentials.create(createOptions);
|
|
1074
|
+
} catch (err) {
|
|
1075
|
+
throw identifyRegistrationError({ error: err, options: createOptions });
|
|
1076
|
+
}
|
|
1077
|
+
if (!credential) {
|
|
1078
|
+
throw new Error("Registration was not completed");
|
|
1079
|
+
}
|
|
1080
|
+
const { id, rawId, response, type } = credential;
|
|
1081
|
+
let transports = void 0;
|
|
1082
|
+
if (typeof response.getTransports === "function") {
|
|
1083
|
+
transports = response.getTransports();
|
|
1084
|
+
}
|
|
1085
|
+
let responsePublicKeyAlgorithm = void 0;
|
|
1086
|
+
if (typeof response.getPublicKeyAlgorithm === "function") {
|
|
1087
|
+
try {
|
|
1088
|
+
responsePublicKeyAlgorithm = response.getPublicKeyAlgorithm();
|
|
1089
|
+
} catch (error) {
|
|
1090
|
+
warnOnBrokenImplementation("getPublicKeyAlgorithm()", error);
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
let responsePublicKey = void 0;
|
|
1094
|
+
if (typeof response.getPublicKey === "function") {
|
|
1095
|
+
try {
|
|
1096
|
+
const _publicKey = response.getPublicKey();
|
|
1097
|
+
if (_publicKey !== null) {
|
|
1098
|
+
responsePublicKey = bufferToBase64URLString(_publicKey);
|
|
1099
|
+
}
|
|
1100
|
+
} catch (error) {
|
|
1101
|
+
warnOnBrokenImplementation("getPublicKey()", error);
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
let responseAuthenticatorData;
|
|
1105
|
+
if (typeof response.getAuthenticatorData === "function") {
|
|
1106
|
+
try {
|
|
1107
|
+
responseAuthenticatorData = bufferToBase64URLString(response.getAuthenticatorData());
|
|
1108
|
+
} catch (error) {
|
|
1109
|
+
warnOnBrokenImplementation("getAuthenticatorData()", error);
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
return {
|
|
1113
|
+
id,
|
|
1114
|
+
rawId: bufferToBase64URLString(rawId),
|
|
1115
|
+
response: {
|
|
1116
|
+
attestationObject: bufferToBase64URLString(response.attestationObject),
|
|
1117
|
+
clientDataJSON: bufferToBase64URLString(response.clientDataJSON),
|
|
1118
|
+
transports,
|
|
1119
|
+
publicKeyAlgorithm: responsePublicKeyAlgorithm,
|
|
1120
|
+
publicKey: responsePublicKey,
|
|
1121
|
+
authenticatorData: responseAuthenticatorData
|
|
1122
|
+
},
|
|
1123
|
+
type,
|
|
1124
|
+
clientExtensionResults: credential.getClientExtensionResults(),
|
|
1125
|
+
authenticatorAttachment: toAuthenticatorAttachment(credential.authenticatorAttachment)
|
|
1126
|
+
};
|
|
1127
|
+
}
|
|
1128
|
+
function warnOnBrokenImplementation(methodName, cause) {
|
|
1129
|
+
console.warn(`The browser extension that intercepted this WebAuthn API call incorrectly implemented ${methodName}. You should report this error to them.
|
|
1130
|
+
`, cause);
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
// ../../node_modules/.pnpm/@simplewebauthn+browser@13.2.2/node_modules/@simplewebauthn/browser/esm/helpers/browserSupportsWebAuthnAutofill.js
|
|
1134
|
+
function browserSupportsWebAuthnAutofill() {
|
|
1135
|
+
if (!browserSupportsWebAuthn()) {
|
|
1136
|
+
return _browserSupportsWebAuthnAutofillInternals.stubThis(new Promise((resolve) => resolve(false)));
|
|
1137
|
+
}
|
|
1138
|
+
const globalPublicKeyCredential = globalThis.PublicKeyCredential;
|
|
1139
|
+
if (globalPublicKeyCredential?.isConditionalMediationAvailable === void 0) {
|
|
1140
|
+
return _browserSupportsWebAuthnAutofillInternals.stubThis(new Promise((resolve) => resolve(false)));
|
|
1141
|
+
}
|
|
1142
|
+
return _browserSupportsWebAuthnAutofillInternals.stubThis(globalPublicKeyCredential.isConditionalMediationAvailable());
|
|
1143
|
+
}
|
|
1144
|
+
var _browserSupportsWebAuthnAutofillInternals = {
|
|
1145
|
+
stubThis: (value) => value
|
|
1146
|
+
};
|
|
1147
|
+
|
|
1148
|
+
// ../../node_modules/.pnpm/@simplewebauthn+browser@13.2.2/node_modules/@simplewebauthn/browser/esm/helpers/identifyAuthenticationError.js
|
|
1149
|
+
function identifyAuthenticationError({ error, options }) {
|
|
1150
|
+
const { publicKey } = options;
|
|
1151
|
+
if (!publicKey) {
|
|
1152
|
+
throw Error("options was missing required publicKey property");
|
|
1153
|
+
}
|
|
1154
|
+
if (error.name === "AbortError") {
|
|
1155
|
+
if (options.signal instanceof AbortSignal) {
|
|
1156
|
+
return new WebAuthnError({
|
|
1157
|
+
message: "Authentication ceremony was sent an abort signal",
|
|
1158
|
+
code: "ERROR_CEREMONY_ABORTED",
|
|
1159
|
+
cause: error
|
|
1160
|
+
});
|
|
1161
|
+
}
|
|
1162
|
+
} else if (error.name === "NotAllowedError") {
|
|
1163
|
+
return new WebAuthnError({
|
|
1164
|
+
message: error.message,
|
|
1165
|
+
code: "ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY",
|
|
1166
|
+
cause: error
|
|
1167
|
+
});
|
|
1168
|
+
} else if (error.name === "SecurityError") {
|
|
1169
|
+
const effectiveDomain = globalThis.location.hostname;
|
|
1170
|
+
if (!isValidDomain(effectiveDomain)) {
|
|
1171
|
+
return new WebAuthnError({
|
|
1172
|
+
message: `${globalThis.location.hostname} is an invalid domain`,
|
|
1173
|
+
code: "ERROR_INVALID_DOMAIN",
|
|
1174
|
+
cause: error
|
|
1175
|
+
});
|
|
1176
|
+
} else if (publicKey.rpId !== effectiveDomain) {
|
|
1177
|
+
return new WebAuthnError({
|
|
1178
|
+
message: `The RP ID "${publicKey.rpId}" is invalid for this domain`,
|
|
1179
|
+
code: "ERROR_INVALID_RP_ID",
|
|
1180
|
+
cause: error
|
|
1181
|
+
});
|
|
1182
|
+
}
|
|
1183
|
+
} else if (error.name === "UnknownError") {
|
|
1184
|
+
return new WebAuthnError({
|
|
1185
|
+
message: "The authenticator was unable to process the specified options, or could not create a new assertion signature",
|
|
1186
|
+
code: "ERROR_AUTHENTICATOR_GENERAL_ERROR",
|
|
1187
|
+
cause: error
|
|
1188
|
+
});
|
|
1189
|
+
}
|
|
1190
|
+
return error;
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
// ../../node_modules/.pnpm/@simplewebauthn+browser@13.2.2/node_modules/@simplewebauthn/browser/esm/methods/startAuthentication.js
|
|
1194
|
+
async function startAuthentication(options) {
|
|
1195
|
+
if (!options.optionsJSON && options.challenge) {
|
|
1196
|
+
console.warn("startAuthentication() was not called correctly. It will try to continue with the provided options, but this call should be refactored to use the expected call structure instead. See https://simplewebauthn.dev/docs/packages/browser#typeerror-cannot-read-properties-of-undefined-reading-challenge for more information.");
|
|
1197
|
+
options = { optionsJSON: options };
|
|
1198
|
+
}
|
|
1199
|
+
const { optionsJSON, useBrowserAutofill = false, verifyBrowserAutofillInput = true } = options;
|
|
1200
|
+
if (!browserSupportsWebAuthn()) {
|
|
1201
|
+
throw new Error("WebAuthn is not supported in this browser");
|
|
1202
|
+
}
|
|
1203
|
+
let allowCredentials;
|
|
1204
|
+
if (optionsJSON.allowCredentials?.length !== 0) {
|
|
1205
|
+
allowCredentials = optionsJSON.allowCredentials?.map(toPublicKeyCredentialDescriptor);
|
|
1206
|
+
}
|
|
1207
|
+
const publicKey = {
|
|
1208
|
+
...optionsJSON,
|
|
1209
|
+
challenge: base64URLStringToBuffer(optionsJSON.challenge),
|
|
1210
|
+
allowCredentials
|
|
1211
|
+
};
|
|
1212
|
+
const getOptions = {};
|
|
1213
|
+
if (useBrowserAutofill) {
|
|
1214
|
+
if (!await browserSupportsWebAuthnAutofill()) {
|
|
1215
|
+
throw Error("Browser does not support WebAuthn autofill");
|
|
1216
|
+
}
|
|
1217
|
+
const eligibleInputs = document.querySelectorAll("input[autocomplete$='webauthn']");
|
|
1218
|
+
if (eligibleInputs.length < 1 && verifyBrowserAutofillInput) {
|
|
1219
|
+
throw Error('No <input> with "webauthn" as the only or last value in its `autocomplete` attribute was detected');
|
|
1220
|
+
}
|
|
1221
|
+
getOptions.mediation = "conditional";
|
|
1222
|
+
publicKey.allowCredentials = [];
|
|
1223
|
+
}
|
|
1224
|
+
getOptions.publicKey = publicKey;
|
|
1225
|
+
getOptions.signal = WebAuthnAbortService.createNewAbortSignal();
|
|
1226
|
+
let credential;
|
|
1227
|
+
try {
|
|
1228
|
+
credential = await navigator.credentials.get(getOptions);
|
|
1229
|
+
} catch (err) {
|
|
1230
|
+
throw identifyAuthenticationError({ error: err, options: getOptions });
|
|
1231
|
+
}
|
|
1232
|
+
if (!credential) {
|
|
1233
|
+
throw new Error("Authentication was not completed");
|
|
1234
|
+
}
|
|
1235
|
+
const { id, rawId, response, type } = credential;
|
|
1236
|
+
let userHandle = void 0;
|
|
1237
|
+
if (response.userHandle) {
|
|
1238
|
+
userHandle = bufferToBase64URLString(response.userHandle);
|
|
1239
|
+
}
|
|
1240
|
+
return {
|
|
1241
|
+
id,
|
|
1242
|
+
rawId: bufferToBase64URLString(rawId),
|
|
1243
|
+
response: {
|
|
1244
|
+
authenticatorData: bufferToBase64URLString(response.authenticatorData),
|
|
1245
|
+
clientDataJSON: bufferToBase64URLString(response.clientDataJSON),
|
|
1246
|
+
signature: bufferToBase64URLString(response.signature),
|
|
1247
|
+
userHandle
|
|
1248
|
+
},
|
|
1249
|
+
type,
|
|
1250
|
+
clientExtensionResults: credential.getClientExtensionResults(),
|
|
1251
|
+
authenticatorAttachment: toAuthenticatorAttachment(credential.authenticatorAttachment)
|
|
1252
|
+
};
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
// src/runtime-imports.ts
|
|
1256
|
+
var importSolanaWeb3 = () => new Function('return import("@solana/web3.js")')();
|
|
1257
|
+
|
|
1258
|
+
// src/components/tabs/wallet-connect.tsx
|
|
1259
|
+
import { Fragment, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1260
|
+
var MOBILE_WALLETS = [
|
|
1261
|
+
{
|
|
1262
|
+
name: "Phantom",
|
|
1263
|
+
icon: "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTA4IiBoZWlnaHQ9IjEwOCIgdmlld0JveD0iMCAwIDEwOCAxMDgiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxMDgiIGhlaWdodD0iMTA4IiByeD0iMjYiIGZpbGw9IiNBQjlGRjIiLz4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik00Ni41MjY3IDY5LjkyMjlDNDIuMDA1NCA3Ni44NTA5IDM0LjQyOTIgODUuNjE4MiAyNC4zNDggODUuNjE4MkMxOS41ODI0IDg1LjYxODIgMTUgODMuNjU2MyAxNSA3NS4xMzQyQzE1IDUzLjQzMDUgNDQuNjMyNiAxOS44MzI3IDcyLjEyNjggMTkuODMyN0M4Ny43NjggMTkuODMyNyA5NCAzMC42ODQ2IDk0IDQzLjAwNzlDOTQgNTguODI1OCA4My43MzU1IDc2LjkxMjIgNzMuNTMyMSA3Ni45MTIyQzcwLjI5MzkgNzYuOTEyMiA2OC43MDUzIDc1LjEzNDIgNjguNzA1MyA3Mi4zMTRDNjguNzA1MyA3MS41NzgzIDY4LjgyNzUgNzAuNzgxMiA2OS4wNzE5IDY5LjkyMjlDNjUuNTg5MyA3NS44Njk5IDU4Ljg2ODUgODEuMzg3OCA1Mi41NzU0IDgxLjM4NzhDNDcuOTkzIDgxLjM4NzggNDUuNjcxMyA3OC41MDYzIDQ1LjY3MTMgNzQuNDU5OEM0NS42NzEzIDcyLjk4ODQgNDUuOTc2OCA3MS40NTU2IDQ2LjUyNjcgNjkuOTIyOVpNODMuNjc2MSA0Mi41Nzk0QzgzLjY3NjEgNDYuMTcwNCA4MS41NTc1IDQ3Ljk2NTggNzkuMTg3NSA0Ny45NjU4Qzc2Ljc4MTYgNDcuOTY1OCA3NC42OTg5IDQ2LjE3MDQgNzQuNjk4OSA0Mi41Nzk0Qzc0LjY5ODkgMzguOTg4NSA3Ni43ODE2IDM3LjE5MzEgNzkuMTg3NSAzNy4xOTMxQzgxLjU1NzUgMzcuMTkzMSA4My42NzYxIDM4Ljk4ODUgODMuNjc2MSA0Mi41Nzk0Wk03MC4yMTAzIDQyLjU3OTVDNzAuMjEwMyA0Ni4xNzA0IDY4LjA5MTYgNDcuOTY1OCA2NS43MjE2IDQ3Ljk2NThDNjMuMzE1NyA0Ny45NjU4IDYxLjIzMyA0Ni4xNzA0IDYxLjIzMyA0Mi41Nzk1QzYxLjIzMyAzOC45ODg1IDYzLjMxNTcgMzcuMTkzMSA2NS43MjE2IDM3LjE5MzFDNjguMDkxNiAzNy4xOTMxIDcwLjIxMDMgMzguOTg4NSA3MC4yMTAzIDQyLjU3OTVaIiBmaWxsPSIjRkZGREY4Ii8+Cjwvc3ZnPgo=",
|
|
1264
|
+
buildUrl: (url) => `https://phantom.app/ul/browse/${encodeURIComponent(url)}?ref=${encodeURIComponent(url)}`
|
|
1265
|
+
},
|
|
1266
|
+
{
|
|
1267
|
+
name: "Solflare",
|
|
1268
|
+
icon: "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIGlkPSJTIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MCA1MCI+PGRlZnM+PHN0eWxlPi5jbHMtMXtmaWxsOiMwMjA1MGE7c3Ryb2tlOiNmZmVmNDY7c3Ryb2tlLW1pdGVybGltaXQ6MTA7c3Ryb2tlLXdpZHRoOi41cHg7fS5jbHMtMntmaWxsOiNmZmVmNDY7fTwvc3R5bGU+PC9kZWZzPjxyZWN0IGNsYXNzPSJjbHMtMiIgeD0iMCIgd2lkdGg9IjUwIiBoZWlnaHQ9IjUwIiByeD0iMTIiIHJ5PSIxMiIvPjxwYXRoIGNsYXNzPSJjbHMtMSIgZD0iTTI0LjIzLDI2LjQybDIuNDYtMi4zOCw0LjU5LDEuNWMzLjAxLDEsNC41MSwyLjg0LDQuNTEsNS40MywwLDEuOTYtLjc1LDMuMjYtMi4yNSw0LjkzbC0uNDYuNS4xNy0xLjE3Yy42Ny00LjI2LS41OC02LjA5LTQuNzItNy40M2wtNC4zLTEuMzhoMFpNMTguMDUsMTEuODVsMTIuNTIsNC4xNy0yLjcxLDIuNTktNi41MS0yLjE3Yy0yLjI1LS43NS0zLjAxLTEuOTYtMy4zLTQuNTF2LS4wOGgwWk0xNy4zLDMzLjA2bDIuODQtMi43MSw1LjM0LDEuNzVjMi44LjkyLDMuNzYsMi4xMywzLjQ2LDUuMThsLTExLjY1LTQuMjJoMFpNMTMuNzEsMjAuOTVjMC0uNzkuNDItMS41NCwxLjEzLTIuMTcuNzUsMS4wOSwyLjA1LDIuMDUsNC4wOSwyLjcxbDQuNDIsMS40Ni0yLjQ2LDIuMzgtNC4zNC0xLjQyYy0yLS42Ny0yLjg0LTEuNjctMi44NC0yLjk2TTI2LjgyLDQyLjg3YzkuMTgtNi4wOSwxNC4xMS0xMC4yMywxNC4xMS0xNS4zMiwwLTMuMzgtMi01LjI2LTYuNDMtNi43MmwtMy4zNC0xLjEzLDkuMTQtOC43Ny0xLjg0LTEuOTYtMi43MSwyLjM4LTEyLjgxLTQuMjJjLTMuOTcsMS4yOS04Ljk3LDUuMDktOC45Nyw4Ljg5LDAsLjQyLjA0LjgzLjE3LDEuMjktMy4zLDEuODgtNC42MywzLjYzLTQuNjMsNS44LDAsMi4wNSwxLjA5LDQuMDksNC41NSw1LjIybDIuNzUuOTItOS41Miw5LjE0LDEuODQsMS45NiwyLjk2LTIuNzEsMTQuNzMsNS4yMmgwWiIvPjwvc3ZnPg==",
|
|
1269
|
+
buildUrl: (url) => `https://solflare.com/ul/v1/browse/${encodeURIComponent(url)}?ref=${encodeURIComponent(url)}`
|
|
1270
|
+
},
|
|
1271
|
+
{
|
|
1272
|
+
name: "Backpack",
|
|
1273
|
+
icon: "data:image/webp;base64,UklGRlgCAABXRUJQVlA4WAoAAAAQAAAAOwAAOwAAQUxQSC8AAAABL6CmbQOGP75e/cti0oiIOI0KBto2+a8ACZjAAQ7wL2gCSCL6PwER2t+KmZpFDwBWUDggAgIAAPALAJ0BKjwAPAA+SSCMRKKiIRYKrTQoBISyAGp7nL12mKuwHiZ/pX71XoA8m7rXfQA8q39lfhJ/cL0ZimIeNSGxOaSWtqEDDdJ++ARG5/LVm6OquItoAu4rpz8VksUr1NEDJ0CeZucwAP7+VdYX7J5e8V3cJZ9QoY9vj8KsNGOvZSm+khDZOvwCeiDFjGweq/8KIUmVQk3T/qW3ONyMcepN2uwyEXpZZiidST0x705r7ZnNuWX42r8oi+FEshvXGWm6DabjfrMbiaxJ15irl2Gj3nKWbZ2v3k/jd172plSBejBs7dNt90Re/m+nmv5kNjM/SPfa4EpzvOdu/8BR+hTnR4qe4eWhTsh/oecEfN31OExdi/6AYrsth4nlx/qGEP70b1/TNKmgaB/18+c9ntTfhku9wYpy17/wN3r+B/1geZPzth+AtNsMDXE99PaPLWdFP9MOIMD6ME+MpZu2H7WSfzY7MkWs/N/NNzAZ0P/9vMvxgcdzFHvs/doeUU9WyVv+Ll8QDJbn9NZl86ZZNYtfP4Ol1hiJHWosWyj/66vhMUeFIcDheXIOSRTzvKwj5ffEl7fn/QwflNU556SEjM8MriwSFJ/nrFBrr8O1/pqdrYGqPijG6tjsBVBHp+EDAbtNLL375I2jnG9i3Xb1ZphsFYNjdRaknYdFf4g5iJBoAAA=",
|
|
1274
|
+
buildUrl: (url) => `https://backpack.app/ul/browse/${encodeURIComponent(url)}`
|
|
1275
|
+
}
|
|
1276
|
+
];
|
|
1277
|
+
function isMobile() {
|
|
1278
|
+
if (typeof navigator === "undefined") return false;
|
|
1279
|
+
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
|
1280
|
+
}
|
|
1281
|
+
function WalletConnectForm() {
|
|
1282
|
+
const { walletAdapter, setModalStep, config } = useForgeConnect();
|
|
1283
|
+
const methods = resolveLoginMethods(config);
|
|
1284
|
+
const showBack = methods.length > 1;
|
|
1285
|
+
if (!walletAdapter && isMobile()) {
|
|
1286
|
+
return /* @__PURE__ */ jsx4(MobileWalletFlow, {});
|
|
1287
|
+
}
|
|
1288
|
+
if (walletAdapter) {
|
|
1289
|
+
return /* @__PURE__ */ jsx4(WalletAdapterFlow, {});
|
|
1290
|
+
}
|
|
1291
|
+
return /* @__PURE__ */ jsxs4("div", { className: "fc-tab", children: [
|
|
1292
|
+
/* @__PURE__ */ jsx4("h3", { className: "fc-tab-title", children: "Connect wallet" }),
|
|
1293
|
+
/* @__PURE__ */ jsxs4("p", { className: "fc-text", children: [
|
|
1294
|
+
"No wallet adapter detected. Pass ",
|
|
1295
|
+
/* @__PURE__ */ jsx4("code", { children: "walletAdapter" }),
|
|
1296
|
+
" to",
|
|
1297
|
+
" ",
|
|
1298
|
+
/* @__PURE__ */ jsx4("code", { children: "<ForgeConnectProvider>" }),
|
|
1299
|
+
" to enable wallet login."
|
|
1300
|
+
] }),
|
|
1301
|
+
showBack && /* @__PURE__ */ jsx4("p", { className: "fc-switch", children: /* @__PURE__ */ jsx4("button", { type: "button", className: "fc-link", onClick: () => setModalStep("method-select"), children: "Back" }) })
|
|
1302
|
+
] });
|
|
1303
|
+
}
|
|
1304
|
+
function MobileWalletFlow() {
|
|
1305
|
+
const { setModalStep, config } = useForgeConnect();
|
|
1306
|
+
const methods = resolveLoginMethods(config);
|
|
1307
|
+
const showBack = methods.length > 1;
|
|
1308
|
+
const walletConfig = config.walletConfig;
|
|
1309
|
+
const preferred = walletConfig?.preferredWallets ?? [];
|
|
1310
|
+
const onlyPreferred = walletConfig?.onlyPreferred ?? false;
|
|
1311
|
+
const walletsToShow = useMemo(() => {
|
|
1312
|
+
if (preferred.length > 0) {
|
|
1313
|
+
const prefList = preferred.map((name) => MOBILE_WALLETS.find((mw) => mw.name === name)).filter(Boolean);
|
|
1314
|
+
if (onlyPreferred) return prefList;
|
|
1315
|
+
const prefNames = new Set(preferred);
|
|
1316
|
+
const others = MOBILE_WALLETS.filter((mw) => !prefNames.has(mw.name));
|
|
1317
|
+
return [...prefList, ...others];
|
|
1318
|
+
}
|
|
1319
|
+
return MOBILE_WALLETS;
|
|
1320
|
+
}, [preferred, onlyPreferred]);
|
|
1321
|
+
const handleOpen = (mw) => {
|
|
1322
|
+
const pageUrl = window.location.href;
|
|
1323
|
+
window.location.href = mw.buildUrl(pageUrl);
|
|
1324
|
+
};
|
|
1325
|
+
return /* @__PURE__ */ jsxs4("div", { className: "fc-tab", children: [
|
|
1326
|
+
/* @__PURE__ */ jsx4("div", { className: "fc-wallet-list", children: walletsToShow.map((mw) => /* @__PURE__ */ jsxs4(
|
|
1327
|
+
"button",
|
|
1328
|
+
{
|
|
1329
|
+
type: "button",
|
|
1330
|
+
className: "fc-btn fc-btn-wallet",
|
|
1331
|
+
onClick: () => handleOpen(mw),
|
|
1332
|
+
children: [
|
|
1333
|
+
/* @__PURE__ */ jsx4("span", { style: { position: "relative", display: "inline-flex" }, children: /* @__PURE__ */ jsx4("img", { src: mw.icon, alt: "", className: "fc-wallet-icon" }) }),
|
|
1334
|
+
/* @__PURE__ */ jsx4("span", { className: "fc-wallet-name", children: mw.name }),
|
|
1335
|
+
/* @__PURE__ */ jsx4("span", { className: "fc-badge-preferred", children: "Open app" })
|
|
1336
|
+
]
|
|
1337
|
+
},
|
|
1338
|
+
mw.name
|
|
1339
|
+
)) }),
|
|
1340
|
+
/* @__PURE__ */ jsx4("p", { className: "fc-text", style: { textAlign: "center", fontSize: 12, opacity: 0.6 }, children: "You'll be redirected to the wallet app to sign in." }),
|
|
1341
|
+
showBack && /* @__PURE__ */ jsx4("p", { className: "fc-switch", children: /* @__PURE__ */ jsx4("button", { type: "button", className: "fc-link", onClick: () => setModalStep("method-select"), children: "Back" }) })
|
|
1342
|
+
] });
|
|
1343
|
+
}
|
|
1344
|
+
function WalletAdapterFlow() {
|
|
1345
|
+
const { walletAdapter, loginWithWallet, setModalStep, config } = useForgeConnect();
|
|
1346
|
+
const wallet = walletAdapter;
|
|
1347
|
+
const walletConfig = config.walletConfig;
|
|
1348
|
+
const methods = resolveLoginMethods(config);
|
|
1349
|
+
const showBack = methods.length > 1;
|
|
1350
|
+
const [error, setError] = useState3("");
|
|
1351
|
+
const [loading, setLoading] = useState3(false);
|
|
1352
|
+
const [showOther, setShowOther] = useState3(false);
|
|
1353
|
+
const [coldWallet, setColdWallet] = useState3(false);
|
|
1354
|
+
const mobile = useMemo(() => isMobile(), []);
|
|
1355
|
+
const coldWalletRef = useRef3(coldWallet);
|
|
1356
|
+
coldWalletRef.current = coldWallet;
|
|
1357
|
+
const buildSignTxFnForAdapter = useCallback((adapter) => {
|
|
1358
|
+
if (!adapter.signTransaction) return void 0;
|
|
1359
|
+
return async (txBase64) => {
|
|
1360
|
+
const { Transaction } = await importSolanaWeb3();
|
|
1361
|
+
const bytes = Uint8Array.from(atob(txBase64), (c) => c.charCodeAt(0));
|
|
1362
|
+
const tx = Transaction.from(bytes);
|
|
1363
|
+
const signedTx = await adapter.signTransaction(tx);
|
|
1364
|
+
return btoa(String.fromCharCode(...new Uint8Array(signedTx.serialize())));
|
|
1365
|
+
};
|
|
1366
|
+
}, []);
|
|
1367
|
+
const handleConnect = async (w) => {
|
|
1368
|
+
if (w.readyState !== "Installed") {
|
|
1369
|
+
if (mobile) {
|
|
1370
|
+
const mw = MOBILE_WALLETS.find((m) => m.name === w.adapter.name);
|
|
1371
|
+
if (mw) {
|
|
1372
|
+
window.location.href = mw.buildUrl(window.location.href);
|
|
1373
|
+
return;
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
const url = w.adapter.url;
|
|
1377
|
+
if (url) window.open(url, "_blank");
|
|
1378
|
+
return;
|
|
1379
|
+
}
|
|
1380
|
+
setError("");
|
|
1381
|
+
setLoading(true);
|
|
1382
|
+
try {
|
|
1383
|
+
if (!w.adapter.connected) {
|
|
1384
|
+
await w.adapter.connect();
|
|
1385
|
+
}
|
|
1386
|
+
const pk = w.adapter.publicKey;
|
|
1387
|
+
if (!pk) throw new Error("Wallet did not provide a public key.");
|
|
1388
|
+
const address = pk.toBase58();
|
|
1389
|
+
const useCold = coldWalletRef.current;
|
|
1390
|
+
const adapterSignMessage = w.adapter.signMessage ? (msg) => w.adapter.signMessage(msg) : void 0;
|
|
1391
|
+
await loginWithWallet(
|
|
1392
|
+
address,
|
|
1393
|
+
useCold ? void 0 : adapterSignMessage,
|
|
1394
|
+
"solana",
|
|
1395
|
+
useCold ? buildSignTxFnForAdapter(w.adapter) : void 0
|
|
1396
|
+
);
|
|
1397
|
+
} catch (err) {
|
|
1398
|
+
setError(err instanceof Error ? err.message : "Could not verify your wallet. Please try again.");
|
|
1399
|
+
setLoading(false);
|
|
1400
|
+
}
|
|
1401
|
+
};
|
|
1402
|
+
const handleMobileOpen = (mw) => {
|
|
1403
|
+
window.location.href = mw.buildUrl(window.location.href);
|
|
1404
|
+
};
|
|
1405
|
+
const preferred = walletConfig?.preferredWallets ?? [];
|
|
1406
|
+
const onlyPreferred = walletConfig?.onlyPreferred ?? false;
|
|
1407
|
+
const preferredSet = new Set(preferred);
|
|
1408
|
+
const connectedWalletName = wallet.publicKey ? wallet.wallets.find((w) => w.adapter.connected)?.adapter.name ?? null : null;
|
|
1409
|
+
const { preferredWallets, otherWallets } = useMemo(() => {
|
|
1410
|
+
const all = wallet.wallets;
|
|
1411
|
+
const prefList = preferred.map((name) => all.find((w) => w.adapter.name === name)).filter(Boolean);
|
|
1412
|
+
if (onlyPreferred && preferred.length > 0) {
|
|
1413
|
+
return { preferredWallets: prefList, otherWallets: [] };
|
|
1414
|
+
}
|
|
1415
|
+
const others = all.filter(
|
|
1416
|
+
(w) => !preferredSet.has(w.adapter.name) && w.readyState === "Installed"
|
|
1417
|
+
);
|
|
1418
|
+
return { preferredWallets: prefList, otherWallets: others };
|
|
1419
|
+
}, [wallet.wallets, walletConfig]);
|
|
1420
|
+
const mobileExtraWallets = useMemo(() => {
|
|
1421
|
+
if (!mobile) return [];
|
|
1422
|
+
const adapterNames = new Set(wallet.wallets.map((w) => w.adapter.name));
|
|
1423
|
+
return MOBILE_WALLETS.filter((mw) => !adapterNames.has(mw.name));
|
|
1424
|
+
}, [mobile, wallet.wallets]);
|
|
1425
|
+
return /* @__PURE__ */ jsxs4("div", { className: "fc-tab", children: [
|
|
1426
|
+
loading ? /* @__PURE__ */ jsxs4("div", { style: { textAlign: "center", padding: "24px 0" }, children: [
|
|
1427
|
+
/* @__PURE__ */ jsx4("p", { className: "fc-tab-title", children: "Connecting..." }),
|
|
1428
|
+
/* @__PURE__ */ jsx4("p", { className: "fc-text", children: coldWallet ? "Confirm the transaction on your device" : "Approve the connection, then sign the verification request in your wallet" }),
|
|
1429
|
+
error && /* @__PURE__ */ jsxs4(Fragment, { children: [
|
|
1430
|
+
/* @__PURE__ */ jsx4("p", { className: "fc-error", children: error }),
|
|
1431
|
+
/* @__PURE__ */ jsx4(
|
|
1432
|
+
"button",
|
|
1433
|
+
{
|
|
1434
|
+
type: "button",
|
|
1435
|
+
className: "fc-btn fc-btn-secondary",
|
|
1436
|
+
onClick: () => {
|
|
1437
|
+
setLoading(false);
|
|
1438
|
+
setError("");
|
|
1439
|
+
},
|
|
1440
|
+
style: { marginTop: 8 },
|
|
1441
|
+
children: "Try again"
|
|
1442
|
+
}
|
|
1443
|
+
)
|
|
1444
|
+
] })
|
|
1445
|
+
] }) : /* @__PURE__ */ jsxs4(Fragment, { children: [
|
|
1446
|
+
/* @__PURE__ */ jsxs4("div", { className: "fc-wallet-list", children: [
|
|
1447
|
+
preferredWallets.map((w) => {
|
|
1448
|
+
const installed = w.readyState === "Installed";
|
|
1449
|
+
const isConnected = w.adapter.name === connectedWalletName;
|
|
1450
|
+
return /* @__PURE__ */ jsxs4(
|
|
1451
|
+
"button",
|
|
1452
|
+
{
|
|
1453
|
+
type: "button",
|
|
1454
|
+
className: "fc-btn fc-btn-wallet",
|
|
1455
|
+
onClick: () => handleConnect(w),
|
|
1456
|
+
children: [
|
|
1457
|
+
/* @__PURE__ */ jsx4("span", { className: installed ? "fc-installed-dot" : "", style: { position: "relative", display: "inline-flex" }, children: /* @__PURE__ */ jsx4("img", { src: w.adapter.icon, alt: "", className: "fc-wallet-icon" }) }),
|
|
1458
|
+
/* @__PURE__ */ jsx4("span", { className: "fc-wallet-name", children: w.adapter.name }),
|
|
1459
|
+
isConnected ? /* @__PURE__ */ jsx4("span", { className: "fc-badge-preferred", children: "Last used" }) : /* @__PURE__ */ jsx4("span", { className: "fc-badge-preferred", children: "Preferred" }),
|
|
1460
|
+
!installed && mobile && MOBILE_WALLETS.some((mw) => mw.name === w.adapter.name) ? /* @__PURE__ */ jsx4("span", { className: "fc-badge-install", children: "Open app" }) : !installed && /* @__PURE__ */ jsx4("span", { className: "fc-badge-install", children: "Install" })
|
|
1461
|
+
]
|
|
1462
|
+
},
|
|
1463
|
+
w.adapter.name
|
|
1464
|
+
);
|
|
1465
|
+
}),
|
|
1466
|
+
mobileExtraWallets.map((mw) => /* @__PURE__ */ jsxs4(
|
|
1467
|
+
"button",
|
|
1468
|
+
{
|
|
1469
|
+
type: "button",
|
|
1470
|
+
className: "fc-btn fc-btn-wallet",
|
|
1471
|
+
onClick: () => handleMobileOpen(mw),
|
|
1472
|
+
children: [
|
|
1473
|
+
/* @__PURE__ */ jsx4("span", { style: { position: "relative", display: "inline-flex" }, children: /* @__PURE__ */ jsx4("img", { src: mw.icon, alt: "", className: "fc-wallet-icon" }) }),
|
|
1474
|
+
/* @__PURE__ */ jsx4("span", { className: "fc-wallet-name", children: mw.name }),
|
|
1475
|
+
/* @__PURE__ */ jsx4("span", { className: "fc-badge-install", children: "Open app" })
|
|
1476
|
+
]
|
|
1477
|
+
},
|
|
1478
|
+
mw.name
|
|
1479
|
+
)),
|
|
1480
|
+
otherWallets.length > 0 && !showOther && /* @__PURE__ */ jsxs4(
|
|
1481
|
+
"button",
|
|
1482
|
+
{
|
|
1483
|
+
type: "button",
|
|
1484
|
+
className: "fc-btn fc-btn-wallet",
|
|
1485
|
+
onClick: () => setShowOther(true),
|
|
1486
|
+
children: [
|
|
1487
|
+
/* @__PURE__ */ jsx4("span", { className: "fc-other-wallets-icon", children: /* @__PURE__ */ jsxs4("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", children: [
|
|
1488
|
+
/* @__PURE__ */ jsx4("rect", { x: "2", y: "3", width: "16", height: "14", rx: "2", stroke: "currentColor", strokeWidth: "1.5" }),
|
|
1489
|
+
/* @__PURE__ */ jsx4("path", { d: "M2 7h16", stroke: "currentColor", strokeWidth: "1.5" }),
|
|
1490
|
+
/* @__PURE__ */ jsx4("rect", { x: "11", y: "10", width: "7", height: "4", rx: "1", stroke: "currentColor", strokeWidth: "1.5" })
|
|
1491
|
+
] }) }),
|
|
1492
|
+
/* @__PURE__ */ jsx4("span", { className: "fc-wallet-name", children: "Other wallets" })
|
|
1493
|
+
]
|
|
1494
|
+
}
|
|
1495
|
+
),
|
|
1496
|
+
showOther && otherWallets.map((w) => {
|
|
1497
|
+
const isConnected = w.adapter.name === connectedWalletName;
|
|
1498
|
+
return /* @__PURE__ */ jsxs4(
|
|
1499
|
+
"button",
|
|
1500
|
+
{
|
|
1501
|
+
type: "button",
|
|
1502
|
+
className: "fc-btn fc-btn-wallet",
|
|
1503
|
+
onClick: () => handleConnect(w),
|
|
1504
|
+
children: [
|
|
1505
|
+
/* @__PURE__ */ jsx4("span", { className: "fc-installed-dot", style: { position: "relative", display: "inline-flex" }, children: /* @__PURE__ */ jsx4("img", { src: w.adapter.icon, alt: "", className: "fc-wallet-icon" }) }),
|
|
1506
|
+
/* @__PURE__ */ jsx4("span", { className: "fc-wallet-name", children: w.adapter.name }),
|
|
1507
|
+
isConnected && /* @__PURE__ */ jsx4("span", { className: "fc-badge-preferred", children: "Last used" })
|
|
1508
|
+
]
|
|
1509
|
+
},
|
|
1510
|
+
w.adapter.name
|
|
1511
|
+
);
|
|
1512
|
+
})
|
|
1513
|
+
] }),
|
|
1514
|
+
preferredWallets.length === 0 && otherWallets.length === 0 && mobileExtraWallets.length === 0 && /* @__PURE__ */ jsx4("p", { className: "fc-text", children: "No wallet found. Please install a Solana wallet (like Phantom) to continue." }),
|
|
1515
|
+
/* @__PURE__ */ jsxs4("label", { className: "fc-cold-wallet-toggle", children: [
|
|
1516
|
+
/* @__PURE__ */ jsx4(
|
|
1517
|
+
"input",
|
|
1518
|
+
{
|
|
1519
|
+
type: "checkbox",
|
|
1520
|
+
checked: coldWallet,
|
|
1521
|
+
onChange: (e) => setColdWallet(e.target.checked)
|
|
1522
|
+
}
|
|
1523
|
+
),
|
|
1524
|
+
/* @__PURE__ */ jsx4("span", { className: "fc-toggle-track" }),
|
|
1525
|
+
/* @__PURE__ */ jsxs4("span", { className: "fc-cold-wallet-label", children: [
|
|
1526
|
+
/* @__PURE__ */ jsx4("span", { children: "Hardware wallet" }),
|
|
1527
|
+
/* @__PURE__ */ jsx4("span", { children: "Ledger, Trezor, Keystone..." })
|
|
1528
|
+
] })
|
|
1529
|
+
] }),
|
|
1530
|
+
error && /* @__PURE__ */ jsx4("p", { className: "fc-error", children: error })
|
|
1531
|
+
] }),
|
|
1532
|
+
showBack && !loading && /* @__PURE__ */ jsx4("p", { className: "fc-switch", children: /* @__PURE__ */ jsx4("button", { type: "button", className: "fc-link", onClick: () => setModalStep("method-select"), children: "Back" }) })
|
|
1533
|
+
] });
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
// src/components/tabs/forgot-password.tsx
|
|
1537
|
+
import { useState as useState4 } from "react";
|
|
1538
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1539
|
+
function ForgotPasswordForm() {
|
|
1540
|
+
const { forgotPassword, resetPassword, setModalStep } = useForgeConnect();
|
|
1541
|
+
const [step, setStep] = useState4("email");
|
|
1542
|
+
const [email, setEmail] = useState4("");
|
|
1543
|
+
const [token, setToken] = useState4("");
|
|
1544
|
+
const [password, setPassword] = useState4("");
|
|
1545
|
+
const [error, setError] = useState4("");
|
|
1546
|
+
const [loading, setLoading] = useState4(false);
|
|
1547
|
+
const handleSendCode = async (e) => {
|
|
1548
|
+
e.preventDefault();
|
|
1549
|
+
setError("");
|
|
1550
|
+
setLoading(true);
|
|
1551
|
+
try {
|
|
1552
|
+
await forgotPassword(email);
|
|
1553
|
+
setStep("reset");
|
|
1554
|
+
} catch (err) {
|
|
1555
|
+
setError(err instanceof Error ? err.message : "Could not send the reset code. Please try again.");
|
|
1556
|
+
} finally {
|
|
1557
|
+
setLoading(false);
|
|
1558
|
+
}
|
|
1559
|
+
};
|
|
1560
|
+
const handleReset = async (e) => {
|
|
1561
|
+
e.preventDefault();
|
|
1562
|
+
setError("");
|
|
1563
|
+
setLoading(true);
|
|
1564
|
+
try {
|
|
1565
|
+
await resetPassword(token.trim(), password);
|
|
1566
|
+
setStep("done");
|
|
1567
|
+
} catch (err) {
|
|
1568
|
+
setError(err instanceof Error ? err.message : "Could not reset your password. Please try again.");
|
|
1569
|
+
} finally {
|
|
1570
|
+
setLoading(false);
|
|
1571
|
+
}
|
|
1572
|
+
};
|
|
1573
|
+
if (step === "done") {
|
|
1574
|
+
return /* @__PURE__ */ jsxs5("div", { className: "fc-tab", children: [
|
|
1575
|
+
/* @__PURE__ */ jsx5("h3", { className: "fc-tab-title", children: "Password updated" }),
|
|
1576
|
+
/* @__PURE__ */ jsx5("p", { className: "fc-text", children: "Your password has been reset. You can now sign in." }),
|
|
1577
|
+
/* @__PURE__ */ jsx5("button", { type: "button", className: "fc-btn fc-btn-primary", onClick: () => setModalStep("email-login"), children: "Sign in" })
|
|
1578
|
+
] });
|
|
1579
|
+
}
|
|
1580
|
+
if (step === "reset") {
|
|
1581
|
+
return /* @__PURE__ */ jsxs5("div", { className: "fc-tab", children: [
|
|
1582
|
+
/* @__PURE__ */ jsx5("h3", { className: "fc-tab-title", children: "Set new password" }),
|
|
1583
|
+
/* @__PURE__ */ jsxs5("p", { className: "fc-text", children: [
|
|
1584
|
+
"We sent a reset code to ",
|
|
1585
|
+
/* @__PURE__ */ jsx5("strong", { children: email }),
|
|
1586
|
+
". Paste it below with your new password."
|
|
1587
|
+
] }),
|
|
1588
|
+
/* @__PURE__ */ jsxs5("form", { onSubmit: handleReset, className: "fc-form", children: [
|
|
1589
|
+
/* @__PURE__ */ jsxs5("label", { className: "fc-label", children: [
|
|
1590
|
+
"Reset code",
|
|
1591
|
+
/* @__PURE__ */ jsx5(
|
|
1592
|
+
"input",
|
|
1593
|
+
{
|
|
1594
|
+
type: "text",
|
|
1595
|
+
className: "fc-input",
|
|
1596
|
+
value: token,
|
|
1597
|
+
onChange: (e) => setToken(e.target.value),
|
|
1598
|
+
placeholder: "Paste the code from your email",
|
|
1599
|
+
required: true,
|
|
1600
|
+
autoComplete: "off"
|
|
1601
|
+
}
|
|
1602
|
+
)
|
|
1603
|
+
] }),
|
|
1604
|
+
/* @__PURE__ */ jsxs5("label", { className: "fc-label", children: [
|
|
1605
|
+
"New password",
|
|
1606
|
+
/* @__PURE__ */ jsx5(
|
|
1607
|
+
"input",
|
|
1608
|
+
{
|
|
1609
|
+
type: "password",
|
|
1610
|
+
className: "fc-input",
|
|
1611
|
+
value: password,
|
|
1612
|
+
onChange: (e) => setPassword(e.target.value),
|
|
1613
|
+
placeholder: "8+ characters",
|
|
1614
|
+
required: true,
|
|
1615
|
+
autoComplete: "new-password",
|
|
1616
|
+
minLength: 8
|
|
1617
|
+
}
|
|
1618
|
+
)
|
|
1619
|
+
] }),
|
|
1620
|
+
error && /* @__PURE__ */ jsx5("p", { className: "fc-error", children: error }),
|
|
1621
|
+
/* @__PURE__ */ jsx5("button", { type: "submit", className: "fc-btn fc-btn-primary", disabled: loading, children: loading ? "Resetting..." : "Reset password" })
|
|
1622
|
+
] }),
|
|
1623
|
+
/* @__PURE__ */ jsx5("p", { className: "fc-switch", children: /* @__PURE__ */ jsx5("button", { type: "button", className: "fc-link", onClick: () => setStep("email"), children: "Resend code" }) })
|
|
1624
|
+
] });
|
|
1625
|
+
}
|
|
1626
|
+
return /* @__PURE__ */ jsxs5("div", { className: "fc-tab", children: [
|
|
1627
|
+
/* @__PURE__ */ jsx5("h3", { className: "fc-tab-title", children: "Reset your password" }),
|
|
1628
|
+
/* @__PURE__ */ jsx5("p", { className: "fc-text", children: "Enter your email and we'll send you a reset code." }),
|
|
1629
|
+
/* @__PURE__ */ jsxs5("form", { onSubmit: handleSendCode, className: "fc-form", children: [
|
|
1630
|
+
/* @__PURE__ */ jsxs5("label", { className: "fc-label", children: [
|
|
1631
|
+
"Email",
|
|
1632
|
+
/* @__PURE__ */ jsx5(
|
|
1633
|
+
"input",
|
|
1634
|
+
{
|
|
1635
|
+
type: "email",
|
|
1636
|
+
className: "fc-input",
|
|
1637
|
+
value: email,
|
|
1638
|
+
onChange: (e) => setEmail(e.target.value),
|
|
1639
|
+
placeholder: "you@example.com",
|
|
1640
|
+
required: true,
|
|
1641
|
+
autoComplete: "email"
|
|
1642
|
+
}
|
|
1643
|
+
)
|
|
1644
|
+
] }),
|
|
1645
|
+
error && /* @__PURE__ */ jsx5("p", { className: "fc-error", children: error }),
|
|
1646
|
+
/* @__PURE__ */ jsx5("button", { type: "submit", className: "fc-btn fc-btn-primary", disabled: loading, children: loading ? "Sending..." : "Send reset code" })
|
|
1647
|
+
] }),
|
|
1648
|
+
/* @__PURE__ */ jsx5("p", { className: "fc-switch", children: /* @__PURE__ */ jsx5("button", { type: "button", className: "fc-link", onClick: () => setModalStep("email-login"), children: "Back to sign in" }) })
|
|
1649
|
+
] });
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
// src/components/tabs/verify-2fa.tsx
|
|
1653
|
+
import { useState as useState5, useRef as useRef4, useEffect as useEffect3, useCallback as useCallback2 } from "react";
|
|
1654
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1655
|
+
function Verify2FAForm() {
|
|
1656
|
+
const { verify2FA, verifyRecoveryCode, setModalStep } = useForgeConnect();
|
|
1657
|
+
const [code, setCode] = useState5("");
|
|
1658
|
+
const [loading, setLoading] = useState5(false);
|
|
1659
|
+
const [error, setError] = useState5("");
|
|
1660
|
+
const [useRecovery, setUseRecovery] = useState5(false);
|
|
1661
|
+
const inputRef = useRef4(null);
|
|
1662
|
+
const submittingRef = useRef4(false);
|
|
1663
|
+
useEffect3(() => {
|
|
1664
|
+
inputRef.current?.focus();
|
|
1665
|
+
}, [useRecovery]);
|
|
1666
|
+
const submitCode = useCallback2(async (codeValue, isRecovery) => {
|
|
1667
|
+
if (submittingRef.current || !codeValue.trim()) return;
|
|
1668
|
+
submittingRef.current = true;
|
|
1669
|
+
setLoading(true);
|
|
1670
|
+
setError("");
|
|
1671
|
+
try {
|
|
1672
|
+
if (isRecovery) {
|
|
1673
|
+
await verifyRecoveryCode(codeValue.trim());
|
|
1674
|
+
} else {
|
|
1675
|
+
await verify2FA(codeValue.trim());
|
|
1676
|
+
}
|
|
1677
|
+
} catch (err) {
|
|
1678
|
+
setError(err instanceof Error ? err.message : "Verification failed. Please try again.");
|
|
1679
|
+
} finally {
|
|
1680
|
+
setLoading(false);
|
|
1681
|
+
submittingRef.current = false;
|
|
1682
|
+
}
|
|
1683
|
+
}, [verify2FA, verifyRecoveryCode]);
|
|
1684
|
+
const handleSubmit = async (e) => {
|
|
1685
|
+
e?.preventDefault();
|
|
1686
|
+
await submitCode(code, useRecovery);
|
|
1687
|
+
};
|
|
1688
|
+
const handleCodeChange = (value) => {
|
|
1689
|
+
setCode(value);
|
|
1690
|
+
if (!useRecovery && value.length === 6 && /^\d{6}$/.test(value)) {
|
|
1691
|
+
submitCode(value, false);
|
|
1692
|
+
}
|
|
1693
|
+
};
|
|
1694
|
+
return /* @__PURE__ */ jsxs6("div", { className: "fc-tab", children: [
|
|
1695
|
+
/* @__PURE__ */ jsxs6("button", { type: "button", className: "fc-back", onClick: () => setModalStep("method-select"), children: [
|
|
1696
|
+
/* @__PURE__ */ jsx6("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: /* @__PURE__ */ jsx6("path", { d: "M10 12L6 8l4-4", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) }),
|
|
1697
|
+
"Back"
|
|
1698
|
+
] }),
|
|
1699
|
+
/* @__PURE__ */ jsx6("p", { className: "fc-account-section-desc", style: { marginTop: 8 }, children: useRecovery ? "Enter one of your recovery codes." : "Enter the 6-digit code from your authenticator app." }),
|
|
1700
|
+
error && /* @__PURE__ */ jsx6("p", { className: "fc-error", children: error }),
|
|
1701
|
+
/* @__PURE__ */ jsxs6("form", { onSubmit: handleSubmit, className: "fc-form", children: [
|
|
1702
|
+
/* @__PURE__ */ jsx6(
|
|
1703
|
+
"input",
|
|
1704
|
+
{
|
|
1705
|
+
ref: inputRef,
|
|
1706
|
+
className: "fc-input fc-input-code",
|
|
1707
|
+
type: "text",
|
|
1708
|
+
inputMode: useRecovery ? "text" : "numeric",
|
|
1709
|
+
autoComplete: "one-time-code",
|
|
1710
|
+
placeholder: useRecovery ? "Recovery code" : "000000",
|
|
1711
|
+
maxLength: useRecovery ? 20 : 6,
|
|
1712
|
+
value: code,
|
|
1713
|
+
onChange: (e) => handleCodeChange(e.target.value),
|
|
1714
|
+
style: useRecovery ? {} : { textAlign: "center", letterSpacing: "0.3em", fontSize: "1.25rem" }
|
|
1715
|
+
}
|
|
1716
|
+
),
|
|
1717
|
+
/* @__PURE__ */ jsx6("button", { type: "submit", className: "fc-btn fc-btn-primary", disabled: loading || !code.trim(), children: loading ? "Verifying..." : "Verify" })
|
|
1718
|
+
] }),
|
|
1719
|
+
/* @__PURE__ */ jsx6(
|
|
1720
|
+
"button",
|
|
1721
|
+
{
|
|
1722
|
+
type: "button",
|
|
1723
|
+
className: "fc-link",
|
|
1724
|
+
onClick: () => {
|
|
1725
|
+
setUseRecovery(!useRecovery);
|
|
1726
|
+
setCode("");
|
|
1727
|
+
setError("");
|
|
1728
|
+
},
|
|
1729
|
+
style: { marginTop: 12 },
|
|
1730
|
+
children: useRecovery ? "Use authenticator code instead" : "Use a recovery code"
|
|
1731
|
+
}
|
|
1732
|
+
)
|
|
1733
|
+
] });
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
// src/components/svg-icon.tsx
|
|
1737
|
+
import { useRef as useRef5, useEffect as useEffect4 } from "react";
|
|
1738
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
1739
|
+
function SvgIcon({ svg, className }) {
|
|
1740
|
+
const ref = useRef5(null);
|
|
1741
|
+
useEffect4(() => {
|
|
1742
|
+
if (!ref.current || !svg) return;
|
|
1743
|
+
try {
|
|
1744
|
+
const doc = new DOMParser().parseFromString(svg, "text/html");
|
|
1745
|
+
const svgEl = doc.body.querySelector("svg");
|
|
1746
|
+
if (!svgEl) {
|
|
1747
|
+
ref.current.textContent = "";
|
|
1748
|
+
return;
|
|
1749
|
+
}
|
|
1750
|
+
const dangerous = svgEl.querySelectorAll("script,iframe,object,embed,foreignObject");
|
|
1751
|
+
dangerous.forEach((el) => el.remove());
|
|
1752
|
+
const all = svgEl.querySelectorAll("*");
|
|
1753
|
+
all.forEach((el) => {
|
|
1754
|
+
for (const attr of Array.from(el.attributes)) {
|
|
1755
|
+
if (attr.name.startsWith("on")) {
|
|
1756
|
+
el.removeAttribute(attr.name);
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
});
|
|
1760
|
+
ref.current.textContent = "";
|
|
1761
|
+
ref.current.appendChild(svgEl);
|
|
1762
|
+
} catch {
|
|
1763
|
+
ref.current.textContent = "";
|
|
1764
|
+
}
|
|
1765
|
+
}, [svg]);
|
|
1766
|
+
return /* @__PURE__ */ jsx7("span", { ref, className });
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
// src/components/tabs/oauth-buttons.tsx
|
|
1770
|
+
import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1771
|
+
var PROVIDER_INFO = {
|
|
1772
|
+
google: {
|
|
1773
|
+
label: "Google",
|
|
1774
|
+
icon: '<svg viewBox="0 0 24 24" width="20" height="20"><path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 0 1-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1z" fill="#4285F4"/><path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill="#34A853"/><path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" fill="#FBBC05"/><path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill="#EA4335"/></svg>'
|
|
1775
|
+
},
|
|
1776
|
+
discord: {
|
|
1777
|
+
label: "Discord",
|
|
1778
|
+
icon: '<svg viewBox="0 0 24 24" width="20" height="20"><path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028c.462-.63.874-1.295 1.226-1.994a.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128c.126-.094.252-.192.372-.292a.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03z" fill="#5865F2"/></svg>'
|
|
1779
|
+
},
|
|
1780
|
+
twitter: {
|
|
1781
|
+
label: "Twitter",
|
|
1782
|
+
icon: '<svg viewBox="0 0 24 24" width="20" height="20"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" fill="currentColor"/></svg>'
|
|
1783
|
+
},
|
|
1784
|
+
apple: {
|
|
1785
|
+
label: "Apple",
|
|
1786
|
+
icon: '<svg viewBox="0 0 24 24" width="20" height="20"><path d="M17.05 20.28c-.98.95-2.05.88-3.08.4-1.09-.5-2.08-.52-3.23 0-1.44.64-2.2.45-3.06-.4C3.79 16.17 4.36 9.02 8.8 8.78c1.27.06 2.15.72 2.91.76.93-.19 1.82-.88 2.83-.8 1.21.1 2.12.58 2.72 1.49-2.46 1.48-1.88 4.73.52 5.64-.42 1.13-.98 2.24-1.73 3.41zM12.03 8.7c-.12-2.35 1.82-4.38 4.04-4.54.29 2.56-2.34 4.68-4.04 4.54z" fill="currentColor"/></svg>'
|
|
1787
|
+
},
|
|
1788
|
+
telegram: {
|
|
1789
|
+
label: "Telegram",
|
|
1790
|
+
icon: '<svg viewBox="0 0 24 24" width="20" height="20"><path d="M11.944 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0a12 12 0 0 0-.056 0zm4.962 7.224c.1-.002.321.023.465.14a.506.506 0 0 1 .171.325c.016.093.036.306.02.472-.18 1.898-.962 6.502-1.36 8.627-.168.9-.499 1.201-.82 1.23-.696.065-1.225-.46-1.9-.902-1.056-.693-1.653-1.124-2.678-1.8-1.185-.78-.417-1.21.258-1.91.177-.184 3.247-2.977 3.307-3.23.007-.032.014-.15-.056-.212s-.174-.041-.249-.024c-.106.024-1.793 1.14-5.061 3.345-.479.33-.913.49-1.302.48-.428-.008-1.252-.241-1.865-.44-.752-.245-1.349-.374-1.297-.789.027-.216.325-.437.893-.663 3.498-1.524 5.83-2.529 6.998-3.014 3.332-1.386 4.025-1.627 4.476-1.635z" fill="#2AABEE"/></svg>'
|
|
1791
|
+
}
|
|
1792
|
+
};
|
|
1793
|
+
function OAuthButton({ provider }) {
|
|
1794
|
+
const { loginWithOAuth } = useForgeConnect();
|
|
1795
|
+
const info = PROVIDER_INFO[provider];
|
|
1796
|
+
return /* @__PURE__ */ jsxs7(
|
|
1797
|
+
"button",
|
|
1798
|
+
{
|
|
1799
|
+
type: "button",
|
|
1800
|
+
className: "fc-btn fc-btn-oauth",
|
|
1801
|
+
onClick: () => loginWithOAuth(provider),
|
|
1802
|
+
children: [
|
|
1803
|
+
/* @__PURE__ */ jsx8(SvgIcon, { svg: info.icon, className: "fc-oauth-icon" }),
|
|
1804
|
+
/* @__PURE__ */ jsx8("span", { className: "fc-btn-name", children: info.label })
|
|
1805
|
+
]
|
|
1806
|
+
}
|
|
1807
|
+
);
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
// src/components/login-modal.tsx
|
|
1811
|
+
import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1812
|
+
function LoginModal() {
|
|
1813
|
+
const { modal, closeModal, config } = useForgeConnect();
|
|
1814
|
+
const renderStep = () => {
|
|
1815
|
+
switch (modal.step) {
|
|
1816
|
+
case "email-login":
|
|
1817
|
+
return /* @__PURE__ */ jsx9(EmailLoginForm, {});
|
|
1818
|
+
case "email-register":
|
|
1819
|
+
return /* @__PURE__ */ jsx9(EmailRegisterForm, {});
|
|
1820
|
+
case "email-otp":
|
|
1821
|
+
return /* @__PURE__ */ jsx9(EmailOtpForm, {});
|
|
1822
|
+
case "wallet-connect":
|
|
1823
|
+
return /* @__PURE__ */ jsx9(WalletConnectForm, {});
|
|
1824
|
+
case "forgot-password":
|
|
1825
|
+
return /* @__PURE__ */ jsx9(ForgotPasswordForm, {});
|
|
1826
|
+
case "verify-2fa":
|
|
1827
|
+
return /* @__PURE__ */ jsx9(Verify2FAForm, {});
|
|
1828
|
+
case "oauth":
|
|
1829
|
+
return /* @__PURE__ */ jsx9(OAuthLoadingView, {});
|
|
1830
|
+
case "success":
|
|
1831
|
+
return /* @__PURE__ */ jsx9(SuccessView, {});
|
|
1832
|
+
case "method-select":
|
|
1833
|
+
default:
|
|
1834
|
+
return /* @__PURE__ */ jsx9(MethodSelect, {});
|
|
1835
|
+
}
|
|
1836
|
+
};
|
|
1837
|
+
const renderLogo = () => {
|
|
1838
|
+
if (config.appearance?.logoNode) {
|
|
1839
|
+
return /* @__PURE__ */ jsx9("div", { className: "fc-logo", children: config.appearance.logoNode });
|
|
1840
|
+
}
|
|
1841
|
+
if (config.appearance?.logo) {
|
|
1842
|
+
return /* @__PURE__ */ jsx9("img", { src: config.appearance.logo, alt: "", className: "fc-logo" });
|
|
1843
|
+
}
|
|
1844
|
+
return null;
|
|
1845
|
+
};
|
|
1846
|
+
return /* @__PURE__ */ jsx9(ModalOverlay, { isOpen: modal.isOpen, onClose: closeModal, children: /* @__PURE__ */ jsx9(
|
|
1847
|
+
"div",
|
|
1848
|
+
{
|
|
1849
|
+
className: "fc-modal-content",
|
|
1850
|
+
style: {
|
|
1851
|
+
"--fc-accent": config.appearance?.accentColor ?? "#8b5cf6"
|
|
1852
|
+
},
|
|
1853
|
+
"data-theme": config.appearance?.theme ?? "light",
|
|
1854
|
+
children: modal.step === "success" || modal.step === "oauth" ? renderStep() : /* @__PURE__ */ jsxs8(Fragment2, { children: [
|
|
1855
|
+
renderLogo(),
|
|
1856
|
+
/* @__PURE__ */ jsx9("h2", { className: "fc-modal-title", children: config.appearance?.title ?? "Log in or sign up" }),
|
|
1857
|
+
renderStep(),
|
|
1858
|
+
(config.appearance?.termsUrl || config.appearance?.privacyUrl) && /* @__PURE__ */ jsxs8("p", { className: "fc-legal", children: [
|
|
1859
|
+
"By continuing, you agree to our",
|
|
1860
|
+
" ",
|
|
1861
|
+
config.appearance.termsUrl && /* @__PURE__ */ jsx9("a", { href: config.appearance.termsUrl, target: "_blank", rel: "noopener noreferrer", className: "fc-legal-link", children: "Terms" }),
|
|
1862
|
+
config.appearance.termsUrl && config.appearance.privacyUrl && " and ",
|
|
1863
|
+
config.appearance.privacyUrl && /* @__PURE__ */ jsx9("a", { href: config.appearance.privacyUrl, target: "_blank", rel: "noopener noreferrer", className: "fc-legal-link", children: "Privacy Policy" })
|
|
1864
|
+
] })
|
|
1865
|
+
] })
|
|
1866
|
+
}
|
|
1867
|
+
) });
|
|
1868
|
+
}
|
|
1869
|
+
var EmailIcon = () => /* @__PURE__ */ jsx9("div", { className: "fc-method-icon-wrap", children: /* @__PURE__ */ jsxs8("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", className: "fc-method-icon", children: [
|
|
1870
|
+
/* @__PURE__ */ jsx9("rect", { x: "2", y: "4", width: "16", height: "12", rx: "2", stroke: "currentColor", strokeWidth: "1.5" }),
|
|
1871
|
+
/* @__PURE__ */ jsx9("path", { d: "M2 6l8 5 8-5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
|
|
1872
|
+
] }) });
|
|
1873
|
+
var OtpIcon = () => /* @__PURE__ */ jsx9("div", { className: "fc-method-icon-wrap", children: /* @__PURE__ */ jsxs8("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", className: "fc-method-icon", children: [
|
|
1874
|
+
/* @__PURE__ */ jsx9("rect", { x: "3", y: "6", width: "14", height: "9", rx: "1.5", stroke: "currentColor", strokeWidth: "1.5" }),
|
|
1875
|
+
/* @__PURE__ */ jsx9("circle", { cx: "6.5", cy: "10.5", r: "1", fill: "currentColor" }),
|
|
1876
|
+
/* @__PURE__ */ jsx9("circle", { cx: "10", cy: "10.5", r: "1", fill: "currentColor" }),
|
|
1877
|
+
/* @__PURE__ */ jsx9("circle", { cx: "13.5", cy: "10.5", r: "1", fill: "currentColor" })
|
|
1878
|
+
] }) });
|
|
1879
|
+
var WalletIcon = () => /* @__PURE__ */ jsx9("div", { className: "fc-method-icon-wrap", children: /* @__PURE__ */ jsxs8("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", className: "fc-method-icon", children: [
|
|
1880
|
+
/* @__PURE__ */ jsx9("rect", { x: "2", y: "5", width: "16", height: "11", rx: "2", stroke: "currentColor", strokeWidth: "1.5" }),
|
|
1881
|
+
/* @__PURE__ */ jsx9("rect", { x: "13", y: "9", width: "5", height: "3", rx: "1", stroke: "currentColor", strokeWidth: "1.5" })
|
|
1882
|
+
] }) });
|
|
1883
|
+
var PasskeyIcon = () => /* @__PURE__ */ jsx9("div", { className: "fc-method-icon-wrap", children: /* @__PURE__ */ jsxs8("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", className: "fc-method-icon", children: [
|
|
1884
|
+
/* @__PURE__ */ jsx9("circle", { cx: "8", cy: "7", r: "3", stroke: "currentColor", strokeWidth: "1.5" }),
|
|
1885
|
+
/* @__PURE__ */ jsx9("path", { d: "M13 13.5a5 5 0 0 0-10 0", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" }),
|
|
1886
|
+
/* @__PURE__ */ jsx9("path", { d: "M15 10v4m0 0l-1.5-1m1.5 1l1.5-1", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" })
|
|
1887
|
+
] }) });
|
|
1888
|
+
var LoadingSpinner = () => /* @__PURE__ */ jsx9("div", { className: "fc-method-icon-wrap", children: /* @__PURE__ */ jsx9("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", className: "fc-method-icon fc-spin", children: /* @__PURE__ */ jsx9("path", { d: "M10 2a8 8 0 0 1 8 8", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" }) }) });
|
|
1889
|
+
function MethodSelect() {
|
|
1890
|
+
const { setModalStep, loginWithPasskey, config } = useForgeConnect();
|
|
1891
|
+
const methods = resolveLoginMethods(config);
|
|
1892
|
+
const [passkeyLoading, setPasskeyLoading] = useState6(false);
|
|
1893
|
+
const [passkeyError, setPasskeyError] = useState6("");
|
|
1894
|
+
const handlePasskey = async () => {
|
|
1895
|
+
setPasskeyLoading(true);
|
|
1896
|
+
setPasskeyError("");
|
|
1897
|
+
try {
|
|
1898
|
+
await loginWithPasskey();
|
|
1899
|
+
} catch (err) {
|
|
1900
|
+
setPasskeyError(err instanceof Error ? err.message : "Passkey authentication failed.");
|
|
1901
|
+
} finally {
|
|
1902
|
+
setPasskeyLoading(false);
|
|
1903
|
+
}
|
|
1904
|
+
};
|
|
1905
|
+
const elements = [];
|
|
1906
|
+
let i = 0;
|
|
1907
|
+
while (i < methods.length) {
|
|
1908
|
+
const method = methods[i];
|
|
1909
|
+
if (isOAuthMethod(method)) {
|
|
1910
|
+
const oauthGroup = [];
|
|
1911
|
+
while (i < methods.length && isOAuthMethod(methods[i])) {
|
|
1912
|
+
oauthGroup.push(methods[i]);
|
|
1913
|
+
i++;
|
|
1914
|
+
}
|
|
1915
|
+
if (elements.length > 0) {
|
|
1916
|
+
elements.push(/* @__PURE__ */ jsx9("div", { className: "fc-divider", children: /* @__PURE__ */ jsx9("span", { children: "or" }) }, `div-before-${oauthGroup[0]}`));
|
|
1917
|
+
}
|
|
1918
|
+
elements.push(
|
|
1919
|
+
/* @__PURE__ */ jsx9("div", { className: "fc-oauth-group", children: oauthGroup.map((p) => /* @__PURE__ */ jsx9(OAuthButton, { provider: p }, p)) }, `oauth-${oauthGroup.join("-")}`)
|
|
1920
|
+
);
|
|
1921
|
+
} else {
|
|
1922
|
+
if (elements.length > 0) {
|
|
1923
|
+
const prev = methods[i - 1];
|
|
1924
|
+
if (prev && isOAuthMethod(prev)) {
|
|
1925
|
+
elements.push(/* @__PURE__ */ jsx9("div", { className: "fc-divider", children: /* @__PURE__ */ jsx9("span", { children: "or" }) }, `div-after-${prev}`));
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
switch (method) {
|
|
1929
|
+
case "email":
|
|
1930
|
+
elements.push(
|
|
1931
|
+
/* @__PURE__ */ jsxs8("button", { type: "button", className: "fc-btn fc-btn-method", onClick: () => setModalStep("email-login"), children: [
|
|
1932
|
+
/* @__PURE__ */ jsx9(EmailIcon, {}),
|
|
1933
|
+
" ",
|
|
1934
|
+
/* @__PURE__ */ jsx9("span", { className: "fc-btn-name", children: "Password" })
|
|
1935
|
+
] }, "email")
|
|
1936
|
+
);
|
|
1937
|
+
break;
|
|
1938
|
+
case "otp":
|
|
1939
|
+
elements.push(
|
|
1940
|
+
/* @__PURE__ */ jsxs8("button", { type: "button", className: "fc-btn fc-btn-method", onClick: () => setModalStep("email-otp"), children: [
|
|
1941
|
+
/* @__PURE__ */ jsx9(OtpIcon, {}),
|
|
1942
|
+
" ",
|
|
1943
|
+
/* @__PURE__ */ jsx9("span", { className: "fc-btn-name", children: "Magic code" })
|
|
1944
|
+
] }, "otp")
|
|
1945
|
+
);
|
|
1946
|
+
break;
|
|
1947
|
+
case "wallet":
|
|
1948
|
+
elements.push(
|
|
1949
|
+
/* @__PURE__ */ jsxs8("button", { type: "button", className: "fc-btn fc-btn-method", onClick: () => setModalStep("wallet-connect"), children: [
|
|
1950
|
+
/* @__PURE__ */ jsx9(WalletIcon, {}),
|
|
1951
|
+
" ",
|
|
1952
|
+
/* @__PURE__ */ jsx9("span", { className: "fc-btn-name", children: "Wallet" })
|
|
1953
|
+
] }, "wallet")
|
|
1954
|
+
);
|
|
1955
|
+
break;
|
|
1956
|
+
case "passkey":
|
|
1957
|
+
elements.push(
|
|
1958
|
+
/* @__PURE__ */ jsxs8("button", { type: "button", className: "fc-btn fc-btn-method", onClick: handlePasskey, disabled: passkeyLoading, children: [
|
|
1959
|
+
passkeyLoading ? /* @__PURE__ */ jsx9(LoadingSpinner, {}) : /* @__PURE__ */ jsx9(PasskeyIcon, {}),
|
|
1960
|
+
/* @__PURE__ */ jsx9("span", { className: "fc-btn-name", children: passkeyLoading ? "Waiting for passkey..." : "Passkey" })
|
|
1961
|
+
] }, "passkey")
|
|
1962
|
+
);
|
|
1963
|
+
break;
|
|
1964
|
+
}
|
|
1965
|
+
i++;
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
return /* @__PURE__ */ jsxs8("div", { className: "fc-method-select", children: [
|
|
1969
|
+
passkeyError && /* @__PURE__ */ jsx9("p", { className: "fc-error", children: passkeyError }),
|
|
1970
|
+
elements
|
|
1971
|
+
] });
|
|
1972
|
+
}
|
|
1973
|
+
function OAuthLoadingView() {
|
|
1974
|
+
const { setModalStep } = useForgeConnect();
|
|
1975
|
+
return /* @__PURE__ */ jsxs8("div", { className: "fc-success", children: [
|
|
1976
|
+
/* @__PURE__ */ jsx9("div", { className: "fc-success-icon", children: /* @__PURE__ */ jsx9("svg", { width: "52", height: "52", viewBox: "0 0 52 52", className: "fc-spin", children: /* @__PURE__ */ jsx9("circle", { cx: "26", cy: "26", r: "24", fill: "none", stroke: "var(--fc-accent, #8b5cf6)", strokeWidth: "3", strokeLinecap: "round", strokeDasharray: "100", strokeDashoffset: "30" }) }) }),
|
|
1977
|
+
/* @__PURE__ */ jsx9("p", { className: "fc-success-text", children: "Completing sign in..." }),
|
|
1978
|
+
/* @__PURE__ */ jsx9("button", { type: "button", className: "fc-btn fc-btn-secondary", style: { marginTop: "16px" }, onClick: () => setModalStep("method-select"), children: "Cancel" })
|
|
1979
|
+
] });
|
|
1980
|
+
}
|
|
1981
|
+
function SuccessView() {
|
|
1982
|
+
return /* @__PURE__ */ jsxs8("div", { className: "fc-success", children: [
|
|
1983
|
+
/* @__PURE__ */ jsx9("div", { className: "fc-success-icon", children: /* @__PURE__ */ jsxs8("svg", { viewBox: "0 0 52 52", className: "fc-success-check", children: [
|
|
1984
|
+
/* @__PURE__ */ jsx9("circle", { className: "fc-success-circle", cx: "26", cy: "26", r: "24", fill: "none" }),
|
|
1985
|
+
/* @__PURE__ */ jsx9("path", { className: "fc-success-tick", fill: "none", d: "M15 26l7.5 7.5L37 19" })
|
|
1986
|
+
] }) }),
|
|
1987
|
+
/* @__PURE__ */ jsx9("p", { className: "fc-success-text", children: "You're in" })
|
|
1988
|
+
] });
|
|
1989
|
+
}
|
|
1990
|
+
|
|
1991
|
+
// src/components/account-modal.tsx
|
|
1992
|
+
import { useState as useState14, useEffect as useEffect10, useRef as useRef7, useCallback as useCallback7 } from "react";
|
|
1993
|
+
|
|
1994
|
+
// src/hooks/use-user.ts
|
|
1995
|
+
import { useState as useState7, useCallback as useCallback3, useEffect as useEffect5, useRef as useRef6 } from "react";
|
|
1996
|
+
function useUser() {
|
|
1997
|
+
const { auth, api, config, getAccessToken } = useForgeConnect();
|
|
1998
|
+
const [authMethods, setAuthMethods] = useState7(null);
|
|
1999
|
+
const [loading, setLoading] = useState7(false);
|
|
2000
|
+
const pendingRefreshRef = useRef6(false);
|
|
2001
|
+
const updateProfile = useCallback3(
|
|
2002
|
+
async (data) => {
|
|
2003
|
+
const token = getAccessToken();
|
|
2004
|
+
if (!token) throw new Error("Please sign in to continue.");
|
|
2005
|
+
return api.updateMe(token, data);
|
|
2006
|
+
},
|
|
2007
|
+
[api, getAccessToken]
|
|
2008
|
+
);
|
|
2009
|
+
const fetchAuthMethods = useCallback3(async () => {
|
|
2010
|
+
const token = getAccessToken();
|
|
2011
|
+
if (!token) throw new Error("Please sign in to continue.");
|
|
2012
|
+
setLoading(true);
|
|
2013
|
+
try {
|
|
2014
|
+
const methods = await api.getAuthMethods(token);
|
|
2015
|
+
setAuthMethods(methods);
|
|
2016
|
+
return methods;
|
|
2017
|
+
} finally {
|
|
2018
|
+
setLoading(false);
|
|
2019
|
+
}
|
|
2020
|
+
}, [api, getAccessToken]);
|
|
2021
|
+
const linkAuthMethod = useCallback3(
|
|
2022
|
+
async (data) => {
|
|
2023
|
+
const token = getAccessToken();
|
|
2024
|
+
if (!token) throw new Error("Please sign in to continue.");
|
|
2025
|
+
await api.linkAuthMethod(token, data);
|
|
2026
|
+
await fetchAuthMethods();
|
|
2027
|
+
},
|
|
2028
|
+
[api, getAccessToken, fetchAuthMethods]
|
|
2029
|
+
);
|
|
2030
|
+
const linkOtpSend = useCallback3(
|
|
2031
|
+
async (email) => {
|
|
2032
|
+
const token = getAccessToken();
|
|
2033
|
+
if (!token) throw new Error("Please sign in to continue.");
|
|
2034
|
+
await api.linkOtpSend(token, email);
|
|
2035
|
+
},
|
|
2036
|
+
[api, getAccessToken]
|
|
2037
|
+
);
|
|
2038
|
+
const linkOtpVerify = useCallback3(
|
|
2039
|
+
async (email, code) => {
|
|
2040
|
+
const token = getAccessToken();
|
|
2041
|
+
if (!token) throw new Error("Please sign in to continue.");
|
|
2042
|
+
await api.linkOtpVerify(token, email, code);
|
|
2043
|
+
await fetchAuthMethods();
|
|
2044
|
+
},
|
|
2045
|
+
[api, getAccessToken, fetchAuthMethods]
|
|
2046
|
+
);
|
|
2047
|
+
const unlinkAuthMethod = useCallback3(
|
|
2048
|
+
async (id) => {
|
|
2049
|
+
const token = getAccessToken();
|
|
2050
|
+
if (!token) throw new Error("Please sign in to continue.");
|
|
2051
|
+
await api.unlinkAuthMethod(token, id);
|
|
2052
|
+
await fetchAuthMethods();
|
|
2053
|
+
},
|
|
2054
|
+
[api, getAccessToken, fetchAuthMethods]
|
|
2055
|
+
);
|
|
2056
|
+
const linkOAuth = useCallback3(
|
|
2057
|
+
async (provider) => {
|
|
2058
|
+
const token = getAccessToken();
|
|
2059
|
+
if (!token) throw new Error("Please sign in to continue.");
|
|
2060
|
+
const { intentToken } = await api.createLinkIntent(token);
|
|
2061
|
+
const redirectUri = encodeURIComponent(window.location.origin + "/fc-oauth-callback");
|
|
2062
|
+
const url = `${config.apiUrl}/auth/oauth/${provider}/link?intent=${encodeURIComponent(intentToken)}&redirect_uri=${redirectUri}`;
|
|
2063
|
+
const width = 500;
|
|
2064
|
+
const height = 600;
|
|
2065
|
+
const left = window.screenX + (window.innerWidth - width) / 2;
|
|
2066
|
+
const top = window.screenY + (window.innerHeight - height) / 2;
|
|
2067
|
+
window.open(url, "fc_oauth_link", `width=${width},height=${height},left=${left},top=${top}`);
|
|
2068
|
+
pendingRefreshRef.current = true;
|
|
2069
|
+
},
|
|
2070
|
+
[api, config.apiUrl, getAccessToken]
|
|
2071
|
+
);
|
|
2072
|
+
useEffect5(() => {
|
|
2073
|
+
const handleMessage = (event) => {
|
|
2074
|
+
if (event.origin !== window.location.origin) return;
|
|
2075
|
+
if (event.data?.type === "fc_oauth_link_success" && pendingRefreshRef.current) {
|
|
2076
|
+
pendingRefreshRef.current = false;
|
|
2077
|
+
fetchAuthMethods().catch(() => {
|
|
2078
|
+
});
|
|
2079
|
+
}
|
|
2080
|
+
};
|
|
2081
|
+
window.addEventListener("message", handleMessage);
|
|
2082
|
+
return () => window.removeEventListener("message", handleMessage);
|
|
2083
|
+
}, [fetchAuthMethods]);
|
|
2084
|
+
return {
|
|
2085
|
+
user: auth.user,
|
|
2086
|
+
authMethods,
|
|
2087
|
+
loading,
|
|
2088
|
+
updateProfile,
|
|
2089
|
+
fetchAuthMethods,
|
|
2090
|
+
linkAuthMethod,
|
|
2091
|
+
linkOtpSend,
|
|
2092
|
+
linkOtpVerify,
|
|
2093
|
+
unlinkAuthMethod,
|
|
2094
|
+
linkOAuth
|
|
2095
|
+
};
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2098
|
+
// src/hooks/use-wallets.ts
|
|
2099
|
+
import { useState as useState8, useCallback as useCallback4 } from "react";
|
|
2100
|
+
|
|
2101
|
+
// src/lib/utils.ts
|
|
2102
|
+
var BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
2103
|
+
function uint8ArrayToBase58(bytes) {
|
|
2104
|
+
const digits = [0];
|
|
2105
|
+
for (const byte of bytes) {
|
|
2106
|
+
let carry = byte;
|
|
2107
|
+
for (let j = 0; j < digits.length; j++) {
|
|
2108
|
+
carry += digits[j] << 8;
|
|
2109
|
+
digits[j] = carry % 58;
|
|
2110
|
+
carry = carry / 58 | 0;
|
|
2111
|
+
}
|
|
2112
|
+
while (carry > 0) {
|
|
2113
|
+
digits.push(carry % 58);
|
|
2114
|
+
carry = carry / 58 | 0;
|
|
2115
|
+
}
|
|
2116
|
+
}
|
|
2117
|
+
let str = "";
|
|
2118
|
+
for (let i = 0; i < bytes.length && bytes[i] === 0; i++) {
|
|
2119
|
+
str += "1";
|
|
2120
|
+
}
|
|
2121
|
+
for (let i = digits.length - 1; i >= 0; i--) {
|
|
2122
|
+
str += BASE58_ALPHABET[digits[i]];
|
|
2123
|
+
}
|
|
2124
|
+
return str;
|
|
2125
|
+
}
|
|
2126
|
+
function timeAgo(dateStr) {
|
|
2127
|
+
const now = Date.now();
|
|
2128
|
+
const then = new Date(dateStr).getTime();
|
|
2129
|
+
const diff = now - then;
|
|
2130
|
+
const mins = Math.floor(diff / 6e4);
|
|
2131
|
+
if (mins < 1) return "just now";
|
|
2132
|
+
if (mins < 60) return `${mins}m ago`;
|
|
2133
|
+
const hours = Math.floor(mins / 60);
|
|
2134
|
+
if (hours < 24) return `${hours}h ago`;
|
|
2135
|
+
const days = Math.floor(hours / 24);
|
|
2136
|
+
if (days < 30) return `${days}d ago`;
|
|
2137
|
+
return new Date(dateStr).toLocaleDateString();
|
|
2138
|
+
}
|
|
2139
|
+
|
|
2140
|
+
// src/hooks/use-wallets.ts
|
|
2141
|
+
function useWallets() {
|
|
2142
|
+
const { api, getAccessToken } = useForgeConnect();
|
|
2143
|
+
const [wallets, setWallets] = useState8(null);
|
|
2144
|
+
const [loading, setLoading] = useState8(false);
|
|
2145
|
+
const fetchWallets = useCallback4(async () => {
|
|
2146
|
+
const token = getAccessToken();
|
|
2147
|
+
if (!token) throw new Error("Please sign in to continue.");
|
|
2148
|
+
setLoading(true);
|
|
2149
|
+
try {
|
|
2150
|
+
const data = await api.getWallets(token);
|
|
2151
|
+
setWallets(data);
|
|
2152
|
+
return data;
|
|
2153
|
+
} finally {
|
|
2154
|
+
setLoading(false);
|
|
2155
|
+
}
|
|
2156
|
+
}, [api, getAccessToken]);
|
|
2157
|
+
const updateWallet = useCallback4(
|
|
2158
|
+
async (id, data) => {
|
|
2159
|
+
const token = getAccessToken();
|
|
2160
|
+
if (!token) throw new Error("Please sign in to continue.");
|
|
2161
|
+
await api.updateWallet(token, id, data);
|
|
2162
|
+
await fetchWallets();
|
|
2163
|
+
},
|
|
2164
|
+
[api, getAccessToken, fetchWallets]
|
|
2165
|
+
);
|
|
2166
|
+
const linkWallet = useCallback4(
|
|
2167
|
+
async (walletAddress, signMessage, chain = "solana") => {
|
|
2168
|
+
const token = getAccessToken();
|
|
2169
|
+
if (!token) throw new Error("Please sign in to continue.");
|
|
2170
|
+
const { challengeId, message: challengeMessage } = await api.walletChallenge(walletAddress, chain);
|
|
2171
|
+
const encoded = new TextEncoder().encode(challengeMessage);
|
|
2172
|
+
const signatureBytes = await signMessage(encoded);
|
|
2173
|
+
const signature = chain === "solana" ? uint8ArrayToBase58(signatureBytes) : Array.from(signatureBytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
2174
|
+
await api.linkAuthMethod(token, {
|
|
2175
|
+
provider: `${chain}_wallet`,
|
|
2176
|
+
challengeId,
|
|
2177
|
+
signature,
|
|
2178
|
+
walletAddress
|
|
2179
|
+
});
|
|
2180
|
+
await fetchWallets();
|
|
2181
|
+
},
|
|
2182
|
+
[api, getAccessToken, fetchWallets]
|
|
2183
|
+
);
|
|
2184
|
+
return {
|
|
2185
|
+
wallets,
|
|
2186
|
+
loading,
|
|
2187
|
+
fetchWallets,
|
|
2188
|
+
updateWallet,
|
|
2189
|
+
linkWallet
|
|
2190
|
+
};
|
|
2191
|
+
}
|
|
2192
|
+
|
|
2193
|
+
// src/hooks/use-sessions.ts
|
|
2194
|
+
import { useState as useState9, useCallback as useCallback5 } from "react";
|
|
2195
|
+
function useSessions() {
|
|
2196
|
+
const { api, getAccessToken } = useForgeConnect();
|
|
2197
|
+
const [sessions, setSessions] = useState9(null);
|
|
2198
|
+
const [loading, setLoading] = useState9(false);
|
|
2199
|
+
const fetchSessions = useCallback5(async () => {
|
|
2200
|
+
const token = getAccessToken();
|
|
2201
|
+
if (!token) throw new Error("Please sign in to continue.");
|
|
2202
|
+
setLoading(true);
|
|
2203
|
+
try {
|
|
2204
|
+
const data = await api.getSessions(token);
|
|
2205
|
+
setSessions(data);
|
|
2206
|
+
return data;
|
|
2207
|
+
} finally {
|
|
2208
|
+
setLoading(false);
|
|
2209
|
+
}
|
|
2210
|
+
}, [api, getAccessToken]);
|
|
2211
|
+
const revokeSession = useCallback5(
|
|
2212
|
+
async (id) => {
|
|
2213
|
+
const token = getAccessToken();
|
|
2214
|
+
if (!token) throw new Error("Please sign in to continue.");
|
|
2215
|
+
await api.revokeSession(token, id);
|
|
2216
|
+
await fetchSessions();
|
|
2217
|
+
},
|
|
2218
|
+
[api, getAccessToken, fetchSessions]
|
|
2219
|
+
);
|
|
2220
|
+
return {
|
|
2221
|
+
sessions,
|
|
2222
|
+
loading,
|
|
2223
|
+
fetchSessions,
|
|
2224
|
+
revokeSession
|
|
2225
|
+
};
|
|
2226
|
+
}
|
|
2227
|
+
|
|
2228
|
+
// src/components/two-factor-modal.tsx
|
|
2229
|
+
import { useState as useState10, useEffect as useEffect6 } from "react";
|
|
2230
|
+
import { Fragment as Fragment3, jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
2231
|
+
function TwoFactorModal({ isOpen, onClose, initialEnabled, onStatusChange }) {
|
|
2232
|
+
const { api, getAccessToken, config } = useForgeConnect();
|
|
2233
|
+
const theme = config.appearance?.theme ?? "light";
|
|
2234
|
+
const [step, setStep] = useState10(initialEnabled ? "manage" : "setup");
|
|
2235
|
+
const [setupData, setSetupData] = useState10(null);
|
|
2236
|
+
const [code, setCode] = useState10("");
|
|
2237
|
+
const [loading, setLoading] = useState10(false);
|
|
2238
|
+
const [msg, setMsg] = useState10(null);
|
|
2239
|
+
const [showSecret, setShowSecret] = useState10(false);
|
|
2240
|
+
const [recoveryCodes, setRecoveryCodes] = useState10([]);
|
|
2241
|
+
useEffect6(() => {
|
|
2242
|
+
if (isOpen) {
|
|
2243
|
+
setStep(initialEnabled ? "manage" : "setup");
|
|
2244
|
+
setSetupData(null);
|
|
2245
|
+
setCode("");
|
|
2246
|
+
setMsg(null);
|
|
2247
|
+
setLoading(false);
|
|
2248
|
+
setShowSecret(false);
|
|
2249
|
+
setRecoveryCodes([]);
|
|
2250
|
+
if (!initialEnabled) {
|
|
2251
|
+
const token = getAccessToken();
|
|
2252
|
+
if (token) {
|
|
2253
|
+
setLoading(true);
|
|
2254
|
+
api.setup2FA(token).then((data) => {
|
|
2255
|
+
setSetupData(data);
|
|
2256
|
+
setRecoveryCodes(data.recoveryCodes);
|
|
2257
|
+
}).catch((err) => {
|
|
2258
|
+
setMsg({ text: err instanceof Error ? err.message : "Could not set up 2FA.", ok: false });
|
|
2259
|
+
}).finally(() => setLoading(false));
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2263
|
+
}, [isOpen, initialEnabled, api, getAccessToken]);
|
|
2264
|
+
const handleEnable = async () => {
|
|
2265
|
+
const token = getAccessToken();
|
|
2266
|
+
if (!token || !code) return;
|
|
2267
|
+
setLoading(true);
|
|
2268
|
+
setMsg(null);
|
|
2269
|
+
try {
|
|
2270
|
+
await api.enable2FA(token, code);
|
|
2271
|
+
onStatusChange(true);
|
|
2272
|
+
setStep("recovery-codes");
|
|
2273
|
+
setCode("");
|
|
2274
|
+
} catch (err) {
|
|
2275
|
+
setMsg({ text: err instanceof Error ? err.message : "Invalid code.", ok: false });
|
|
2276
|
+
} finally {
|
|
2277
|
+
setLoading(false);
|
|
2278
|
+
}
|
|
2279
|
+
};
|
|
2280
|
+
const handleDisable = async () => {
|
|
2281
|
+
const token = getAccessToken();
|
|
2282
|
+
if (!token || !code) return;
|
|
2283
|
+
setLoading(true);
|
|
2284
|
+
setMsg(null);
|
|
2285
|
+
try {
|
|
2286
|
+
await api.disable2FA(token, code);
|
|
2287
|
+
onStatusChange(false);
|
|
2288
|
+
setCode("");
|
|
2289
|
+
onClose();
|
|
2290
|
+
} catch (err) {
|
|
2291
|
+
setMsg({ text: err instanceof Error ? err.message : "Invalid code.", ok: false });
|
|
2292
|
+
} finally {
|
|
2293
|
+
setLoading(false);
|
|
2294
|
+
}
|
|
2295
|
+
};
|
|
2296
|
+
const handleRegenerate = async () => {
|
|
2297
|
+
const token = getAccessToken();
|
|
2298
|
+
if (!token || !code) return;
|
|
2299
|
+
setLoading(true);
|
|
2300
|
+
setMsg(null);
|
|
2301
|
+
try {
|
|
2302
|
+
const { recoveryCodes: codes } = await api.regenerateRecoveryCodes(token, code);
|
|
2303
|
+
setRecoveryCodes(codes);
|
|
2304
|
+
setStep("recovery-codes");
|
|
2305
|
+
setCode("");
|
|
2306
|
+
} catch (err) {
|
|
2307
|
+
setMsg({ text: err instanceof Error ? err.message : "Invalid code.", ok: false });
|
|
2308
|
+
} finally {
|
|
2309
|
+
setLoading(false);
|
|
2310
|
+
}
|
|
2311
|
+
};
|
|
2312
|
+
const handleCopyAll = () => {
|
|
2313
|
+
navigator.clipboard.writeText(recoveryCodes.join("\n")).catch(() => {
|
|
2314
|
+
});
|
|
2315
|
+
};
|
|
2316
|
+
const goBack = () => {
|
|
2317
|
+
setCode("");
|
|
2318
|
+
setMsg(null);
|
|
2319
|
+
setStep("manage");
|
|
2320
|
+
};
|
|
2321
|
+
if (!isOpen) return null;
|
|
2322
|
+
return /* @__PURE__ */ jsx10(ModalOverlay, { isOpen, onClose, children: /* @__PURE__ */ jsxs9(
|
|
2323
|
+
"div",
|
|
2324
|
+
{
|
|
2325
|
+
className: "fc-modal-content",
|
|
2326
|
+
style: { "--fc-accent": config.appearance?.accentColor ?? "#8b5cf6" },
|
|
2327
|
+
"data-theme": theme,
|
|
2328
|
+
children: [
|
|
2329
|
+
step === "setup" && /* @__PURE__ */ jsxs9(Fragment3, { children: [
|
|
2330
|
+
/* @__PURE__ */ jsx10("h2", { className: "fc-modal-title", children: "Set up 2FA" }),
|
|
2331
|
+
msg && /* @__PURE__ */ jsx10("p", { className: msg.ok ? "fc-text" : "fc-error", children: msg.text }),
|
|
2332
|
+
loading && !setupData && /* @__PURE__ */ jsx10("p", { className: "fc-text", children: "Loading..." }),
|
|
2333
|
+
setupData && /* @__PURE__ */ jsxs9("div", { className: "fc-tab", children: [
|
|
2334
|
+
/* @__PURE__ */ jsx10("p", { className: "fc-account-section-desc", style: { textAlign: "center" }, children: "Scan this QR code with your authenticator app" }),
|
|
2335
|
+
/* @__PURE__ */ jsx10("div", { style: { display: "flex", justifyContent: "center", margin: "12px 0" }, children: /* @__PURE__ */ jsx10(
|
|
2336
|
+
"img",
|
|
2337
|
+
{
|
|
2338
|
+
src: setupData.qrCodeDataUrl,
|
|
2339
|
+
alt: "TOTP QR code",
|
|
2340
|
+
style: { width: 180, height: 180, borderRadius: 12 }
|
|
2341
|
+
}
|
|
2342
|
+
) }),
|
|
2343
|
+
/* @__PURE__ */ jsxs9("div", { style: { textAlign: "center", marginBottom: 12 }, children: [
|
|
2344
|
+
/* @__PURE__ */ jsx10("button", { type: "button", className: "fc-link", onClick: () => setShowSecret(!showSecret), children: showSecret ? "Hide code" : "Can't scan? Enter manually" }),
|
|
2345
|
+
showSecret && /* @__PURE__ */ jsx10("p", { style: { margin: "8px 0 0", fontFamily: "monospace", fontSize: 12, opacity: 0.7, wordBreak: "break-all" }, children: setupData.secret })
|
|
2346
|
+
] }),
|
|
2347
|
+
/* @__PURE__ */ jsx10(
|
|
2348
|
+
"input",
|
|
2349
|
+
{
|
|
2350
|
+
className: "fc-input",
|
|
2351
|
+
type: "text",
|
|
2352
|
+
inputMode: "numeric",
|
|
2353
|
+
placeholder: "Enter 6-digit code",
|
|
2354
|
+
maxLength: 6,
|
|
2355
|
+
value: code,
|
|
2356
|
+
onChange: (e) => setCode(e.target.value.replace(/\D/g, "")),
|
|
2357
|
+
style: { textAlign: "center", letterSpacing: "0.2em" },
|
|
2358
|
+
autoFocus: true
|
|
2359
|
+
}
|
|
2360
|
+
),
|
|
2361
|
+
/* @__PURE__ */ jsx10(
|
|
2362
|
+
"button",
|
|
2363
|
+
{
|
|
2364
|
+
type: "button",
|
|
2365
|
+
className: "fc-btn fc-btn-primary",
|
|
2366
|
+
onClick: handleEnable,
|
|
2367
|
+
disabled: loading || code.length !== 6,
|
|
2368
|
+
style: { marginTop: 10 },
|
|
2369
|
+
children: loading ? "Verifying..." : "Verify & Enable"
|
|
2370
|
+
}
|
|
2371
|
+
)
|
|
2372
|
+
] })
|
|
2373
|
+
] }),
|
|
2374
|
+
step === "manage" && /* @__PURE__ */ jsxs9(Fragment3, { children: [
|
|
2375
|
+
/* @__PURE__ */ jsx10("h2", { className: "fc-modal-title", children: "Two-factor authentication" }),
|
|
2376
|
+
/* @__PURE__ */ jsx10("div", { style: { textAlign: "center", marginBottom: 20 }, children: /* @__PURE__ */ jsx10("span", { className: "fc-badge-verified", style: { fontSize: 12, padding: "4px 12px" }, children: "Enabled" }) }),
|
|
2377
|
+
/* @__PURE__ */ jsxs9("div", { className: "fc-tab", children: [
|
|
2378
|
+
/* @__PURE__ */ jsxs9(
|
|
2379
|
+
"button",
|
|
2380
|
+
{
|
|
2381
|
+
type: "button",
|
|
2382
|
+
className: "fc-security-card",
|
|
2383
|
+
onClick: () => {
|
|
2384
|
+
setCode("");
|
|
2385
|
+
setMsg(null);
|
|
2386
|
+
setStep("confirm-regenerate");
|
|
2387
|
+
},
|
|
2388
|
+
children: [
|
|
2389
|
+
/* @__PURE__ */ jsx10("span", { className: "fc-security-card-icon", children: /* @__PURE__ */ jsxs9("svg", { viewBox: "0 0 20 20", fill: "none", children: [
|
|
2390
|
+
/* @__PURE__ */ jsx10("path", { d: "M3 10a7 7 0 0 1 7-7m0 14a7 7 0 0 1-7-7m14 0a7 7 0 0 1-7 7m0-14a7 7 0 0 1 7 7", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" }),
|
|
2391
|
+
/* @__PURE__ */ jsx10("path", { d: "M14 3l-1 3h3", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" })
|
|
2392
|
+
] }) }),
|
|
2393
|
+
/* @__PURE__ */ jsxs9("span", { className: "fc-security-card-info", children: [
|
|
2394
|
+
/* @__PURE__ */ jsx10("span", { className: "fc-security-card-label", children: "Regenerate recovery codes" }),
|
|
2395
|
+
/* @__PURE__ */ jsx10("span", { className: "fc-security-card-detail", children: "Get new backup codes" })
|
|
2396
|
+
] }),
|
|
2397
|
+
/* @__PURE__ */ jsx10("span", { className: "fc-security-card-chevron", children: /* @__PURE__ */ jsx10("svg", { viewBox: "0 0 16 16", fill: "none", children: /* @__PURE__ */ jsx10("path", { d: "M6 4l4 4-4 4", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) }) })
|
|
2398
|
+
]
|
|
2399
|
+
}
|
|
2400
|
+
),
|
|
2401
|
+
/* @__PURE__ */ jsxs9(
|
|
2402
|
+
"button",
|
|
2403
|
+
{
|
|
2404
|
+
type: "button",
|
|
2405
|
+
className: "fc-security-card",
|
|
2406
|
+
onClick: () => {
|
|
2407
|
+
setCode("");
|
|
2408
|
+
setMsg(null);
|
|
2409
|
+
setStep("confirm-disable");
|
|
2410
|
+
},
|
|
2411
|
+
children: [
|
|
2412
|
+
/* @__PURE__ */ jsx10("span", { className: "fc-security-card-icon", style: { color: "#dc2626" }, children: /* @__PURE__ */ jsxs9("svg", { viewBox: "0 0 20 20", fill: "none", children: [
|
|
2413
|
+
/* @__PURE__ */ jsx10("path", { d: "M10 2l6 3v4c0 4.42-2.56 8.22-6 9.5C6.56 17.22 4 13.42 4 9V5l6-3z", stroke: "currentColor", strokeWidth: "1.5", strokeLinejoin: "round" }),
|
|
2414
|
+
/* @__PURE__ */ jsx10("path", { d: "M7.5 7.5l5 5M12.5 7.5l-5 5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
|
|
2415
|
+
] }) }),
|
|
2416
|
+
/* @__PURE__ */ jsxs9("span", { className: "fc-security-card-info", children: [
|
|
2417
|
+
/* @__PURE__ */ jsx10("span", { className: "fc-security-card-label", style: { color: "#dc2626" }, children: "Disable 2FA" }),
|
|
2418
|
+
/* @__PURE__ */ jsx10("span", { className: "fc-security-card-detail", children: "Remove two-factor authentication" })
|
|
2419
|
+
] }),
|
|
2420
|
+
/* @__PURE__ */ jsx10("span", { className: "fc-security-card-chevron", children: /* @__PURE__ */ jsx10("svg", { viewBox: "0 0 16 16", fill: "none", children: /* @__PURE__ */ jsx10("path", { d: "M6 4l4 4-4 4", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) }) })
|
|
2421
|
+
]
|
|
2422
|
+
}
|
|
2423
|
+
)
|
|
2424
|
+
] })
|
|
2425
|
+
] }),
|
|
2426
|
+
step === "confirm-regenerate" && /* @__PURE__ */ jsxs9(Fragment3, { children: [
|
|
2427
|
+
/* @__PURE__ */ jsx10("h2", { className: "fc-modal-title", children: "Regenerate recovery codes" }),
|
|
2428
|
+
/* @__PURE__ */ jsx10("p", { className: "fc-account-section-desc", style: { textAlign: "center" }, children: "Enter your 2FA code to generate new recovery codes. Your old codes will stop working." }),
|
|
2429
|
+
msg && /* @__PURE__ */ jsx10("p", { className: msg.ok ? "fc-text" : "fc-error", children: msg.text }),
|
|
2430
|
+
/* @__PURE__ */ jsxs9("div", { className: "fc-tab", children: [
|
|
2431
|
+
/* @__PURE__ */ jsx10(
|
|
2432
|
+
"input",
|
|
2433
|
+
{
|
|
2434
|
+
className: "fc-input",
|
|
2435
|
+
type: "text",
|
|
2436
|
+
inputMode: "numeric",
|
|
2437
|
+
placeholder: "Enter 6-digit code",
|
|
2438
|
+
maxLength: 6,
|
|
2439
|
+
value: code,
|
|
2440
|
+
onChange: (e) => setCode(e.target.value.replace(/\D/g, "")),
|
|
2441
|
+
style: { textAlign: "center", letterSpacing: "0.2em" },
|
|
2442
|
+
autoFocus: true
|
|
2443
|
+
}
|
|
2444
|
+
),
|
|
2445
|
+
/* @__PURE__ */ jsx10(
|
|
2446
|
+
"button",
|
|
2447
|
+
{
|
|
2448
|
+
type: "button",
|
|
2449
|
+
className: "fc-btn fc-btn-primary",
|
|
2450
|
+
onClick: handleRegenerate,
|
|
2451
|
+
disabled: loading || code.length !== 6,
|
|
2452
|
+
style: { marginTop: 10 },
|
|
2453
|
+
children: loading ? "Generating..." : "Regenerate codes"
|
|
2454
|
+
}
|
|
2455
|
+
),
|
|
2456
|
+
/* @__PURE__ */ jsx10("div", { className: "fc-switch", style: { marginTop: 12 }, children: /* @__PURE__ */ jsx10("button", { type: "button", className: "fc-link", onClick: goBack, children: "Back" }) })
|
|
2457
|
+
] })
|
|
2458
|
+
] }),
|
|
2459
|
+
step === "confirm-disable" && /* @__PURE__ */ jsxs9(Fragment3, { children: [
|
|
2460
|
+
/* @__PURE__ */ jsx10("h2", { className: "fc-modal-title", children: "Disable 2FA" }),
|
|
2461
|
+
/* @__PURE__ */ jsx10("p", { className: "fc-account-section-desc", style: { textAlign: "center" }, children: "Enter your 2FA code to disable two-factor authentication." }),
|
|
2462
|
+
msg && /* @__PURE__ */ jsx10("p", { className: msg.ok ? "fc-text" : "fc-error", children: msg.text }),
|
|
2463
|
+
/* @__PURE__ */ jsxs9("div", { className: "fc-tab", children: [
|
|
2464
|
+
/* @__PURE__ */ jsx10(
|
|
2465
|
+
"input",
|
|
2466
|
+
{
|
|
2467
|
+
className: "fc-input",
|
|
2468
|
+
type: "text",
|
|
2469
|
+
inputMode: "numeric",
|
|
2470
|
+
placeholder: "Enter 6-digit code",
|
|
2471
|
+
maxLength: 6,
|
|
2472
|
+
value: code,
|
|
2473
|
+
onChange: (e) => setCode(e.target.value.replace(/\D/g, "")),
|
|
2474
|
+
style: { textAlign: "center", letterSpacing: "0.2em" },
|
|
2475
|
+
autoFocus: true
|
|
2476
|
+
}
|
|
2477
|
+
),
|
|
2478
|
+
/* @__PURE__ */ jsx10(
|
|
2479
|
+
"button",
|
|
2480
|
+
{
|
|
2481
|
+
type: "button",
|
|
2482
|
+
className: "fc-btn fc-btn-secondary",
|
|
2483
|
+
onClick: handleDisable,
|
|
2484
|
+
disabled: loading || code.length !== 6,
|
|
2485
|
+
style: { marginTop: 10, background: "rgba(220, 38, 38, 0.1)", color: "#dc2626", borderColor: "rgba(220, 38, 38, 0.2)" },
|
|
2486
|
+
children: loading ? "Disabling..." : "Disable 2FA"
|
|
2487
|
+
}
|
|
2488
|
+
),
|
|
2489
|
+
/* @__PURE__ */ jsx10("div", { className: "fc-switch", style: { marginTop: 12 }, children: /* @__PURE__ */ jsx10("button", { type: "button", className: "fc-link", onClick: goBack, children: "Back" }) })
|
|
2490
|
+
] })
|
|
2491
|
+
] }),
|
|
2492
|
+
step === "recovery-codes" && /* @__PURE__ */ jsxs9(Fragment3, { children: [
|
|
2493
|
+
/* @__PURE__ */ jsx10("h2", { className: "fc-modal-title", children: "Save your recovery codes" }),
|
|
2494
|
+
/* @__PURE__ */ jsx10("p", { className: "fc-account-section-desc", style: { textAlign: "center" }, children: "Store these codes somewhere safe. Each code can only be used once." }),
|
|
2495
|
+
/* @__PURE__ */ jsx10("div", { className: "fc-recovery-grid", children: recoveryCodes.map((c, i) => /* @__PURE__ */ jsx10("div", { className: "fc-recovery-code", children: c }, i)) }),
|
|
2496
|
+
/* @__PURE__ */ jsx10("div", { style: { textAlign: "center", margin: "8px 0 12px" }, children: /* @__PURE__ */ jsx10("button", { type: "button", className: "fc-link", onClick: handleCopyAll, children: "Copy all" }) }),
|
|
2497
|
+
/* @__PURE__ */ jsx10("button", { type: "button", className: "fc-btn fc-btn-primary", onClick: onClose, children: "Done" })
|
|
2498
|
+
] })
|
|
2499
|
+
]
|
|
2500
|
+
}
|
|
2501
|
+
) });
|
|
2502
|
+
}
|
|
2503
|
+
|
|
2504
|
+
// src/components/passkeys-modal.tsx
|
|
2505
|
+
import { useState as useState11, useEffect as useEffect7, useCallback as useCallback6 } from "react";
|
|
2506
|
+
import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
2507
|
+
function PasskeysModal({ isOpen, onClose, onCountChange }) {
|
|
2508
|
+
const { api, getAccessToken, config } = useForgeConnect();
|
|
2509
|
+
const theme = config.appearance?.theme ?? "light";
|
|
2510
|
+
const [passkeys, setPasskeys] = useState11([]);
|
|
2511
|
+
const [loading, setLoading] = useState11(false);
|
|
2512
|
+
const [addLoading, setAddLoading] = useState11(false);
|
|
2513
|
+
const [msg, setMsg] = useState11("");
|
|
2514
|
+
const fetchPasskeys = useCallback6(async () => {
|
|
2515
|
+
const token = getAccessToken();
|
|
2516
|
+
if (!token) return;
|
|
2517
|
+
setLoading(true);
|
|
2518
|
+
try {
|
|
2519
|
+
const list = await api.getPasskeys(token);
|
|
2520
|
+
setPasskeys(list);
|
|
2521
|
+
onCountChange(list.length);
|
|
2522
|
+
} catch {
|
|
2523
|
+
} finally {
|
|
2524
|
+
setLoading(false);
|
|
2525
|
+
}
|
|
2526
|
+
}, [api, getAccessToken, onCountChange]);
|
|
2527
|
+
useEffect7(() => {
|
|
2528
|
+
if (isOpen) {
|
|
2529
|
+
setMsg("");
|
|
2530
|
+
fetchPasskeys();
|
|
2531
|
+
}
|
|
2532
|
+
}, [isOpen, fetchPasskeys]);
|
|
2533
|
+
const handleAdd = async () => {
|
|
2534
|
+
const token = getAccessToken();
|
|
2535
|
+
if (!token) return;
|
|
2536
|
+
setAddLoading(true);
|
|
2537
|
+
setMsg("");
|
|
2538
|
+
try {
|
|
2539
|
+
const { options, challengeKey } = await api.getPasskeyRegisterOptions(token, config.webauthnRpId, config.webauthnOrigin);
|
|
2540
|
+
const regResponse = await startRegistration({ optionsJSON: options });
|
|
2541
|
+
const passkey = await api.verifyPasskeyRegistration(token, challengeKey, regResponse, void 0, config.webauthnRpId, config.webauthnOrigin);
|
|
2542
|
+
const updated = [...passkeys, passkey];
|
|
2543
|
+
setPasskeys(updated);
|
|
2544
|
+
onCountChange(updated.length);
|
|
2545
|
+
} catch (err) {
|
|
2546
|
+
setMsg(err instanceof Error ? err.message : "Could not add passkey.");
|
|
2547
|
+
} finally {
|
|
2548
|
+
setAddLoading(false);
|
|
2549
|
+
}
|
|
2550
|
+
};
|
|
2551
|
+
const handleDelete = async (id) => {
|
|
2552
|
+
const token = getAccessToken();
|
|
2553
|
+
if (!token) return;
|
|
2554
|
+
setMsg("");
|
|
2555
|
+
try {
|
|
2556
|
+
await api.deletePasskey(token, id);
|
|
2557
|
+
const updated = passkeys.filter((p) => p.id !== id);
|
|
2558
|
+
setPasskeys(updated);
|
|
2559
|
+
onCountChange(updated.length);
|
|
2560
|
+
} catch (err) {
|
|
2561
|
+
setMsg(err instanceof Error ? err.message : "Could not remove passkey.");
|
|
2562
|
+
}
|
|
2563
|
+
};
|
|
2564
|
+
if (!isOpen) return null;
|
|
2565
|
+
return /* @__PURE__ */ jsx11(ModalOverlay, { isOpen, onClose, children: /* @__PURE__ */ jsxs10(
|
|
2566
|
+
"div",
|
|
2567
|
+
{
|
|
2568
|
+
className: "fc-modal-content",
|
|
2569
|
+
style: { "--fc-accent": config.appearance?.accentColor ?? "#8b5cf6" },
|
|
2570
|
+
"data-theme": theme,
|
|
2571
|
+
children: [
|
|
2572
|
+
/* @__PURE__ */ jsx11("h2", { className: "fc-modal-title", children: "Passkeys" }),
|
|
2573
|
+
/* @__PURE__ */ jsx11("p", { className: "fc-account-section-desc", style: { textAlign: "center" }, children: "Sign in without a password using biometrics or a security key." }),
|
|
2574
|
+
msg && /* @__PURE__ */ jsx11("p", { className: "fc-error", children: msg }),
|
|
2575
|
+
loading && /* @__PURE__ */ jsx11("p", { className: "fc-text", children: "Loading..." }),
|
|
2576
|
+
/* @__PURE__ */ jsxs10("div", { className: "fc-tab", children: [
|
|
2577
|
+
passkeys.map((pk) => /* @__PURE__ */ jsxs10("div", { className: "fc-account-item", children: [
|
|
2578
|
+
/* @__PURE__ */ jsx11("span", { className: "fc-account-item-icon", children: /* @__PURE__ */ jsxs10("svg", { width: "16", height: "16", viewBox: "0 0 20 20", fill: "none", children: [
|
|
2579
|
+
/* @__PURE__ */ jsx11("circle", { cx: "8", cy: "7", r: "3", stroke: "currentColor", strokeWidth: "1.5" }),
|
|
2580
|
+
/* @__PURE__ */ jsx11("path", { d: "M13 13.5a5 5 0 0 0-10 0", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" }),
|
|
2581
|
+
/* @__PURE__ */ jsx11("path", { d: "M15 10v4m0 0l-1.5-1m1.5 1l1.5-1", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" })
|
|
2582
|
+
] }) }),
|
|
2583
|
+
/* @__PURE__ */ jsxs10("div", { className: "fc-account-item-info", children: [
|
|
2584
|
+
/* @__PURE__ */ jsx11("span", { className: "fc-account-item-label", children: pk.name || "Passkey" }),
|
|
2585
|
+
/* @__PURE__ */ jsxs10("span", { className: "fc-account-item-detail", children: [
|
|
2586
|
+
pk.deviceType ?? "Unknown device",
|
|
2587
|
+
pk.lastUsedAt ? ` \xB7 Last used ${timeAgo(pk.lastUsedAt)}` : ""
|
|
2588
|
+
] })
|
|
2589
|
+
] }),
|
|
2590
|
+
/* @__PURE__ */ jsxs10("div", { className: "fc-account-item-actions", children: [
|
|
2591
|
+
pk.backedUp && /* @__PURE__ */ jsx11("span", { className: "fc-badge-verified", children: "Synced" }),
|
|
2592
|
+
/* @__PURE__ */ jsx11("button", { type: "button", className: "fc-btn-danger-sm", onClick: () => handleDelete(pk.id), children: "Remove" })
|
|
2593
|
+
] })
|
|
2594
|
+
] }, pk.id)),
|
|
2595
|
+
!loading && passkeys.length === 0 && /* @__PURE__ */ jsx11("p", { className: "fc-account-empty", children: "No passkeys yet" }),
|
|
2596
|
+
/* @__PURE__ */ jsx11(
|
|
2597
|
+
"button",
|
|
2598
|
+
{
|
|
2599
|
+
type: "button",
|
|
2600
|
+
className: "fc-btn fc-btn-secondary",
|
|
2601
|
+
onClick: handleAdd,
|
|
2602
|
+
disabled: addLoading,
|
|
2603
|
+
style: { marginTop: 8 },
|
|
2604
|
+
children: addLoading ? "Adding..." : "+ Add passkey"
|
|
2605
|
+
}
|
|
2606
|
+
)
|
|
2607
|
+
] })
|
|
2608
|
+
]
|
|
2609
|
+
}
|
|
2610
|
+
) });
|
|
2611
|
+
}
|
|
2612
|
+
|
|
2613
|
+
// src/components/password-modal.tsx
|
|
2614
|
+
import { useState as useState12, useEffect as useEffect8 } from "react";
|
|
2615
|
+
|
|
2616
|
+
// src/components/error-view.tsx
|
|
2617
|
+
import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
2618
|
+
function ErrorView({
|
|
2619
|
+
title = "Something went wrong",
|
|
2620
|
+
message,
|
|
2621
|
+
onRetry,
|
|
2622
|
+
onClose
|
|
2623
|
+
}) {
|
|
2624
|
+
return /* @__PURE__ */ jsxs11("div", { className: "fc-error-view", children: [
|
|
2625
|
+
/* @__PURE__ */ jsx12("div", { className: "fc-error-icon", children: /* @__PURE__ */ jsxs11("svg", { viewBox: "0 0 52 52", className: "fc-error-check", children: [
|
|
2626
|
+
/* @__PURE__ */ jsx12("circle", { className: "fc-error-circle", cx: "26", cy: "26", r: "24", fill: "none" }),
|
|
2627
|
+
/* @__PURE__ */ jsx12("line", { className: "fc-error-cross-1", x1: "16", y1: "16", x2: "36", y2: "36" }),
|
|
2628
|
+
/* @__PURE__ */ jsx12("line", { className: "fc-error-cross-2", x1: "36", y1: "16", x2: "16", y2: "36" })
|
|
2629
|
+
] }) }),
|
|
2630
|
+
/* @__PURE__ */ jsx12("p", { className: "fc-error-text", children: title }),
|
|
2631
|
+
message && /* @__PURE__ */ jsx12("p", { className: "fc-error-subtext", children: message }),
|
|
2632
|
+
(onRetry || onClose) && /* @__PURE__ */ jsxs11("div", { className: "fc-error-actions", children: [
|
|
2633
|
+
onRetry && /* @__PURE__ */ jsx12("button", { type: "button", className: "fc-btn fc-btn-primary", onClick: onRetry, children: "Try again" }),
|
|
2634
|
+
onClose && /* @__PURE__ */ jsx12("button", { type: "button", className: "fc-btn fc-btn-secondary", onClick: onClose, children: "Close" })
|
|
2635
|
+
] })
|
|
2636
|
+
] });
|
|
2637
|
+
}
|
|
2638
|
+
|
|
2639
|
+
// src/components/password-modal.tsx
|
|
2640
|
+
import { Fragment as Fragment4, jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
2641
|
+
function PasswordModal({ isOpen, onClose, hasPassword }) {
|
|
2642
|
+
const { api, getAccessToken, config } = useForgeConnect();
|
|
2643
|
+
const theme = config.appearance?.theme ?? "light";
|
|
2644
|
+
const [step, setStep] = useState12("form");
|
|
2645
|
+
const [currentPassword, setCurrentPassword] = useState12("");
|
|
2646
|
+
const [newPassword, setNewPassword] = useState12("");
|
|
2647
|
+
const [loading, setLoading] = useState12(false);
|
|
2648
|
+
const [error, setError] = useState12("");
|
|
2649
|
+
useEffect8(() => {
|
|
2650
|
+
if (isOpen) {
|
|
2651
|
+
setStep("form");
|
|
2652
|
+
setCurrentPassword("");
|
|
2653
|
+
setNewPassword("");
|
|
2654
|
+
setError("");
|
|
2655
|
+
setLoading(false);
|
|
2656
|
+
}
|
|
2657
|
+
}, [isOpen]);
|
|
2658
|
+
const handleSubmit = async (e) => {
|
|
2659
|
+
e.preventDefault();
|
|
2660
|
+
setError("");
|
|
2661
|
+
setLoading(true);
|
|
2662
|
+
try {
|
|
2663
|
+
const token = getAccessToken();
|
|
2664
|
+
if (!token) throw new Error("Please sign in to continue.");
|
|
2665
|
+
await api.setPassword(token, newPassword, hasPassword ? currentPassword : void 0);
|
|
2666
|
+
setStep("done");
|
|
2667
|
+
} catch (err) {
|
|
2668
|
+
const msg = err instanceof Error ? err.message : "Could not set your password. Please try again.";
|
|
2669
|
+
setError(msg);
|
|
2670
|
+
setStep("error");
|
|
2671
|
+
} finally {
|
|
2672
|
+
setLoading(false);
|
|
2673
|
+
}
|
|
2674
|
+
};
|
|
2675
|
+
const title = hasPassword ? "Change password" : "Set a password";
|
|
2676
|
+
return /* @__PURE__ */ jsx13(ModalOverlay, { isOpen, onClose, children: /* @__PURE__ */ jsx13(
|
|
2677
|
+
"div",
|
|
2678
|
+
{
|
|
2679
|
+
className: "fc-modal-content",
|
|
2680
|
+
style: { "--fc-accent": config.appearance?.accentColor ?? "#8b5cf6" },
|
|
2681
|
+
"data-theme": theme,
|
|
2682
|
+
children: step === "done" ? /* @__PURE__ */ jsxs12("div", { className: "fc-tab", style: { textAlign: "center", padding: "24px 0" }, children: [
|
|
2683
|
+
/* @__PURE__ */ jsxs12("div", { className: "fc-success", children: [
|
|
2684
|
+
/* @__PURE__ */ jsx13("div", { className: "fc-success-icon", children: /* @__PURE__ */ jsxs12("svg", { viewBox: "0 0 52 52", className: "fc-success-check", children: [
|
|
2685
|
+
/* @__PURE__ */ jsx13("circle", { className: "fc-success-circle", cx: "26", cy: "26", r: "24", fill: "none" }),
|
|
2686
|
+
/* @__PURE__ */ jsx13("path", { className: "fc-success-tick", fill: "none", d: "M15 26l7.5 7.5L37 19" })
|
|
2687
|
+
] }) }),
|
|
2688
|
+
/* @__PURE__ */ jsxs12("p", { className: "fc-success-text", children: [
|
|
2689
|
+
"Password ",
|
|
2690
|
+
hasPassword ? "updated" : "set"
|
|
2691
|
+
] })
|
|
2692
|
+
] }),
|
|
2693
|
+
/* @__PURE__ */ jsx13("button", { type: "button", className: "fc-btn fc-btn-primary", onClick: onClose, style: { marginTop: 16 }, children: "Done" })
|
|
2694
|
+
] }) : step === "error" ? /* @__PURE__ */ jsx13(
|
|
2695
|
+
ErrorView,
|
|
2696
|
+
{
|
|
2697
|
+
title: "Password update failed",
|
|
2698
|
+
message: error,
|
|
2699
|
+
onRetry: () => setStep("form"),
|
|
2700
|
+
onClose
|
|
2701
|
+
}
|
|
2702
|
+
) : /* @__PURE__ */ jsxs12(Fragment4, { children: [
|
|
2703
|
+
/* @__PURE__ */ jsx13("h2", { className: "fc-modal-title", children: title }),
|
|
2704
|
+
/* @__PURE__ */ jsx13("div", { className: "fc-tab", children: /* @__PURE__ */ jsxs12("form", { onSubmit: handleSubmit, className: "fc-form", children: [
|
|
2705
|
+
hasPassword && /* @__PURE__ */ jsxs12("label", { className: "fc-label", children: [
|
|
2706
|
+
"Current password",
|
|
2707
|
+
/* @__PURE__ */ jsx13(
|
|
2708
|
+
"input",
|
|
2709
|
+
{
|
|
2710
|
+
type: "password",
|
|
2711
|
+
className: "fc-input",
|
|
2712
|
+
value: currentPassword,
|
|
2713
|
+
onChange: (e) => setCurrentPassword(e.target.value),
|
|
2714
|
+
placeholder: "Enter current password",
|
|
2715
|
+
required: true,
|
|
2716
|
+
autoComplete: "current-password"
|
|
2717
|
+
}
|
|
2718
|
+
)
|
|
2719
|
+
] }),
|
|
2720
|
+
/* @__PURE__ */ jsxs12("label", { className: "fc-label", children: [
|
|
2721
|
+
"New password",
|
|
2722
|
+
/* @__PURE__ */ jsx13(
|
|
2723
|
+
"input",
|
|
2724
|
+
{
|
|
2725
|
+
type: "password",
|
|
2726
|
+
className: "fc-input",
|
|
2727
|
+
value: newPassword,
|
|
2728
|
+
onChange: (e) => setNewPassword(e.target.value),
|
|
2729
|
+
placeholder: "8+ characters",
|
|
2730
|
+
required: true,
|
|
2731
|
+
autoComplete: "new-password",
|
|
2732
|
+
minLength: 8
|
|
2733
|
+
}
|
|
2734
|
+
)
|
|
2735
|
+
] }),
|
|
2736
|
+
error && /* @__PURE__ */ jsx13("p", { className: "fc-error", children: error }),
|
|
2737
|
+
/* @__PURE__ */ jsx13("button", { type: "submit", className: "fc-btn fc-btn-primary", disabled: loading, children: loading ? "Saving..." : hasPassword ? "Update password" : "Set password" })
|
|
2738
|
+
] }) })
|
|
2739
|
+
] })
|
|
2740
|
+
}
|
|
2741
|
+
) });
|
|
2742
|
+
}
|
|
2743
|
+
|
|
2744
|
+
// src/components/delete-account-modal.tsx
|
|
2745
|
+
import { useState as useState13, useEffect as useEffect9 } from "react";
|
|
2746
|
+
import { Fragment as Fragment5, jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
2747
|
+
function DeleteAccountModal({ isOpen, onClose, onDeleted }) {
|
|
2748
|
+
const { api, getAccessToken, config, logout } = useForgeConnect();
|
|
2749
|
+
const theme = config.appearance?.theme ?? "light";
|
|
2750
|
+
const [step, setStep] = useState13("confirm");
|
|
2751
|
+
const [code, setCode] = useState13("");
|
|
2752
|
+
const [loading, setLoading] = useState13(false);
|
|
2753
|
+
const [error, setError] = useState13("");
|
|
2754
|
+
useEffect9(() => {
|
|
2755
|
+
if (isOpen) {
|
|
2756
|
+
setStep("confirm");
|
|
2757
|
+
setCode("");
|
|
2758
|
+
setError("");
|
|
2759
|
+
setLoading(false);
|
|
2760
|
+
}
|
|
2761
|
+
}, [isOpen]);
|
|
2762
|
+
const handleDelete = async () => {
|
|
2763
|
+
setError("");
|
|
2764
|
+
setLoading(true);
|
|
2765
|
+
try {
|
|
2766
|
+
const token = getAccessToken();
|
|
2767
|
+
if (!token) throw new Error("Please sign in to continue.");
|
|
2768
|
+
const { requiresVerification } = await api.requestAccountDeletion(token);
|
|
2769
|
+
if (requiresVerification) {
|
|
2770
|
+
setStep("code");
|
|
2771
|
+
} else {
|
|
2772
|
+
await api.confirmAccountDeletion(token);
|
|
2773
|
+
setStep("done");
|
|
2774
|
+
}
|
|
2775
|
+
} catch (err) {
|
|
2776
|
+
const msg = err instanceof Error ? err.message : "Something went wrong. Please try again.";
|
|
2777
|
+
setError(msg);
|
|
2778
|
+
setStep("error");
|
|
2779
|
+
} finally {
|
|
2780
|
+
setLoading(false);
|
|
2781
|
+
}
|
|
2782
|
+
};
|
|
2783
|
+
const handleConfirmCode = async (e) => {
|
|
2784
|
+
e.preventDefault();
|
|
2785
|
+
setError("");
|
|
2786
|
+
setLoading(true);
|
|
2787
|
+
try {
|
|
2788
|
+
const token = getAccessToken();
|
|
2789
|
+
if (!token) throw new Error("Please sign in to continue.");
|
|
2790
|
+
await api.confirmAccountDeletion(token, code);
|
|
2791
|
+
setStep("done");
|
|
2792
|
+
} catch (err) {
|
|
2793
|
+
const msg = err instanceof Error ? err.message : "Something went wrong. Please try again.";
|
|
2794
|
+
setError(msg);
|
|
2795
|
+
setStep("error");
|
|
2796
|
+
} finally {
|
|
2797
|
+
setLoading(false);
|
|
2798
|
+
}
|
|
2799
|
+
};
|
|
2800
|
+
const handleDone = () => {
|
|
2801
|
+
logout();
|
|
2802
|
+
onDeleted();
|
|
2803
|
+
};
|
|
2804
|
+
if (!isOpen) return null;
|
|
2805
|
+
return /* @__PURE__ */ jsx14(ModalOverlay, { isOpen, onClose: step === "done" ? handleDone : onClose, children: /* @__PURE__ */ jsxs13(
|
|
2806
|
+
"div",
|
|
2807
|
+
{
|
|
2808
|
+
className: "fc-modal-content",
|
|
2809
|
+
style: { "--fc-accent": config.appearance?.accentColor ?? "#8b5cf6" },
|
|
2810
|
+
"data-theme": theme,
|
|
2811
|
+
children: [
|
|
2812
|
+
step === "confirm" && /* @__PURE__ */ jsxs13(Fragment5, { children: [
|
|
2813
|
+
/* @__PURE__ */ jsx14("h2", { className: "fc-modal-title", children: "Delete account" }),
|
|
2814
|
+
/* @__PURE__ */ jsxs13("div", { className: "fc-tab", style: { textAlign: "center" }, children: [
|
|
2815
|
+
/* @__PURE__ */ jsx14("p", { className: "fc-account-section-desc", children: "This will permanently delete your account and all associated data. This action cannot be undone." }),
|
|
2816
|
+
error && /* @__PURE__ */ jsx14("p", { className: "fc-error", children: error }),
|
|
2817
|
+
/* @__PURE__ */ jsx14(
|
|
2818
|
+
"button",
|
|
2819
|
+
{
|
|
2820
|
+
type: "button",
|
|
2821
|
+
className: "fc-btn fc-btn-secondary",
|
|
2822
|
+
onClick: handleDelete,
|
|
2823
|
+
disabled: loading,
|
|
2824
|
+
style: { marginTop: 12, background: "rgba(220, 38, 38, 0.1)", color: "#dc2626", borderColor: "rgba(220, 38, 38, 0.2)" },
|
|
2825
|
+
children: loading ? "Processing..." : "Delete my account"
|
|
2826
|
+
}
|
|
2827
|
+
),
|
|
2828
|
+
/* @__PURE__ */ jsx14("div", { style: { marginTop: 12 }, children: /* @__PURE__ */ jsx14("button", { type: "button", className: "fc-link", onClick: onClose, children: "Cancel" }) })
|
|
2829
|
+
] })
|
|
2830
|
+
] }),
|
|
2831
|
+
step === "code" && /* @__PURE__ */ jsxs13(Fragment5, { children: [
|
|
2832
|
+
/* @__PURE__ */ jsx14("h2", { className: "fc-modal-title", children: "Verify your identity" }),
|
|
2833
|
+
/* @__PURE__ */ jsxs13("div", { className: "fc-tab", children: [
|
|
2834
|
+
/* @__PURE__ */ jsx14("p", { className: "fc-account-section-desc", style: { textAlign: "center" }, children: "Enter the 6-digit code sent to your email to confirm deletion." }),
|
|
2835
|
+
error && /* @__PURE__ */ jsx14("p", { className: "fc-error", children: error }),
|
|
2836
|
+
/* @__PURE__ */ jsxs13("form", { onSubmit: handleConfirmCode, className: "fc-form", children: [
|
|
2837
|
+
/* @__PURE__ */ jsx14(
|
|
2838
|
+
"input",
|
|
2839
|
+
{
|
|
2840
|
+
className: "fc-input",
|
|
2841
|
+
type: "text",
|
|
2842
|
+
inputMode: "numeric",
|
|
2843
|
+
placeholder: "Enter 6-digit code",
|
|
2844
|
+
maxLength: 6,
|
|
2845
|
+
value: code,
|
|
2846
|
+
onChange: (e) => setCode(e.target.value.replace(/\D/g, "")),
|
|
2847
|
+
style: { textAlign: "center", letterSpacing: "0.2em" },
|
|
2848
|
+
autoFocus: true
|
|
2849
|
+
}
|
|
2850
|
+
),
|
|
2851
|
+
/* @__PURE__ */ jsx14(
|
|
2852
|
+
"button",
|
|
2853
|
+
{
|
|
2854
|
+
type: "submit",
|
|
2855
|
+
className: "fc-btn fc-btn-secondary",
|
|
2856
|
+
disabled: loading || code.length !== 6,
|
|
2857
|
+
style: { marginTop: 10, background: "rgba(220, 38, 38, 0.1)", color: "#dc2626", borderColor: "rgba(220, 38, 38, 0.2)" },
|
|
2858
|
+
children: loading ? "Deleting..." : "Delete my account"
|
|
2859
|
+
}
|
|
2860
|
+
)
|
|
2861
|
+
] }),
|
|
2862
|
+
/* @__PURE__ */ jsx14("div", { style: { marginTop: 12, textAlign: "center" }, children: /* @__PURE__ */ jsx14("button", { type: "button", className: "fc-link", onClick: onClose, children: "Cancel" }) })
|
|
2863
|
+
] })
|
|
2864
|
+
] }),
|
|
2865
|
+
step === "done" && /* @__PURE__ */ jsxs13("div", { className: "fc-tab", style: { textAlign: "center", padding: "24px 0" }, children: [
|
|
2866
|
+
/* @__PURE__ */ jsxs13("div", { className: "fc-success", children: [
|
|
2867
|
+
/* @__PURE__ */ jsx14("div", { className: "fc-success-icon", children: /* @__PURE__ */ jsxs13("svg", { viewBox: "0 0 52 52", className: "fc-success-check", children: [
|
|
2868
|
+
/* @__PURE__ */ jsx14("circle", { className: "fc-success-circle", cx: "26", cy: "26", r: "24", fill: "none" }),
|
|
2869
|
+
/* @__PURE__ */ jsx14("path", { className: "fc-success-tick", fill: "none", d: "M15 26l7.5 7.5L37 19" })
|
|
2870
|
+
] }) }),
|
|
2871
|
+
/* @__PURE__ */ jsx14("p", { className: "fc-success-text", children: "Account deleted" })
|
|
2872
|
+
] }),
|
|
2873
|
+
/* @__PURE__ */ jsx14("button", { type: "button", className: "fc-btn fc-btn-primary", onClick: handleDone, style: { marginTop: 16 }, children: "Done" })
|
|
2874
|
+
] }),
|
|
2875
|
+
step === "error" && /* @__PURE__ */ jsx14(
|
|
2876
|
+
ErrorView,
|
|
2877
|
+
{
|
|
2878
|
+
title: "Deletion failed",
|
|
2879
|
+
message: error,
|
|
2880
|
+
onRetry: () => setStep("confirm"),
|
|
2881
|
+
onClose
|
|
2882
|
+
}
|
|
2883
|
+
)
|
|
2884
|
+
]
|
|
2885
|
+
}
|
|
2886
|
+
) });
|
|
2887
|
+
}
|
|
2888
|
+
|
|
2889
|
+
// src/components/account-modal.tsx
|
|
2890
|
+
import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
2891
|
+
var METHOD_DISPLAY = {
|
|
2892
|
+
email: {
|
|
2893
|
+
label: "Email",
|
|
2894
|
+
iconHtml: '<svg width="16" height="16" viewBox="0 0 20 20" fill="none"><rect x="2" y="4" width="16" height="12" rx="2" stroke="currentColor" stroke-width="1.5"/><path d="M2 6l8 5 8-5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>'
|
|
2895
|
+
},
|
|
2896
|
+
solana_wallet: {
|
|
2897
|
+
label: "Solana Wallet",
|
|
2898
|
+
iconHtml: '<svg width="16" height="16" viewBox="0 0 20 20" fill="none"><rect x="2" y="5" width="16" height="11" rx="2" stroke="currentColor" stroke-width="1.5"/><rect x="13" y="9" width="5" height="3" rx="1" stroke="currentColor" stroke-width="1.5"/></svg>'
|
|
2899
|
+
},
|
|
2900
|
+
ethereum_wallet: {
|
|
2901
|
+
label: "Ethereum Wallet",
|
|
2902
|
+
iconHtml: '<svg width="16" height="16" viewBox="0 0 20 20" fill="none"><rect x="2" y="5" width="16" height="11" rx="2" stroke="currentColor" stroke-width="1.5"/><rect x="13" y="9" width="5" height="3" rx="1" stroke="currentColor" stroke-width="1.5"/></svg>'
|
|
2903
|
+
}
|
|
2904
|
+
};
|
|
2905
|
+
function getMethodDisplay(provider) {
|
|
2906
|
+
if (provider in PROVIDER_INFO) {
|
|
2907
|
+
const info = PROVIDER_INFO[provider];
|
|
2908
|
+
return { label: info.label, iconHtml: info.icon };
|
|
2909
|
+
}
|
|
2910
|
+
return METHOD_DISPLAY[provider] ?? { label: provider, iconHtml: void 0 };
|
|
2911
|
+
}
|
|
2912
|
+
function truncate(str, max = 12) {
|
|
2913
|
+
if (str.length <= max) return str;
|
|
2914
|
+
return `${str.slice(0, 6)}...${str.slice(-4)}`;
|
|
2915
|
+
}
|
|
2916
|
+
var ChevronRight = () => /* @__PURE__ */ jsx15("span", { className: "fc-security-card-chevron", children: /* @__PURE__ */ jsx15("svg", { viewBox: "0 0 16 16", fill: "none", children: /* @__PURE__ */ jsx15("path", { d: "M6 4l4 4-4 4", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) }) });
|
|
2917
|
+
var TABS = [
|
|
2918
|
+
{ key: "profile", label: "Profile" },
|
|
2919
|
+
{ key: "auth-methods", label: "Logins" },
|
|
2920
|
+
{ key: "wallets", label: "Wallets" },
|
|
2921
|
+
{ key: "sessions", label: "Security" }
|
|
2922
|
+
];
|
|
2923
|
+
function AccountModal() {
|
|
2924
|
+
const { accountModal, closeAccountModal, openLinkModal, linkModal, config, logout } = useForgeConnect();
|
|
2925
|
+
const { user } = useUser();
|
|
2926
|
+
const [activeTab, setActiveTab] = useState14("profile");
|
|
2927
|
+
const [refreshKey, setRefreshKey] = useState14(0);
|
|
2928
|
+
const prevLinkOpen = useRef7(false);
|
|
2929
|
+
useEffect10(() => {
|
|
2930
|
+
if (prevLinkOpen.current && !linkModal.isOpen) {
|
|
2931
|
+
setRefreshKey((k) => k + 1);
|
|
2932
|
+
}
|
|
2933
|
+
prevLinkOpen.current = linkModal.isOpen;
|
|
2934
|
+
}, [linkModal.isOpen]);
|
|
2935
|
+
if (!accountModal.isOpen) return null;
|
|
2936
|
+
const theme = config.appearance?.theme ?? "light";
|
|
2937
|
+
const initial = (user?.displayName ?? user?.primaryEmail ?? "?").charAt(0).toUpperCase();
|
|
2938
|
+
return /* @__PURE__ */ jsx15(ModalOverlay, { isOpen: accountModal.isOpen, onClose: closeAccountModal, children: /* @__PURE__ */ jsxs14(
|
|
2939
|
+
"div",
|
|
2940
|
+
{
|
|
2941
|
+
className: "fc-modal-content",
|
|
2942
|
+
style: { "--fc-accent": config.appearance?.accentColor ?? "#8b5cf6" },
|
|
2943
|
+
"data-theme": theme,
|
|
2944
|
+
children: [
|
|
2945
|
+
/* @__PURE__ */ jsxs14("div", { className: "fc-account-hero", children: [
|
|
2946
|
+
user?.avatarUrl ? /* @__PURE__ */ jsx15("img", { src: user.avatarUrl, alt: "", className: "fc-account-hero-avatar" }) : /* @__PURE__ */ jsx15("span", { className: "fc-account-hero-avatar fc-account-hero-avatar-placeholder", children: initial }),
|
|
2947
|
+
/* @__PURE__ */ jsxs14("div", { className: "fc-account-hero-info", children: [
|
|
2948
|
+
/* @__PURE__ */ jsx15("span", { className: "fc-account-hero-name", children: user?.displayName ?? "Your account" }),
|
|
2949
|
+
user?.primaryEmail && /* @__PURE__ */ jsx15("span", { className: "fc-account-hero-email", children: user.primaryEmail })
|
|
2950
|
+
] })
|
|
2951
|
+
] }),
|
|
2952
|
+
/* @__PURE__ */ jsx15("div", { className: "fc-account-tabs", children: TABS.map((tab) => /* @__PURE__ */ jsx15(
|
|
2953
|
+
"button",
|
|
2954
|
+
{
|
|
2955
|
+
type: "button",
|
|
2956
|
+
className: `fc-account-tab${activeTab === tab.key ? " fc-account-tab-active" : ""}`,
|
|
2957
|
+
onClick: () => setActiveTab(tab.key),
|
|
2958
|
+
children: tab.label
|
|
2959
|
+
},
|
|
2960
|
+
tab.key
|
|
2961
|
+
)) }),
|
|
2962
|
+
activeTab === "profile" && /* @__PURE__ */ jsx15(ProfileTab, {}),
|
|
2963
|
+
activeTab === "auth-methods" && /* @__PURE__ */ jsx15(LoginsTab, { onLink: () => openLinkModal("auth"), refreshKey }),
|
|
2964
|
+
activeTab === "wallets" && /* @__PURE__ */ jsx15(WalletsTab, { onLink: () => openLinkModal("wallet"), refreshKey }),
|
|
2965
|
+
activeTab === "sessions" && /* @__PURE__ */ jsx15(SecurityTab, {}),
|
|
2966
|
+
/* @__PURE__ */ jsx15("div", { className: "fc-account-footer", children: /* @__PURE__ */ jsxs14(
|
|
2967
|
+
"button",
|
|
2968
|
+
{
|
|
2969
|
+
type: "button",
|
|
2970
|
+
className: "fc-btn-logout",
|
|
2971
|
+
onClick: () => {
|
|
2972
|
+
logout();
|
|
2973
|
+
closeAccountModal();
|
|
2974
|
+
},
|
|
2975
|
+
children: [
|
|
2976
|
+
/* @__PURE__ */ jsx15("svg", { width: "14", height: "14", viewBox: "0 0 20 20", fill: "none", children: /* @__PURE__ */ jsx15("path", { d: "M7 17H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3M13 14l4-4-4-4M17 10H7", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) }),
|
|
2977
|
+
"Log out"
|
|
2978
|
+
]
|
|
2979
|
+
}
|
|
2980
|
+
) })
|
|
2981
|
+
]
|
|
2982
|
+
}
|
|
2983
|
+
) });
|
|
2984
|
+
}
|
|
2985
|
+
function ProfileTab() {
|
|
2986
|
+
const { user, updateProfile } = useUser();
|
|
2987
|
+
const [displayName, setDisplayName] = useState14(user?.displayName ?? "");
|
|
2988
|
+
const [avatarUrl, setAvatarUrl] = useState14(user?.avatarUrl ?? "");
|
|
2989
|
+
const [loading, setLoading] = useState14(false);
|
|
2990
|
+
const [msg, setMsg] = useState14(null);
|
|
2991
|
+
useEffect10(() => {
|
|
2992
|
+
setDisplayName(user?.displayName ?? "");
|
|
2993
|
+
setAvatarUrl(user?.avatarUrl ?? "");
|
|
2994
|
+
}, [user]);
|
|
2995
|
+
const handleSave = async () => {
|
|
2996
|
+
setLoading(true);
|
|
2997
|
+
setMsg(null);
|
|
2998
|
+
try {
|
|
2999
|
+
await updateProfile({
|
|
3000
|
+
displayName: displayName || void 0,
|
|
3001
|
+
avatarUrl: avatarUrl || void 0
|
|
3002
|
+
});
|
|
3003
|
+
setMsg({ text: "Profile updated", ok: true });
|
|
3004
|
+
} catch (err) {
|
|
3005
|
+
setMsg({ text: err instanceof Error ? err.message : "Something went wrong. Please try again.", ok: false });
|
|
3006
|
+
} finally {
|
|
3007
|
+
setLoading(false);
|
|
3008
|
+
}
|
|
3009
|
+
};
|
|
3010
|
+
return /* @__PURE__ */ jsx15("div", { className: "fc-tab", children: /* @__PURE__ */ jsxs14("div", { className: "fc-form", children: [
|
|
3011
|
+
/* @__PURE__ */ jsxs14("label", { className: "fc-label", children: [
|
|
3012
|
+
"Name",
|
|
3013
|
+
/* @__PURE__ */ jsx15(
|
|
3014
|
+
"input",
|
|
3015
|
+
{
|
|
3016
|
+
className: "fc-input",
|
|
3017
|
+
value: displayName,
|
|
3018
|
+
onChange: (e) => setDisplayName(e.target.value),
|
|
3019
|
+
placeholder: "How should we call you?"
|
|
3020
|
+
}
|
|
3021
|
+
)
|
|
3022
|
+
] }),
|
|
3023
|
+
/* @__PURE__ */ jsxs14("label", { className: "fc-label", children: [
|
|
3024
|
+
"Photo URL",
|
|
3025
|
+
/* @__PURE__ */ jsx15(
|
|
3026
|
+
"input",
|
|
3027
|
+
{
|
|
3028
|
+
className: "fc-input",
|
|
3029
|
+
value: avatarUrl,
|
|
3030
|
+
onChange: (e) => setAvatarUrl(e.target.value),
|
|
3031
|
+
placeholder: "Paste a link to your photo"
|
|
3032
|
+
}
|
|
3033
|
+
)
|
|
3034
|
+
] }),
|
|
3035
|
+
/* @__PURE__ */ jsx15("button", { type: "button", className: "fc-btn fc-btn-primary", onClick: handleSave, disabled: loading, children: loading ? "Saving..." : "Save changes" }),
|
|
3036
|
+
msg && /* @__PURE__ */ jsx15("p", { className: msg.ok ? "fc-text" : "fc-error", children: msg.text })
|
|
3037
|
+
] }) });
|
|
3038
|
+
}
|
|
3039
|
+
function LoginsTab({ onLink, refreshKey }) {
|
|
3040
|
+
const { authMethods, loading, fetchAuthMethods, unlinkAuthMethod } = useUser();
|
|
3041
|
+
const [msg, setMsg] = useState14("");
|
|
3042
|
+
useEffect10(() => {
|
|
3043
|
+
fetchAuthMethods().catch(() => {
|
|
3044
|
+
});
|
|
3045
|
+
}, [fetchAuthMethods, refreshKey]);
|
|
3046
|
+
const handleRemove = async (id) => {
|
|
3047
|
+
setMsg("");
|
|
3048
|
+
try {
|
|
3049
|
+
await unlinkAuthMethod(id);
|
|
3050
|
+
} catch (err) {
|
|
3051
|
+
setMsg(err instanceof Error ? err.message : "Could not remove this sign-in method. Please try again.");
|
|
3052
|
+
}
|
|
3053
|
+
};
|
|
3054
|
+
return /* @__PURE__ */ jsxs14("div", { className: "fc-tab", children: [
|
|
3055
|
+
msg && /* @__PURE__ */ jsx15("p", { className: "fc-error", children: msg }),
|
|
3056
|
+
loading && /* @__PURE__ */ jsx15("p", { className: "fc-text", children: "Loading..." }),
|
|
3057
|
+
/* @__PURE__ */ jsx15("p", { className: "fc-account-section-desc", children: "Ways you can sign in to your account." }),
|
|
3058
|
+
authMethods?.map((m) => {
|
|
3059
|
+
const display = getMethodDisplay(m.provider);
|
|
3060
|
+
const detail = m.provider === "email" ? m.providerId : m.provider.endsWith("_wallet") ? truncate(m.providerId) : m.providerId;
|
|
3061
|
+
return /* @__PURE__ */ jsxs14("div", { className: "fc-account-item", children: [
|
|
3062
|
+
display.iconHtml && /* @__PURE__ */ jsx15(SvgIcon, { svg: display.iconHtml, className: "fc-account-item-icon" }),
|
|
3063
|
+
/* @__PURE__ */ jsxs14("div", { className: "fc-account-item-info", children: [
|
|
3064
|
+
/* @__PURE__ */ jsx15("span", { className: "fc-account-item-label", children: display.label }),
|
|
3065
|
+
/* @__PURE__ */ jsx15("span", { className: "fc-account-item-detail", children: detail })
|
|
3066
|
+
] }),
|
|
3067
|
+
/* @__PURE__ */ jsxs14("div", { className: "fc-account-item-actions", children: [
|
|
3068
|
+
m.isVerified && /* @__PURE__ */ jsx15("span", { className: "fc-badge-verified", children: "Verified" }),
|
|
3069
|
+
/* @__PURE__ */ jsx15("button", { type: "button", className: "fc-btn-danger-sm", onClick: () => handleRemove(m.id), children: "Remove" })
|
|
3070
|
+
] })
|
|
3071
|
+
] }, m.id);
|
|
3072
|
+
}),
|
|
3073
|
+
authMethods?.length === 0 && /* @__PURE__ */ jsx15("p", { className: "fc-account-empty", children: "No login methods yet" }),
|
|
3074
|
+
/* @__PURE__ */ jsx15("button", { type: "button", className: "fc-btn fc-btn-secondary", onClick: onLink, style: { marginTop: 8 }, children: "+ Add login method" })
|
|
3075
|
+
] });
|
|
3076
|
+
}
|
|
3077
|
+
function WalletsTab({ onLink, refreshKey }) {
|
|
3078
|
+
const { wallets, loading, fetchWallets, updateWallet } = useWallets();
|
|
3079
|
+
const [msg, setMsg] = useState14("");
|
|
3080
|
+
useEffect10(() => {
|
|
3081
|
+
fetchWallets().catch(() => {
|
|
3082
|
+
});
|
|
3083
|
+
}, [fetchWallets, refreshKey]);
|
|
3084
|
+
const handleSetPrimary = async (id) => {
|
|
3085
|
+
setMsg("");
|
|
3086
|
+
try {
|
|
3087
|
+
await updateWallet(id, { isPrimary: true });
|
|
3088
|
+
} catch (err) {
|
|
3089
|
+
setMsg(err instanceof Error ? err.message : "Could not update this wallet. Please try again.");
|
|
3090
|
+
}
|
|
3091
|
+
};
|
|
3092
|
+
return /* @__PURE__ */ jsxs14("div", { className: "fc-tab", children: [
|
|
3093
|
+
msg && /* @__PURE__ */ jsx15("p", { className: "fc-error", children: msg }),
|
|
3094
|
+
loading && /* @__PURE__ */ jsx15("p", { className: "fc-text", children: "Loading..." }),
|
|
3095
|
+
/* @__PURE__ */ jsx15("p", { className: "fc-account-section-desc", children: "Crypto wallets connected to your account." }),
|
|
3096
|
+
wallets?.map((w) => /* @__PURE__ */ jsxs14("div", { className: "fc-account-item", children: [
|
|
3097
|
+
/* @__PURE__ */ jsx15(SvgIcon, { svg: METHOD_DISPLAY[`${w.chain}_wallet`]?.iconHtml ?? "", className: "fc-account-item-icon" }),
|
|
3098
|
+
/* @__PURE__ */ jsxs14("div", { className: "fc-account-item-info", children: [
|
|
3099
|
+
/* @__PURE__ */ jsxs14("span", { className: "fc-account-item-label", children: [
|
|
3100
|
+
w.chain.charAt(0).toUpperCase() + w.chain.slice(1),
|
|
3101
|
+
w.isPrimary && /* @__PURE__ */ jsx15("span", { className: "fc-badge-primary", style: { marginLeft: 6 }, children: "Primary" })
|
|
3102
|
+
] }),
|
|
3103
|
+
/* @__PURE__ */ jsx15("span", { className: "fc-account-item-detail", children: truncate(w.address) })
|
|
3104
|
+
] }),
|
|
3105
|
+
/* @__PURE__ */ jsx15("div", { className: "fc-account-item-actions", children: !w.isPrimary && /* @__PURE__ */ jsx15("button", { type: "button", className: "fc-btn-primary-sm", onClick: () => handleSetPrimary(w.id), children: "Make primary" }) })
|
|
3106
|
+
] }, w.id)),
|
|
3107
|
+
wallets?.length === 0 && /* @__PURE__ */ jsx15("p", { className: "fc-account-empty", children: "No wallets connected" }),
|
|
3108
|
+
/* @__PURE__ */ jsx15("button", { type: "button", className: "fc-btn fc-btn-secondary", onClick: onLink, style: { marginTop: 8 }, children: "+ Connect wallet" })
|
|
3109
|
+
] });
|
|
3110
|
+
}
|
|
3111
|
+
function SecurityTab() {
|
|
3112
|
+
const { sessions, loading, fetchSessions, revokeSession } = useSessions();
|
|
3113
|
+
const { logoutAll, logout, closeAccountModal, api, getAccessToken } = useForgeConnect();
|
|
3114
|
+
const { user, authMethods: userAuthMethods, fetchAuthMethods } = useUser();
|
|
3115
|
+
const [msg, setMsg] = useState14("");
|
|
3116
|
+
const [totpEnabled, setTotpEnabled] = useState14(null);
|
|
3117
|
+
const [passkeyCount, setPasskeyCount] = useState14(0);
|
|
3118
|
+
const [show2FAModal, setShow2FAModal] = useState14(false);
|
|
3119
|
+
const [showPasskeyModal, setShowPasskeyModal] = useState14(false);
|
|
3120
|
+
const [showPasswordModal, setShowPasswordModal] = useState14(false);
|
|
3121
|
+
const [showDeleteModal, setShowDeleteModal] = useState14(false);
|
|
3122
|
+
const refreshStatus = useCallback7(() => {
|
|
3123
|
+
const token = getAccessToken();
|
|
3124
|
+
if (!token) return;
|
|
3125
|
+
api.get2FAStatus(token).then((r) => setTotpEnabled(r.enabled)).catch(() => {
|
|
3126
|
+
});
|
|
3127
|
+
}, [api, getAccessToken]);
|
|
3128
|
+
const refreshPasskeyCount = useCallback7(() => {
|
|
3129
|
+
const token = getAccessToken();
|
|
3130
|
+
if (!token) return;
|
|
3131
|
+
api.getPasskeys(token).then((list) => setPasskeyCount(list.length)).catch(() => {
|
|
3132
|
+
});
|
|
3133
|
+
}, [api, getAccessToken]);
|
|
3134
|
+
useEffect10(() => {
|
|
3135
|
+
fetchSessions().catch(() => {
|
|
3136
|
+
});
|
|
3137
|
+
fetchAuthMethods().catch(() => {
|
|
3138
|
+
});
|
|
3139
|
+
refreshStatus();
|
|
3140
|
+
refreshPasskeyCount();
|
|
3141
|
+
}, [fetchSessions, fetchAuthMethods, refreshStatus, refreshPasskeyCount]);
|
|
3142
|
+
const hasPassword = userAuthMethods?.some((m) => m.provider === "email");
|
|
3143
|
+
const handleRevoke = async (id) => {
|
|
3144
|
+
setMsg("");
|
|
3145
|
+
try {
|
|
3146
|
+
await revokeSession(id);
|
|
3147
|
+
} catch (err) {
|
|
3148
|
+
setMsg(err instanceof Error ? err.message : "Could not sign out this session. Please try again.");
|
|
3149
|
+
}
|
|
3150
|
+
};
|
|
3151
|
+
return /* @__PURE__ */ jsxs14("div", { className: "fc-tab", children: [
|
|
3152
|
+
msg && /* @__PURE__ */ jsx15("p", { className: "fc-error", children: msg }),
|
|
3153
|
+
/* @__PURE__ */ jsxs14("button", { type: "button", className: "fc-security-card", onClick: () => setShow2FAModal(true), children: [
|
|
3154
|
+
/* @__PURE__ */ jsx15("span", { className: "fc-security-card-icon", children: /* @__PURE__ */ jsxs14("svg", { viewBox: "0 0 20 20", fill: "none", children: [
|
|
3155
|
+
/* @__PURE__ */ jsx15("path", { d: "M10 2l6 3v4c0 4.42-2.56 8.22-6 9.5C6.56 17.22 4 13.42 4 9V5l6-3z", stroke: "currentColor", strokeWidth: "1.5", strokeLinejoin: "round" }),
|
|
3156
|
+
/* @__PURE__ */ jsx15("path", { d: "M7.5 10.5l2 2 3.5-4", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" })
|
|
3157
|
+
] }) }),
|
|
3158
|
+
/* @__PURE__ */ jsxs14("span", { className: "fc-security-card-info", children: [
|
|
3159
|
+
/* @__PURE__ */ jsx15("span", { className: "fc-security-card-label", children: "Two-factor authentication" }),
|
|
3160
|
+
/* @__PURE__ */ jsx15("span", { className: "fc-security-card-detail", children: totpEnabled === null ? "Loading..." : totpEnabled ? "Enabled" : "Not enabled" })
|
|
3161
|
+
] }),
|
|
3162
|
+
/* @__PURE__ */ jsx15(ChevronRight, {})
|
|
3163
|
+
] }),
|
|
3164
|
+
/* @__PURE__ */ jsxs14("button", { type: "button", className: "fc-security-card", onClick: () => setShowPasskeyModal(true), children: [
|
|
3165
|
+
/* @__PURE__ */ jsx15("span", { className: "fc-security-card-icon", children: /* @__PURE__ */ jsxs14("svg", { viewBox: "0 0 20 20", fill: "none", children: [
|
|
3166
|
+
/* @__PURE__ */ jsx15("circle", { cx: "8", cy: "7", r: "3", stroke: "currentColor", strokeWidth: "1.5" }),
|
|
3167
|
+
/* @__PURE__ */ jsx15("path", { d: "M13 13.5a5 5 0 0 0-10 0", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" }),
|
|
3168
|
+
/* @__PURE__ */ jsx15("path", { d: "M15 10v4m0 0l-1.5-1m1.5 1l1.5-1", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" })
|
|
3169
|
+
] }) }),
|
|
3170
|
+
/* @__PURE__ */ jsxs14("span", { className: "fc-security-card-info", children: [
|
|
3171
|
+
/* @__PURE__ */ jsx15("span", { className: "fc-security-card-label", children: "Passkeys" }),
|
|
3172
|
+
/* @__PURE__ */ jsx15("span", { className: "fc-security-card-detail", children: passkeyCount === 0 ? "No passkeys" : `${passkeyCount} passkey${passkeyCount !== 1 ? "s" : ""}` })
|
|
3173
|
+
] }),
|
|
3174
|
+
/* @__PURE__ */ jsx15(ChevronRight, {})
|
|
3175
|
+
] }),
|
|
3176
|
+
user?.primaryEmail && /* @__PURE__ */ jsxs14(
|
|
3177
|
+
"button",
|
|
709
3178
|
{
|
|
710
3179
|
type: "button",
|
|
711
|
-
className: "fc-
|
|
712
|
-
onClick: () =>
|
|
3180
|
+
className: "fc-security-card",
|
|
3181
|
+
onClick: () => setShowPasswordModal(true),
|
|
713
3182
|
children: [
|
|
714
|
-
/* @__PURE__ */
|
|
715
|
-
/* @__PURE__ */
|
|
716
|
-
/* @__PURE__ */
|
|
3183
|
+
/* @__PURE__ */ jsx15("span", { className: "fc-security-card-icon", children: /* @__PURE__ */ jsxs14("svg", { viewBox: "0 0 20 20", fill: "none", children: [
|
|
3184
|
+
/* @__PURE__ */ jsx15("rect", { x: "3", y: "9", width: "14", height: "8", rx: "2", stroke: "currentColor", strokeWidth: "1.5" }),
|
|
3185
|
+
/* @__PURE__ */ jsx15("path", { d: "M6 9V6a4 4 0 1 1 8 0v3", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
|
|
3186
|
+
] }) }),
|
|
3187
|
+
/* @__PURE__ */ jsxs14("span", { className: "fc-security-card-info", children: [
|
|
3188
|
+
/* @__PURE__ */ jsx15("span", { className: "fc-security-card-label", children: "Password" }),
|
|
3189
|
+
/* @__PURE__ */ jsx15("span", { className: "fc-security-card-detail", children: hasPassword ? "Change your password" : "Set a password" })
|
|
717
3190
|
] }),
|
|
718
|
-
|
|
3191
|
+
/* @__PURE__ */ jsx15(ChevronRight, {})
|
|
719
3192
|
]
|
|
720
3193
|
}
|
|
721
3194
|
),
|
|
722
|
-
|
|
3195
|
+
/* @__PURE__ */ jsx15("div", { className: "fc-divider", style: { margin: "16px 0" }, children: /* @__PURE__ */ jsx15("span", { children: "active sessions" }) }),
|
|
3196
|
+
loading && /* @__PURE__ */ jsx15("p", { className: "fc-text", children: "Loading..." }),
|
|
3197
|
+
sessions?.map((s) => /* @__PURE__ */ jsxs14("div", { className: "fc-account-item", children: [
|
|
3198
|
+
/* @__PURE__ */ jsx15("span", { className: "fc-account-item-icon", children: /* @__PURE__ */ jsxs14("svg", { width: "16", height: "16", viewBox: "0 0 20 20", fill: "none", children: [
|
|
3199
|
+
/* @__PURE__ */ jsx15("rect", { x: "2", y: "3", width: "16", height: "11", rx: "2", stroke: "currentColor", strokeWidth: "1.5" }),
|
|
3200
|
+
/* @__PURE__ */ jsx15("path", { d: "M7 17h6M10 14v3", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
|
|
3201
|
+
] }) }),
|
|
3202
|
+
/* @__PURE__ */ jsxs14("div", { className: "fc-account-item-info", children: [
|
|
3203
|
+
/* @__PURE__ */ jsx15("span", { className: "fc-account-item-label", children: s.ipAddress === "127.0.0.1" || s.ipAddress === "::1" ? "This device" : truncate(s.ipAddress, 20) }),
|
|
3204
|
+
/* @__PURE__ */ jsxs14("span", { className: "fc-account-item-detail", children: [
|
|
3205
|
+
"Active ",
|
|
3206
|
+
timeAgo(s.lastActiveAt),
|
|
3207
|
+
" \xB7 Expires ",
|
|
3208
|
+
new Date(s.expiresAt).toLocaleDateString()
|
|
3209
|
+
] })
|
|
3210
|
+
] }),
|
|
3211
|
+
/* @__PURE__ */ jsx15("div", { className: "fc-account-item-actions", children: /* @__PURE__ */ jsx15("button", { type: "button", className: "fc-btn-danger-sm", onClick: () => handleRevoke(s.id), children: "Sign out" }) })
|
|
3212
|
+
] }, s.id)),
|
|
3213
|
+
sessions?.length === 0 && /* @__PURE__ */ jsx15("p", { className: "fc-account-empty", children: "No active sessions" }),
|
|
3214
|
+
sessions && sessions.length > 1 && /* @__PURE__ */ jsx15(
|
|
723
3215
|
"button",
|
|
724
3216
|
{
|
|
725
3217
|
type: "button",
|
|
726
|
-
className: "fc-btn fc-btn-
|
|
727
|
-
onClick: () =>
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
/* @__PURE__ */ jsx6("circle", { cx: "10", cy: "10.5", r: "1", fill: "currentColor" }),
|
|
733
|
-
/* @__PURE__ */ jsx6("circle", { cx: "13.5", cy: "10.5", r: "1", fill: "currentColor" })
|
|
734
|
-
] }),
|
|
735
|
-
"Continue with code"
|
|
736
|
-
]
|
|
3218
|
+
className: "fc-btn fc-btn-secondary",
|
|
3219
|
+
onClick: () => {
|
|
3220
|
+
logoutAll();
|
|
3221
|
+
},
|
|
3222
|
+
style: { marginTop: 8 },
|
|
3223
|
+
children: "Sign out of all devices"
|
|
737
3224
|
}
|
|
738
3225
|
),
|
|
739
|
-
|
|
3226
|
+
/* @__PURE__ */ jsx15("div", { className: "fc-divider", style: { margin: "16px 0" }, children: /* @__PURE__ */ jsx15("span", { children: "danger zone" }) }),
|
|
3227
|
+
/* @__PURE__ */ jsx15(
|
|
3228
|
+
"button",
|
|
3229
|
+
{
|
|
3230
|
+
type: "button",
|
|
3231
|
+
className: "fc-btn fc-btn-secondary",
|
|
3232
|
+
onClick: () => setShowDeleteModal(true),
|
|
3233
|
+
style: { background: "rgba(220, 38, 38, 0.1)", color: "#dc2626", borderColor: "rgba(220, 38, 38, 0.2)" },
|
|
3234
|
+
children: "Delete account"
|
|
3235
|
+
}
|
|
3236
|
+
),
|
|
3237
|
+
/* @__PURE__ */ jsx15(
|
|
3238
|
+
TwoFactorModal,
|
|
3239
|
+
{
|
|
3240
|
+
isOpen: show2FAModal,
|
|
3241
|
+
onClose: () => {
|
|
3242
|
+
setShow2FAModal(false);
|
|
3243
|
+
refreshStatus();
|
|
3244
|
+
},
|
|
3245
|
+
initialEnabled: totpEnabled ?? false,
|
|
3246
|
+
onStatusChange: (e) => setTotpEnabled(e)
|
|
3247
|
+
}
|
|
3248
|
+
),
|
|
3249
|
+
/* @__PURE__ */ jsx15(
|
|
3250
|
+
PasskeysModal,
|
|
3251
|
+
{
|
|
3252
|
+
isOpen: showPasskeyModal,
|
|
3253
|
+
onClose: () => {
|
|
3254
|
+
setShowPasskeyModal(false);
|
|
3255
|
+
refreshPasskeyCount();
|
|
3256
|
+
},
|
|
3257
|
+
onCountChange: (count) => setPasskeyCount(count)
|
|
3258
|
+
}
|
|
3259
|
+
),
|
|
3260
|
+
user?.primaryEmail && /* @__PURE__ */ jsx15(
|
|
3261
|
+
PasswordModal,
|
|
3262
|
+
{
|
|
3263
|
+
isOpen: showPasswordModal,
|
|
3264
|
+
onClose: () => {
|
|
3265
|
+
setShowPasswordModal(false);
|
|
3266
|
+
fetchAuthMethods().catch(() => {
|
|
3267
|
+
});
|
|
3268
|
+
},
|
|
3269
|
+
hasPassword: hasPassword ?? false
|
|
3270
|
+
}
|
|
3271
|
+
),
|
|
3272
|
+
/* @__PURE__ */ jsx15(
|
|
3273
|
+
DeleteAccountModal,
|
|
3274
|
+
{
|
|
3275
|
+
isOpen: showDeleteModal,
|
|
3276
|
+
onClose: () => setShowDeleteModal(false),
|
|
3277
|
+
onDeleted: () => {
|
|
3278
|
+
logout();
|
|
3279
|
+
closeAccountModal();
|
|
3280
|
+
}
|
|
3281
|
+
}
|
|
3282
|
+
)
|
|
3283
|
+
] });
|
|
3284
|
+
}
|
|
3285
|
+
|
|
3286
|
+
// src/components/link-auth-modal.tsx
|
|
3287
|
+
import { useState as useState15, useEffect as useEffect11, useMemo as useMemo2 } from "react";
|
|
3288
|
+
import { Fragment as Fragment6, jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
3289
|
+
function LinkAuthModal() {
|
|
3290
|
+
const { linkModal, closeLinkModal, config } = useForgeConnect();
|
|
3291
|
+
const { linkOAuth, authMethods } = useUser();
|
|
3292
|
+
const mode = linkModal.mode ?? "auth";
|
|
3293
|
+
const [step, setStep] = useState15("method-select");
|
|
3294
|
+
const [fatalError, setFatalError] = useState15("");
|
|
3295
|
+
useEffect11(() => {
|
|
3296
|
+
if (linkModal.isOpen) {
|
|
3297
|
+
setStep(mode === "wallet" ? "wallet" : "method-select");
|
|
3298
|
+
setFatalError("");
|
|
3299
|
+
}
|
|
3300
|
+
}, [linkModal.isOpen, mode]);
|
|
3301
|
+
useEffect11(() => {
|
|
3302
|
+
if (!linkModal.isOpen) return;
|
|
3303
|
+
const handleMessage = (event) => {
|
|
3304
|
+
if (event.origin !== window.location.origin) return;
|
|
3305
|
+
if (event.data?.type === "fc_oauth_link_success") {
|
|
3306
|
+
setStep("success");
|
|
3307
|
+
setTimeout(() => {
|
|
3308
|
+
handleClose();
|
|
3309
|
+
}, 1500);
|
|
3310
|
+
}
|
|
3311
|
+
};
|
|
3312
|
+
window.addEventListener("message", handleMessage);
|
|
3313
|
+
return () => window.removeEventListener("message", handleMessage);
|
|
3314
|
+
}, [linkModal.isOpen]);
|
|
3315
|
+
if (!linkModal.isOpen) return null;
|
|
3316
|
+
const theme = config.appearance?.theme ?? "light";
|
|
3317
|
+
const handleClose = () => {
|
|
3318
|
+
setStep("method-select");
|
|
3319
|
+
closeLinkModal();
|
|
3320
|
+
};
|
|
3321
|
+
const handleSuccess = () => {
|
|
3322
|
+
setStep("success");
|
|
3323
|
+
setTimeout(() => {
|
|
3324
|
+
handleClose();
|
|
3325
|
+
}, 1500);
|
|
3326
|
+
};
|
|
3327
|
+
const handleOAuthDirect = (provider) => {
|
|
3328
|
+
linkOAuth(provider);
|
|
3329
|
+
};
|
|
3330
|
+
const handleFatalError = (msg) => {
|
|
3331
|
+
setFatalError(msg);
|
|
3332
|
+
setStep("error");
|
|
3333
|
+
};
|
|
3334
|
+
const title = step === "success" || step === "error" ? "" : step === "email" ? "Link email" : step === "otp" ? "Link email" : mode === "wallet" || step === "wallet" ? "Connect wallet" : "Link a method";
|
|
3335
|
+
return /* @__PURE__ */ jsx16(ModalOverlay, { isOpen: linkModal.isOpen, onClose: handleClose, children: /* @__PURE__ */ jsx16(
|
|
3336
|
+
"div",
|
|
3337
|
+
{
|
|
3338
|
+
className: "fc-modal-content",
|
|
3339
|
+
style: { "--fc-accent": config.appearance?.accentColor ?? "#8b5cf6" },
|
|
3340
|
+
"data-theme": theme,
|
|
3341
|
+
children: step === "success" ? /* @__PURE__ */ jsx16(SuccessView2, {}) : step === "error" ? /* @__PURE__ */ jsx16(
|
|
3342
|
+
ErrorView,
|
|
3343
|
+
{
|
|
3344
|
+
title: "Linking failed",
|
|
3345
|
+
message: fatalError,
|
|
3346
|
+
onRetry: () => setStep(mode === "wallet" ? "wallet" : "method-select"),
|
|
3347
|
+
onClose: handleClose
|
|
3348
|
+
}
|
|
3349
|
+
) : /* @__PURE__ */ jsxs15(Fragment6, { children: [
|
|
3350
|
+
/* @__PURE__ */ jsx16("h2", { className: "fc-modal-title", children: title }),
|
|
3351
|
+
step === "method-select" && /* @__PURE__ */ jsx16(
|
|
3352
|
+
AuthMethodSelectStep,
|
|
3353
|
+
{
|
|
3354
|
+
config,
|
|
3355
|
+
connectedProviders: authMethods?.map((m) => m.provider) ?? [],
|
|
3356
|
+
onSelectEmail: () => setStep("email"),
|
|
3357
|
+
onSelectOtp: () => setStep("otp"),
|
|
3358
|
+
onOAuth: handleOAuthDirect
|
|
3359
|
+
}
|
|
3360
|
+
),
|
|
3361
|
+
step === "email" && /* @__PURE__ */ jsx16(
|
|
3362
|
+
EmailLinkStep,
|
|
3363
|
+
{
|
|
3364
|
+
onBack: mode === "wallet" ? void 0 : () => setStep("method-select"),
|
|
3365
|
+
onSuccess: handleSuccess,
|
|
3366
|
+
onFatalError: handleFatalError
|
|
3367
|
+
}
|
|
3368
|
+
),
|
|
3369
|
+
step === "otp" && /* @__PURE__ */ jsx16(
|
|
3370
|
+
OtpLinkStep,
|
|
3371
|
+
{
|
|
3372
|
+
onBack: () => setStep("method-select"),
|
|
3373
|
+
onSuccess: handleSuccess,
|
|
3374
|
+
onFatalError: handleFatalError
|
|
3375
|
+
}
|
|
3376
|
+
),
|
|
3377
|
+
step === "wallet" && /* @__PURE__ */ jsx16(
|
|
3378
|
+
WalletLinkStep,
|
|
3379
|
+
{
|
|
3380
|
+
onBack: mode === "wallet" ? void 0 : () => setStep("method-select"),
|
|
3381
|
+
onSuccess: handleSuccess,
|
|
3382
|
+
onFatalError: handleFatalError
|
|
3383
|
+
}
|
|
3384
|
+
)
|
|
3385
|
+
] })
|
|
3386
|
+
}
|
|
3387
|
+
) });
|
|
3388
|
+
}
|
|
3389
|
+
var EmailIcon2 = () => /* @__PURE__ */ jsx16("div", { className: "fc-method-icon-wrap", children: /* @__PURE__ */ jsxs15("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", className: "fc-method-icon", children: [
|
|
3390
|
+
/* @__PURE__ */ jsx16("rect", { x: "2", y: "4", width: "16", height: "12", rx: "2", stroke: "currentColor", strokeWidth: "1.5" }),
|
|
3391
|
+
/* @__PURE__ */ jsx16("path", { d: "M2 6l8 5 8-5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
|
|
3392
|
+
] }) });
|
|
3393
|
+
var MagicCodeIcon = () => /* @__PURE__ */ jsx16("div", { className: "fc-method-icon-wrap", children: /* @__PURE__ */ jsxs15("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", className: "fc-method-icon", children: [
|
|
3394
|
+
/* @__PURE__ */ jsx16("rect", { x: "3", y: "5", width: "14", height: "10", rx: "2", stroke: "currentColor", strokeWidth: "1.5" }),
|
|
3395
|
+
/* @__PURE__ */ jsx16("path", { d: "M7 10h1.5M9.5 10h1M11.5 10h1.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
|
|
3396
|
+
] }) });
|
|
3397
|
+
function AuthMethodSelectStep({
|
|
3398
|
+
config,
|
|
3399
|
+
connectedProviders,
|
|
3400
|
+
onSelectEmail,
|
|
3401
|
+
onSelectOtp,
|
|
3402
|
+
onOAuth
|
|
3403
|
+
}) {
|
|
3404
|
+
const methods = resolveLoginMethods(config);
|
|
3405
|
+
const connectedSet = new Set(connectedProviders);
|
|
3406
|
+
const hasEmail = connectedSet.has("email");
|
|
3407
|
+
const availableMethods = methods.filter((m) => {
|
|
3408
|
+
if (m === "wallet" || m === "passkey") return false;
|
|
3409
|
+
if (m === "email") return !hasEmail;
|
|
3410
|
+
if (m === "otp") return !hasEmail;
|
|
3411
|
+
if (isOAuthMethod(m)) return !connectedSet.has(m);
|
|
3412
|
+
return false;
|
|
3413
|
+
});
|
|
3414
|
+
if (availableMethods.length === 0) {
|
|
3415
|
+
return /* @__PURE__ */ jsx16("div", { className: "fc-tab", children: /* @__PURE__ */ jsx16("p", { className: "fc-text", children: "All available login methods are already connected." }) });
|
|
3416
|
+
}
|
|
3417
|
+
return /* @__PURE__ */ jsx16("div", { className: "fc-tab", children: availableMethods.map((method) => {
|
|
3418
|
+
if (method === "email") {
|
|
3419
|
+
return /* @__PURE__ */ jsxs15("button", { type: "button", className: "fc-btn fc-btn-method", onClick: onSelectEmail, children: [
|
|
3420
|
+
/* @__PURE__ */ jsx16(EmailIcon2, {}),
|
|
3421
|
+
" ",
|
|
3422
|
+
/* @__PURE__ */ jsx16("span", { className: "fc-btn-name", children: "Email & password" })
|
|
3423
|
+
] }, "email");
|
|
3424
|
+
}
|
|
3425
|
+
if (method === "otp") {
|
|
3426
|
+
return /* @__PURE__ */ jsxs15("button", { type: "button", className: "fc-btn fc-btn-method", onClick: onSelectOtp, children: [
|
|
3427
|
+
/* @__PURE__ */ jsx16(MagicCodeIcon, {}),
|
|
3428
|
+
" ",
|
|
3429
|
+
/* @__PURE__ */ jsx16("span", { className: "fc-btn-name", children: "Magic code" })
|
|
3430
|
+
] }, "otp");
|
|
3431
|
+
}
|
|
3432
|
+
const provider = method;
|
|
3433
|
+
const info = PROVIDER_INFO[provider];
|
|
3434
|
+
if (!info) return null;
|
|
3435
|
+
return /* @__PURE__ */ jsxs15(
|
|
740
3436
|
"button",
|
|
741
3437
|
{
|
|
742
3438
|
type: "button",
|
|
743
3439
|
className: "fc-btn fc-btn-method",
|
|
744
|
-
onClick: () =>
|
|
3440
|
+
onClick: () => onOAuth(provider),
|
|
745
3441
|
children: [
|
|
746
|
-
/* @__PURE__ */
|
|
747
|
-
|
|
748
|
-
/* @__PURE__ */ jsx6("rect", { x: "13", y: "9", width: "5", height: "3", rx: "1", stroke: "currentColor", strokeWidth: "1.5" })
|
|
749
|
-
] }),
|
|
750
|
-
"Continue with wallet"
|
|
3442
|
+
/* @__PURE__ */ jsx16(SvgIcon, { svg: info.icon, className: "fc-oauth-icon" }),
|
|
3443
|
+
/* @__PURE__ */ jsx16("span", { className: "fc-btn-name", children: info.label })
|
|
751
3444
|
]
|
|
3445
|
+
},
|
|
3446
|
+
provider
|
|
3447
|
+
);
|
|
3448
|
+
}) });
|
|
3449
|
+
}
|
|
3450
|
+
function EmailLinkStep({ onBack, onSuccess, onFatalError }) {
|
|
3451
|
+
const { linkAuthMethod } = useUser();
|
|
3452
|
+
const [email, setEmail] = useState15("");
|
|
3453
|
+
const [password, setPassword] = useState15("");
|
|
3454
|
+
const [loading, setLoading] = useState15(false);
|
|
3455
|
+
const [error, setError] = useState15("");
|
|
3456
|
+
const handleSubmit = async (e) => {
|
|
3457
|
+
e.preventDefault();
|
|
3458
|
+
setLoading(true);
|
|
3459
|
+
setError("");
|
|
3460
|
+
try {
|
|
3461
|
+
await linkAuthMethod({ provider: "email", email, password });
|
|
3462
|
+
onSuccess();
|
|
3463
|
+
} catch (err) {
|
|
3464
|
+
onFatalError(err instanceof Error ? err.message : "Could not link this email. Please try again.");
|
|
3465
|
+
} finally {
|
|
3466
|
+
setLoading(false);
|
|
3467
|
+
}
|
|
3468
|
+
};
|
|
3469
|
+
return /* @__PURE__ */ jsxs15("div", { className: "fc-tab", children: [
|
|
3470
|
+
error && /* @__PURE__ */ jsx16("p", { className: "fc-error", children: error }),
|
|
3471
|
+
/* @__PURE__ */ jsxs15("form", { className: "fc-form", onSubmit: handleSubmit, children: [
|
|
3472
|
+
/* @__PURE__ */ jsxs15("label", { className: "fc-label", children: [
|
|
3473
|
+
"Email",
|
|
3474
|
+
/* @__PURE__ */ jsx16("input", { className: "fc-input", type: "email", value: email, onChange: (e) => setEmail(e.target.value), required: true, placeholder: "you@example.com" })
|
|
3475
|
+
] }),
|
|
3476
|
+
/* @__PURE__ */ jsxs15("label", { className: "fc-label", children: [
|
|
3477
|
+
"Password",
|
|
3478
|
+
/* @__PURE__ */ jsx16("input", { className: "fc-input", type: "password", value: password, onChange: (e) => setPassword(e.target.value), required: true, minLength: 8, placeholder: "8+ characters" })
|
|
3479
|
+
] }),
|
|
3480
|
+
/* @__PURE__ */ jsx16("button", { type: "submit", className: "fc-btn fc-btn-primary", disabled: loading, children: loading ? "Linking..." : "Link email" })
|
|
3481
|
+
] }),
|
|
3482
|
+
onBack && /* @__PURE__ */ jsx16("div", { className: "fc-switch", children: /* @__PURE__ */ jsx16("button", { type: "button", className: "fc-link", onClick: onBack, children: "Back" }) })
|
|
3483
|
+
] });
|
|
3484
|
+
}
|
|
3485
|
+
function OtpLinkStep({ onBack, onSuccess, onFatalError }) {
|
|
3486
|
+
const { linkOtpSend, linkOtpVerify } = useUser();
|
|
3487
|
+
const [email, setEmail] = useState15("");
|
|
3488
|
+
const [code, setCode] = useState15("");
|
|
3489
|
+
const [codeSent, setCodeSent] = useState15(false);
|
|
3490
|
+
const [loading, setLoading] = useState15(false);
|
|
3491
|
+
const [error, setError] = useState15("");
|
|
3492
|
+
const handleSendCode = async (e) => {
|
|
3493
|
+
e.preventDefault();
|
|
3494
|
+
setLoading(true);
|
|
3495
|
+
setError("");
|
|
3496
|
+
try {
|
|
3497
|
+
await linkOtpSend(email);
|
|
3498
|
+
setCodeSent(true);
|
|
3499
|
+
} catch (err) {
|
|
3500
|
+
setError(err instanceof Error ? err.message : "Could not send code. Please try again.");
|
|
3501
|
+
} finally {
|
|
3502
|
+
setLoading(false);
|
|
3503
|
+
}
|
|
3504
|
+
};
|
|
3505
|
+
const handleVerifyCode = async (e) => {
|
|
3506
|
+
e.preventDefault();
|
|
3507
|
+
setLoading(true);
|
|
3508
|
+
setError("");
|
|
3509
|
+
try {
|
|
3510
|
+
await linkOtpVerify(email, code);
|
|
3511
|
+
onSuccess();
|
|
3512
|
+
} catch (err) {
|
|
3513
|
+
onFatalError(err instanceof Error ? err.message : "Invalid code. Please try again.");
|
|
3514
|
+
} finally {
|
|
3515
|
+
setLoading(false);
|
|
3516
|
+
}
|
|
3517
|
+
};
|
|
3518
|
+
if (!codeSent) {
|
|
3519
|
+
return /* @__PURE__ */ jsxs15("div", { className: "fc-tab", children: [
|
|
3520
|
+
error && /* @__PURE__ */ jsx16("p", { className: "fc-error", children: error }),
|
|
3521
|
+
/* @__PURE__ */ jsxs15("form", { className: "fc-form", onSubmit: handleSendCode, children: [
|
|
3522
|
+
/* @__PURE__ */ jsxs15("label", { className: "fc-label", children: [
|
|
3523
|
+
"Email",
|
|
3524
|
+
/* @__PURE__ */ jsx16("input", { className: "fc-input", type: "email", value: email, onChange: (e) => setEmail(e.target.value), required: true, placeholder: "you@example.com" })
|
|
3525
|
+
] }),
|
|
3526
|
+
/* @__PURE__ */ jsx16("button", { type: "submit", className: "fc-btn fc-btn-primary", disabled: loading, children: loading ? "Sending..." : "Send code" })
|
|
3527
|
+
] }),
|
|
3528
|
+
onBack && /* @__PURE__ */ jsx16("div", { className: "fc-switch", children: /* @__PURE__ */ jsx16("button", { type: "button", className: "fc-link", onClick: onBack, children: "Back" }) })
|
|
3529
|
+
] });
|
|
3530
|
+
}
|
|
3531
|
+
return /* @__PURE__ */ jsxs15("div", { className: "fc-tab", children: [
|
|
3532
|
+
/* @__PURE__ */ jsxs15("p", { className: "fc-text", children: [
|
|
3533
|
+
"A 6-digit code was sent to ",
|
|
3534
|
+
/* @__PURE__ */ jsx16("strong", { children: email })
|
|
3535
|
+
] }),
|
|
3536
|
+
error && /* @__PURE__ */ jsx16("p", { className: "fc-error", children: error }),
|
|
3537
|
+
/* @__PURE__ */ jsxs15("form", { className: "fc-form", onSubmit: handleVerifyCode, children: [
|
|
3538
|
+
/* @__PURE__ */ jsxs15("label", { className: "fc-label", children: [
|
|
3539
|
+
"Code",
|
|
3540
|
+
/* @__PURE__ */ jsx16(
|
|
3541
|
+
"input",
|
|
3542
|
+
{
|
|
3543
|
+
className: "fc-input",
|
|
3544
|
+
type: "text",
|
|
3545
|
+
inputMode: "numeric",
|
|
3546
|
+
pattern: "[0-9]{6}",
|
|
3547
|
+
maxLength: 6,
|
|
3548
|
+
value: code,
|
|
3549
|
+
onChange: (e) => setCode(e.target.value.replace(/\D/g, "").slice(0, 6)),
|
|
3550
|
+
required: true,
|
|
3551
|
+
placeholder: "000000",
|
|
3552
|
+
autoFocus: true
|
|
3553
|
+
}
|
|
3554
|
+
)
|
|
3555
|
+
] }),
|
|
3556
|
+
/* @__PURE__ */ jsx16("button", { type: "submit", className: "fc-btn fc-btn-primary", disabled: loading || code.length !== 6, children: loading ? "Verifying..." : "Verify & link" })
|
|
3557
|
+
] }),
|
|
3558
|
+
/* @__PURE__ */ jsx16("div", { className: "fc-switch", children: /* @__PURE__ */ jsx16("button", { type: "button", className: "fc-link", onClick: () => {
|
|
3559
|
+
setCodeSent(false);
|
|
3560
|
+
setCode("");
|
|
3561
|
+
setError("");
|
|
3562
|
+
}, children: "Use a different email" }) })
|
|
3563
|
+
] });
|
|
3564
|
+
}
|
|
3565
|
+
function WalletLinkStep({ onBack, onSuccess, onFatalError }) {
|
|
3566
|
+
const { walletAdapter, config } = useForgeConnect();
|
|
3567
|
+
const { linkWallet } = useWallets();
|
|
3568
|
+
const [loading, setLoading] = useState15(false);
|
|
3569
|
+
const [error, setError] = useState15("");
|
|
3570
|
+
const [showOther, setShowOther] = useState15(false);
|
|
3571
|
+
const mobile = useMemo2(() => isMobile(), []);
|
|
3572
|
+
const walletConfig = config.walletConfig;
|
|
3573
|
+
const preferred = walletConfig?.preferredWallets ?? [];
|
|
3574
|
+
const onlyPreferred = walletConfig?.onlyPreferred ?? false;
|
|
3575
|
+
const handleConnect = async (w) => {
|
|
3576
|
+
if (w.readyState !== "Installed") {
|
|
3577
|
+
if (mobile) {
|
|
3578
|
+
const mw = MOBILE_WALLETS.find((m) => m.name === w.adapter.name);
|
|
3579
|
+
if (mw) {
|
|
3580
|
+
window.location.href = mw.buildUrl(window.location.href);
|
|
3581
|
+
return;
|
|
3582
|
+
}
|
|
752
3583
|
}
|
|
753
|
-
|
|
3584
|
+
const url = w.adapter.url;
|
|
3585
|
+
if (url) window.open(url, "_blank");
|
|
3586
|
+
return;
|
|
3587
|
+
}
|
|
3588
|
+
setError("");
|
|
3589
|
+
setLoading(true);
|
|
3590
|
+
try {
|
|
3591
|
+
if (!w.adapter.connected) await w.adapter.connect();
|
|
3592
|
+
const pk = w.adapter.publicKey;
|
|
3593
|
+
if (!pk) throw new Error("Wallet did not provide a public key.");
|
|
3594
|
+
const adapterSignMessage = w.adapter.signMessage ? (msg) => w.adapter.signMessage(msg) : void 0;
|
|
3595
|
+
if (!adapterSignMessage) throw new Error("This wallet does not support message signing.");
|
|
3596
|
+
await linkWallet(pk.toBase58(), adapterSignMessage, "solana");
|
|
3597
|
+
onSuccess();
|
|
3598
|
+
} catch (err) {
|
|
3599
|
+
onFatalError(err instanceof Error ? err.message : "Could not link this wallet. Please try again.");
|
|
3600
|
+
}
|
|
3601
|
+
};
|
|
3602
|
+
const { preferredWallets, otherWallets } = useMemo2(() => {
|
|
3603
|
+
if (!walletAdapter) return { preferredWallets: [], otherWallets: [] };
|
|
3604
|
+
const all = walletAdapter.wallets;
|
|
3605
|
+
const prefList = preferred.map((name) => all.find((w) => w.adapter.name === name)).filter(Boolean);
|
|
3606
|
+
if (onlyPreferred && preferred.length > 0) return { preferredWallets: prefList, otherWallets: [] };
|
|
3607
|
+
const prefSet = new Set(preferred);
|
|
3608
|
+
const others = all.filter((w) => !prefSet.has(w.adapter.name) && w.readyState === "Installed");
|
|
3609
|
+
return { preferredWallets: prefList, otherWallets: others };
|
|
3610
|
+
}, [walletAdapter?.wallets, walletConfig]);
|
|
3611
|
+
const mobileExtraWallets = useMemo2(() => {
|
|
3612
|
+
if (!mobile || !walletAdapter) return [];
|
|
3613
|
+
const adapterNames = new Set(walletAdapter.wallets.map((w) => w.adapter.name));
|
|
3614
|
+
return MOBILE_WALLETS.filter((mw) => !adapterNames.has(mw.name));
|
|
3615
|
+
}, [mobile, walletAdapter?.wallets]);
|
|
3616
|
+
if (!walletAdapter && mobile) {
|
|
3617
|
+
return /* @__PURE__ */ jsxs15("div", { className: "fc-tab", children: [
|
|
3618
|
+
/* @__PURE__ */ jsx16("div", { className: "fc-wallet-list", children: MOBILE_WALLETS.map((mw) => /* @__PURE__ */ jsxs15(
|
|
3619
|
+
"button",
|
|
3620
|
+
{
|
|
3621
|
+
type: "button",
|
|
3622
|
+
className: "fc-btn fc-btn-wallet",
|
|
3623
|
+
onClick: () => {
|
|
3624
|
+
window.location.href = mw.buildUrl(window.location.href);
|
|
3625
|
+
},
|
|
3626
|
+
children: [
|
|
3627
|
+
/* @__PURE__ */ jsx16("span", { style: { position: "relative", display: "inline-flex" }, children: /* @__PURE__ */ jsx16("img", { src: mw.icon, alt: "", className: "fc-wallet-icon" }) }),
|
|
3628
|
+
/* @__PURE__ */ jsx16("span", { className: "fc-wallet-name", children: mw.name }),
|
|
3629
|
+
/* @__PURE__ */ jsx16("span", { className: "fc-badge-preferred", children: "Open app" })
|
|
3630
|
+
]
|
|
3631
|
+
},
|
|
3632
|
+
mw.name
|
|
3633
|
+
)) }),
|
|
3634
|
+
onBack && /* @__PURE__ */ jsx16("div", { className: "fc-switch", children: /* @__PURE__ */ jsx16("button", { type: "button", className: "fc-link", onClick: onBack, children: "Back" }) })
|
|
3635
|
+
] });
|
|
3636
|
+
}
|
|
3637
|
+
if (!walletAdapter) {
|
|
3638
|
+
return /* @__PURE__ */ jsxs15("div", { className: "fc-tab", children: [
|
|
3639
|
+
/* @__PURE__ */ jsxs15("p", { className: "fc-text", children: [
|
|
3640
|
+
"No wallet adapter detected. Pass ",
|
|
3641
|
+
/* @__PURE__ */ jsx16("code", { children: "walletAdapter" }),
|
|
3642
|
+
" to",
|
|
3643
|
+
" ",
|
|
3644
|
+
/* @__PURE__ */ jsx16("code", { children: "<ForgeConnectProvider>" }),
|
|
3645
|
+
" to enable wallet connection."
|
|
3646
|
+
] }),
|
|
3647
|
+
onBack && /* @__PURE__ */ jsx16("div", { className: "fc-switch", children: /* @__PURE__ */ jsx16("button", { type: "button", className: "fc-link", onClick: onBack, children: "Back" }) })
|
|
3648
|
+
] });
|
|
3649
|
+
}
|
|
3650
|
+
return /* @__PURE__ */ jsxs15("div", { className: "fc-tab", children: [
|
|
3651
|
+
loading ? /* @__PURE__ */ jsxs15("div", { style: { textAlign: "center", padding: "24px 0" }, children: [
|
|
3652
|
+
/* @__PURE__ */ jsx16("p", { className: "fc-tab-title", children: "Connecting..." }),
|
|
3653
|
+
/* @__PURE__ */ jsx16("p", { className: "fc-text", children: "Approve the connection, then sign the verification request in your wallet" }),
|
|
3654
|
+
error && /* @__PURE__ */ jsxs15(Fragment6, { children: [
|
|
3655
|
+
/* @__PURE__ */ jsx16("p", { className: "fc-error", children: error }),
|
|
3656
|
+
/* @__PURE__ */ jsx16(
|
|
3657
|
+
"button",
|
|
3658
|
+
{
|
|
3659
|
+
type: "button",
|
|
3660
|
+
className: "fc-btn fc-btn-secondary",
|
|
3661
|
+
onClick: () => {
|
|
3662
|
+
setLoading(false);
|
|
3663
|
+
setError("");
|
|
3664
|
+
},
|
|
3665
|
+
style: { marginTop: 8 },
|
|
3666
|
+
children: "Try again"
|
|
3667
|
+
}
|
|
3668
|
+
)
|
|
3669
|
+
] })
|
|
3670
|
+
] }) : /* @__PURE__ */ jsxs15(Fragment6, { children: [
|
|
3671
|
+
/* @__PURE__ */ jsxs15("div", { className: "fc-wallet-list", children: [
|
|
3672
|
+
preferredWallets.map((w) => {
|
|
3673
|
+
const installed = w.readyState === "Installed";
|
|
3674
|
+
return /* @__PURE__ */ jsxs15("button", { type: "button", className: "fc-btn fc-btn-wallet", onClick: () => handleConnect(w), children: [
|
|
3675
|
+
/* @__PURE__ */ jsx16("span", { className: installed ? "fc-installed-dot" : "", style: { position: "relative", display: "inline-flex" }, children: /* @__PURE__ */ jsx16("img", { src: w.adapter.icon, alt: "", className: "fc-wallet-icon" }) }),
|
|
3676
|
+
/* @__PURE__ */ jsx16("span", { className: "fc-wallet-name", children: w.adapter.name }),
|
|
3677
|
+
/* @__PURE__ */ jsx16("span", { className: "fc-badge-preferred", children: "Preferred" }),
|
|
3678
|
+
!installed && mobile && MOBILE_WALLETS.some((mw) => mw.name === w.adapter.name) ? /* @__PURE__ */ jsx16("span", { className: "fc-badge-install", children: "Open app" }) : !installed && /* @__PURE__ */ jsx16("span", { className: "fc-badge-install", children: "Install" })
|
|
3679
|
+
] }, w.adapter.name);
|
|
3680
|
+
}),
|
|
3681
|
+
mobileExtraWallets.map((mw) => /* @__PURE__ */ jsxs15(
|
|
3682
|
+
"button",
|
|
3683
|
+
{
|
|
3684
|
+
type: "button",
|
|
3685
|
+
className: "fc-btn fc-btn-wallet",
|
|
3686
|
+
onClick: () => {
|
|
3687
|
+
window.location.href = mw.buildUrl(window.location.href);
|
|
3688
|
+
},
|
|
3689
|
+
children: [
|
|
3690
|
+
/* @__PURE__ */ jsx16("span", { style: { position: "relative", display: "inline-flex" }, children: /* @__PURE__ */ jsx16("img", { src: mw.icon, alt: "", className: "fc-wallet-icon" }) }),
|
|
3691
|
+
/* @__PURE__ */ jsx16("span", { className: "fc-wallet-name", children: mw.name }),
|
|
3692
|
+
/* @__PURE__ */ jsx16("span", { className: "fc-badge-install", children: "Open app" })
|
|
3693
|
+
]
|
|
3694
|
+
},
|
|
3695
|
+
mw.name
|
|
3696
|
+
)),
|
|
3697
|
+
otherWallets.length > 0 && !showOther && /* @__PURE__ */ jsxs15("button", { type: "button", className: "fc-btn fc-btn-wallet", onClick: () => setShowOther(true), children: [
|
|
3698
|
+
/* @__PURE__ */ jsx16("span", { className: "fc-other-wallets-icon", children: /* @__PURE__ */ jsxs15("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", children: [
|
|
3699
|
+
/* @__PURE__ */ jsx16("rect", { x: "2", y: "3", width: "16", height: "14", rx: "2", stroke: "currentColor", strokeWidth: "1.5" }),
|
|
3700
|
+
/* @__PURE__ */ jsx16("path", { d: "M2 7h16", stroke: "currentColor", strokeWidth: "1.5" }),
|
|
3701
|
+
/* @__PURE__ */ jsx16("rect", { x: "11", y: "10", width: "7", height: "4", rx: "1", stroke: "currentColor", strokeWidth: "1.5" })
|
|
3702
|
+
] }) }),
|
|
3703
|
+
/* @__PURE__ */ jsx16("span", { className: "fc-wallet-name", children: "Other wallets" })
|
|
3704
|
+
] }),
|
|
3705
|
+
showOther && otherWallets.map((w) => /* @__PURE__ */ jsxs15("button", { type: "button", className: "fc-btn fc-btn-wallet", onClick: () => handleConnect(w), children: [
|
|
3706
|
+
/* @__PURE__ */ jsx16("span", { className: "fc-installed-dot", style: { position: "relative", display: "inline-flex" }, children: /* @__PURE__ */ jsx16("img", { src: w.adapter.icon, alt: "", className: "fc-wallet-icon" }) }),
|
|
3707
|
+
/* @__PURE__ */ jsx16("span", { className: "fc-wallet-name", children: w.adapter.name })
|
|
3708
|
+
] }, w.adapter.name))
|
|
3709
|
+
] }),
|
|
3710
|
+
preferredWallets.length === 0 && otherWallets.length === 0 && mobileExtraWallets.length === 0 && /* @__PURE__ */ jsx16("p", { className: "fc-text", children: "No wallet found. Please install a Solana wallet (like Phantom) to continue." }),
|
|
3711
|
+
error && /* @__PURE__ */ jsx16("p", { className: "fc-error", children: error })
|
|
3712
|
+
] }),
|
|
3713
|
+
!loading && onBack && /* @__PURE__ */ jsx16("div", { className: "fc-switch", children: /* @__PURE__ */ jsx16("button", { type: "button", className: "fc-link", onClick: onBack, children: "Back" }) })
|
|
3714
|
+
] });
|
|
3715
|
+
}
|
|
3716
|
+
function SuccessView2() {
|
|
3717
|
+
return /* @__PURE__ */ jsxs15("div", { className: "fc-success", children: [
|
|
3718
|
+
/* @__PURE__ */ jsx16("div", { className: "fc-success-icon", children: /* @__PURE__ */ jsxs15("svg", { viewBox: "0 0 52 52", className: "fc-success-check", children: [
|
|
3719
|
+
/* @__PURE__ */ jsx16("circle", { className: "fc-success-circle", cx: "26", cy: "26", r: "24", fill: "none" }),
|
|
3720
|
+
/* @__PURE__ */ jsx16("path", { className: "fc-success-tick", fill: "none", d: "M15 26l7.5 7.5L37 19" })
|
|
3721
|
+
] }) }),
|
|
3722
|
+
/* @__PURE__ */ jsx16("p", { className: "fc-success-text", children: "Linked" })
|
|
754
3723
|
] });
|
|
755
3724
|
}
|
|
756
3725
|
|
|
757
3726
|
// src/provider.tsx
|
|
758
|
-
import { jsx as
|
|
759
|
-
function ForgeConnectProvider({ config, children, onLogin, onLogout }) {
|
|
760
|
-
const [auth, setAuth] =
|
|
3727
|
+
import { jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
3728
|
+
function ForgeConnectProvider({ config, children, onLogin, onLogout, walletAdapter }) {
|
|
3729
|
+
const [auth, setAuth] = useState16({
|
|
761
3730
|
status: "loading",
|
|
762
3731
|
user: null,
|
|
763
3732
|
accessToken: null
|
|
764
3733
|
});
|
|
765
|
-
const [modal, setModal] =
|
|
3734
|
+
const [modal, setModal] = useState16({
|
|
766
3735
|
isOpen: false,
|
|
767
3736
|
step: "method-select"
|
|
768
3737
|
});
|
|
769
|
-
const
|
|
770
|
-
const
|
|
3738
|
+
const [accountModal, setAccountModal] = useState16({ isOpen: false });
|
|
3739
|
+
const [linkModal, setLinkModal] = useState16({ isOpen: false });
|
|
3740
|
+
const [challengeToken, setChallengeToken] = useState16(null);
|
|
3741
|
+
const apiRef = useRef8(createApiClient(config.apiUrl));
|
|
3742
|
+
const refreshTimerRef = useRef8(null);
|
|
771
3743
|
const api = apiRef.current;
|
|
772
|
-
const scheduleRefresh =
|
|
3744
|
+
const scheduleRefresh = useCallback8((token) => {
|
|
773
3745
|
if (refreshTimerRef.current) clearTimeout(refreshTimerRef.current);
|
|
774
3746
|
const delay = getRefreshDelay(token);
|
|
775
3747
|
if (delay === null) return;
|
|
@@ -785,7 +3757,7 @@ function ForgeConnectProvider({ config, children, onLogin, onLogout }) {
|
|
|
785
3757
|
}
|
|
786
3758
|
}, delay);
|
|
787
3759
|
}, [api]);
|
|
788
|
-
|
|
3760
|
+
useEffect12(() => {
|
|
789
3761
|
const init = async () => {
|
|
790
3762
|
const token = getStoredToken();
|
|
791
3763
|
if (!token) {
|
|
@@ -819,13 +3791,21 @@ function ForgeConnectProvider({ config, children, onLogin, onLogout }) {
|
|
|
819
3791
|
if (refreshTimerRef.current) clearTimeout(refreshTimerRef.current);
|
|
820
3792
|
};
|
|
821
3793
|
}, [api, scheduleRefresh]);
|
|
822
|
-
|
|
823
|
-
if (window.location.pathname === "/
|
|
3794
|
+
useEffect12(() => {
|
|
3795
|
+
if (window.location.pathname === "/fc-oauth-callback") {
|
|
824
3796
|
const params = new URLSearchParams(window.location.search);
|
|
825
|
-
|
|
826
|
-
|
|
3797
|
+
if (params.get("fc_link_success") && window.opener) {
|
|
3798
|
+
window.opener.postMessage(
|
|
3799
|
+
{ type: "fc_oauth_link_success", provider: params.get("fc_provider") },
|
|
3800
|
+
window.location.origin
|
|
3801
|
+
);
|
|
3802
|
+
window.close();
|
|
3803
|
+
return;
|
|
3804
|
+
}
|
|
3805
|
+
const authCode = params.get("fc_code");
|
|
3806
|
+
if (authCode && window.opener) {
|
|
827
3807
|
window.opener.postMessage(
|
|
828
|
-
{ type: "
|
|
3808
|
+
{ type: "fc_oauth_code", code: authCode },
|
|
829
3809
|
window.location.origin
|
|
830
3810
|
);
|
|
831
3811
|
window.close();
|
|
@@ -834,16 +3814,27 @@ function ForgeConnectProvider({ config, children, onLogin, onLogout }) {
|
|
|
834
3814
|
}
|
|
835
3815
|
const handleMessage = async (event) => {
|
|
836
3816
|
if (event.origin !== window.location.origin) return;
|
|
837
|
-
if (event.data?.type !== "
|
|
838
|
-
const
|
|
839
|
-
if (!
|
|
840
|
-
setStoredToken(token);
|
|
3817
|
+
if (event.data?.type !== "fc_oauth_code") return;
|
|
3818
|
+
const code = event.data.code;
|
|
3819
|
+
if (!code) return;
|
|
841
3820
|
try {
|
|
3821
|
+
const result = await api.exchangeOAuthCode(code);
|
|
3822
|
+
if (result.requires2FA && result.challengeToken) {
|
|
3823
|
+
setChallengeToken(result.challengeToken);
|
|
3824
|
+
setModal({ isOpen: true, step: "verify-2fa" });
|
|
3825
|
+
return;
|
|
3826
|
+
}
|
|
3827
|
+
const token = result.accessToken;
|
|
3828
|
+
if (!token) return;
|
|
3829
|
+
setStoredToken(token);
|
|
3830
|
+
setModal({ isOpen: true, step: "success" });
|
|
842
3831
|
const user = await api.getMe(token);
|
|
843
3832
|
setAuth({ status: "authenticated", user, accessToken: token });
|
|
844
3833
|
scheduleRefresh(token);
|
|
845
|
-
setModal({ isOpen: false, step: "method-select" });
|
|
846
3834
|
onLogin?.(user);
|
|
3835
|
+
setTimeout(() => {
|
|
3836
|
+
setModal({ isOpen: false, step: "method-select" });
|
|
3837
|
+
}, 1500);
|
|
847
3838
|
} catch {
|
|
848
3839
|
removeStoredToken();
|
|
849
3840
|
}
|
|
@@ -851,68 +3842,120 @@ function ForgeConnectProvider({ config, children, onLogin, onLogout }) {
|
|
|
851
3842
|
window.addEventListener("message", handleMessage);
|
|
852
3843
|
return () => window.removeEventListener("message", handleMessage);
|
|
853
3844
|
}, [api, config.apiUrl, scheduleRefresh, onLogin]);
|
|
854
|
-
|
|
3845
|
+
useEffect12(() => {
|
|
3846
|
+
if (config.loginMethods && config.loginMethods.length > 0) {
|
|
3847
|
+
const hasLegacy = config.oauthProviders || config.walletLogin !== void 0 || config.passwordlessLogin !== void 0;
|
|
3848
|
+
if (hasLegacy) {
|
|
3849
|
+
console.warn(
|
|
3850
|
+
"[ForgeConnect] Both loginMethods and legacy fields (oauthProviders, walletLogin, passwordlessLogin) are set. loginMethods takes precedence."
|
|
3851
|
+
);
|
|
3852
|
+
}
|
|
3853
|
+
}
|
|
3854
|
+
}, []);
|
|
3855
|
+
const handleAuthSuccess = useCallback8(
|
|
855
3856
|
async (token) => {
|
|
856
3857
|
setStoredToken(token);
|
|
857
3858
|
const user = await api.getMe(token);
|
|
858
3859
|
setAuth({ status: "authenticated", user, accessToken: token });
|
|
859
3860
|
scheduleRefresh(token);
|
|
860
|
-
setModal({ isOpen:
|
|
3861
|
+
setModal({ isOpen: true, step: "success" });
|
|
861
3862
|
onLogin?.(user);
|
|
3863
|
+
setTimeout(() => {
|
|
3864
|
+
setModal({ isOpen: false, step: "method-select" });
|
|
3865
|
+
}, 1500);
|
|
862
3866
|
},
|
|
863
3867
|
[api, scheduleRefresh, onLogin]
|
|
864
3868
|
);
|
|
865
|
-
const loginWithEmail =
|
|
3869
|
+
const loginWithEmail = useCallback8(
|
|
866
3870
|
async (email, password) => {
|
|
867
|
-
const
|
|
868
|
-
|
|
3871
|
+
const res = await api.login(email, password);
|
|
3872
|
+
if (res.requires2FA) {
|
|
3873
|
+
setChallengeToken(res.challengeToken);
|
|
3874
|
+
setModal((prev) => ({ ...prev, step: "verify-2fa" }));
|
|
3875
|
+
return;
|
|
3876
|
+
}
|
|
3877
|
+
await handleAuthSuccess(res.accessToken);
|
|
869
3878
|
},
|
|
870
3879
|
[api, handleAuthSuccess]
|
|
871
3880
|
);
|
|
872
|
-
const register =
|
|
3881
|
+
const register = useCallback8(
|
|
873
3882
|
async (email, password, displayName) => {
|
|
874
3883
|
await api.register(email, password, displayName);
|
|
875
3884
|
},
|
|
876
3885
|
[api]
|
|
877
3886
|
);
|
|
878
|
-
const sendOtp =
|
|
3887
|
+
const sendOtp = useCallback8(
|
|
879
3888
|
async (email) => {
|
|
880
3889
|
await api.sendOtp(email);
|
|
881
3890
|
},
|
|
882
3891
|
[api]
|
|
883
3892
|
);
|
|
884
|
-
const verifyOtp =
|
|
3893
|
+
const verifyOtp = useCallback8(
|
|
885
3894
|
async (email, code) => {
|
|
886
|
-
const
|
|
887
|
-
|
|
3895
|
+
const res = await api.verifyOtp(email, code);
|
|
3896
|
+
if (res.requires2FA) {
|
|
3897
|
+
setChallengeToken(res.challengeToken);
|
|
3898
|
+
setModal((prev) => ({ ...prev, step: "verify-2fa" }));
|
|
3899
|
+
return;
|
|
3900
|
+
}
|
|
3901
|
+
await handleAuthSuccess(res.accessToken);
|
|
888
3902
|
},
|
|
889
3903
|
[api, handleAuthSuccess]
|
|
890
3904
|
);
|
|
891
|
-
const loginWithWallet =
|
|
892
|
-
async (walletAddress, signMessage, chain = "solana") => {
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
3905
|
+
const loginWithWallet = useCallback8(
|
|
3906
|
+
async (walletAddress, signMessage, chain = "solana", signTransaction) => {
|
|
3907
|
+
if (signMessage) {
|
|
3908
|
+
const { challengeId: challengeId2, message: challengeMessage } = await api.walletChallenge(walletAddress, chain);
|
|
3909
|
+
const message = new TextEncoder().encode(challengeMessage);
|
|
3910
|
+
const signatureBytes = await signMessage(message);
|
|
3911
|
+
const signature = chain === "solana" ? uint8ArrayToBase58(signatureBytes) : Array.from(signatureBytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
3912
|
+
const res2 = await api.walletVerify(challengeId2, signature, walletAddress);
|
|
3913
|
+
if (res2.requires2FA) {
|
|
3914
|
+
setChallengeToken(res2.challengeToken);
|
|
3915
|
+
setModal((prev) => ({ ...prev, step: "verify-2fa" }));
|
|
3916
|
+
return;
|
|
3917
|
+
}
|
|
3918
|
+
await handleAuthSuccess(res2.accessToken);
|
|
3919
|
+
return;
|
|
3920
|
+
}
|
|
3921
|
+
if (!signTransaction) {
|
|
3922
|
+
throw new Error("Wallet does not support message signing or transaction signing.");
|
|
3923
|
+
}
|
|
3924
|
+
const { challengeId, transaction: txBase64 } = await api.walletChallengeTx(walletAddress, chain);
|
|
3925
|
+
const signedTxBase64 = await signTransaction(txBase64);
|
|
3926
|
+
const res = await api.walletVerifyTx(challengeId, signedTxBase64, walletAddress);
|
|
3927
|
+
if (res.requires2FA) {
|
|
3928
|
+
setChallengeToken(res.challengeToken);
|
|
3929
|
+
setModal((prev) => ({ ...prev, step: "verify-2fa" }));
|
|
3930
|
+
return;
|
|
3931
|
+
}
|
|
3932
|
+
await handleAuthSuccess(res.accessToken);
|
|
899
3933
|
},
|
|
900
3934
|
[api, handleAuthSuccess]
|
|
901
3935
|
);
|
|
902
|
-
const loginWithOAuth =
|
|
3936
|
+
const loginWithOAuth = useCallback8(
|
|
903
3937
|
(provider) => {
|
|
904
3938
|
const callbackUrl = `${config.apiUrl}/auth/oauth/${provider}`;
|
|
905
|
-
const redirectUri = encodeURIComponent(window.location.origin + "/
|
|
3939
|
+
const redirectUri = encodeURIComponent(window.location.origin + "/fc-oauth-callback");
|
|
906
3940
|
const url = `${callbackUrl}?redirect_uri=${redirectUri}`;
|
|
907
3941
|
const width = 500;
|
|
908
3942
|
const height = 600;
|
|
909
3943
|
const left = window.screenX + (window.innerWidth - width) / 2;
|
|
910
3944
|
const top = window.screenY + (window.innerHeight - height) / 2;
|
|
911
|
-
window.open(url, "fc_oauth", `width=${width},height=${height},left=${left},top=${top}`);
|
|
3945
|
+
const popup = window.open(url, "fc_oauth", `width=${width},height=${height},left=${left},top=${top}`);
|
|
3946
|
+
setModal({ isOpen: true, step: "oauth" });
|
|
3947
|
+
if (popup) {
|
|
3948
|
+
const poll = setInterval(() => {
|
|
3949
|
+
if (popup.closed) {
|
|
3950
|
+
clearInterval(poll);
|
|
3951
|
+
setModal((prev) => prev.step === "oauth" ? { isOpen: true, step: "method-select" } : prev);
|
|
3952
|
+
}
|
|
3953
|
+
}, 500);
|
|
3954
|
+
}
|
|
912
3955
|
},
|
|
913
3956
|
[config.apiUrl]
|
|
914
3957
|
);
|
|
915
|
-
const logout =
|
|
3958
|
+
const logout = useCallback8(async () => {
|
|
916
3959
|
const token = auth.accessToken;
|
|
917
3960
|
removeStoredToken();
|
|
918
3961
|
if (refreshTimerRef.current) clearTimeout(refreshTimerRef.current);
|
|
@@ -925,7 +3968,49 @@ function ForgeConnectProvider({ config, children, onLogin, onLogout }) {
|
|
|
925
3968
|
}
|
|
926
3969
|
}
|
|
927
3970
|
}, [auth.accessToken, api, onLogout]);
|
|
928
|
-
const
|
|
3971
|
+
const forgotPassword = useCallback8(
|
|
3972
|
+
async (email) => {
|
|
3973
|
+
await api.forgotPassword(email);
|
|
3974
|
+
},
|
|
3975
|
+
[api]
|
|
3976
|
+
);
|
|
3977
|
+
const resetPassword = useCallback8(
|
|
3978
|
+
async (token, password) => {
|
|
3979
|
+
await api.resetPassword(token, password);
|
|
3980
|
+
},
|
|
3981
|
+
[api]
|
|
3982
|
+
);
|
|
3983
|
+
const verifyEmailToken = useCallback8(
|
|
3984
|
+
async (token) => {
|
|
3985
|
+
const res = await api.verifyEmailToken(token);
|
|
3986
|
+
if (res.requires2FA) {
|
|
3987
|
+
setChallengeToken(res.challengeToken);
|
|
3988
|
+
setModal({ isOpen: true, step: "verify-2fa" });
|
|
3989
|
+
return;
|
|
3990
|
+
}
|
|
3991
|
+
await handleAuthSuccess(res.accessToken);
|
|
3992
|
+
},
|
|
3993
|
+
[api, handleAuthSuccess]
|
|
3994
|
+
);
|
|
3995
|
+
const loginWithPasskey = useCallback8(async () => {
|
|
3996
|
+
const { options, challengeKey } = await api.getPasskeyLoginOptions(config.webauthnRpId);
|
|
3997
|
+
const authResponse = await startAuthentication({ optionsJSON: options });
|
|
3998
|
+
const { accessToken } = await api.verifyPasskeyLogin(challengeKey, authResponse, config.webauthnRpId, config.webauthnOrigin);
|
|
3999
|
+
await handleAuthSuccess(accessToken);
|
|
4000
|
+
}, [api, handleAuthSuccess, config.webauthnRpId, config.webauthnOrigin]);
|
|
4001
|
+
const verify2FA = useCallback8(async (code) => {
|
|
4002
|
+
if (!challengeToken) throw new Error("No 2FA challenge active");
|
|
4003
|
+
const { accessToken } = await api.verify2FA(challengeToken, code);
|
|
4004
|
+
setChallengeToken(null);
|
|
4005
|
+
await handleAuthSuccess(accessToken);
|
|
4006
|
+
}, [api, challengeToken, handleAuthSuccess]);
|
|
4007
|
+
const verifyRecoveryCode = useCallback8(async (code) => {
|
|
4008
|
+
if (!challengeToken) throw new Error("No 2FA challenge active");
|
|
4009
|
+
const { accessToken } = await api.verifyRecoveryCode(challengeToken, code);
|
|
4010
|
+
setChallengeToken(null);
|
|
4011
|
+
await handleAuthSuccess(accessToken);
|
|
4012
|
+
}, [api, challengeToken, handleAuthSuccess]);
|
|
4013
|
+
const logoutAll = useCallback8(async () => {
|
|
929
4014
|
const token = auth.accessToken;
|
|
930
4015
|
removeStoredToken();
|
|
931
4016
|
if (refreshTimerRef.current) clearTimeout(refreshTimerRef.current);
|
|
@@ -938,17 +4023,39 @@ function ForgeConnectProvider({ config, children, onLogin, onLogout }) {
|
|
|
938
4023
|
}
|
|
939
4024
|
}
|
|
940
4025
|
}, [auth.accessToken, api, onLogout]);
|
|
941
|
-
const openModal =
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
4026
|
+
const openModal = useCallback8(() => {
|
|
4027
|
+
const methods = resolveLoginMethods(config);
|
|
4028
|
+
if (methods.length === 1 && isOAuthMethod(methods[0])) {
|
|
4029
|
+
loginWithOAuth(methods[0]);
|
|
4030
|
+
return;
|
|
4031
|
+
}
|
|
4032
|
+
if (config.defaultLoginMethod && isOAuthMethod(config.defaultLoginMethod) && methods.includes(config.defaultLoginMethod)) {
|
|
4033
|
+
loginWithOAuth(config.defaultLoginMethod);
|
|
4034
|
+
return;
|
|
4035
|
+
}
|
|
4036
|
+
const step = resolveInitialStep(config, methods);
|
|
4037
|
+
setModal({ isOpen: true, step });
|
|
4038
|
+
}, [config, loginWithOAuth]);
|
|
4039
|
+
const closeModal = useCallback8(() => {
|
|
945
4040
|
setModal({ isOpen: false, step: "method-select" });
|
|
946
4041
|
}, []);
|
|
947
|
-
const setModalStep =
|
|
4042
|
+
const setModalStep = useCallback8((step) => {
|
|
948
4043
|
setModal((prev) => ({ ...prev, step }));
|
|
949
4044
|
}, []);
|
|
950
|
-
const
|
|
951
|
-
|
|
4045
|
+
const openAccountModal = useCallback8(() => {
|
|
4046
|
+
setAccountModal({ isOpen: true });
|
|
4047
|
+
}, []);
|
|
4048
|
+
const closeAccountModal = useCallback8(() => {
|
|
4049
|
+
setAccountModal({ isOpen: false });
|
|
4050
|
+
}, []);
|
|
4051
|
+
const openLinkModal = useCallback8((mode) => {
|
|
4052
|
+
setLinkModal({ isOpen: true, mode: mode ?? "auth" });
|
|
4053
|
+
}, []);
|
|
4054
|
+
const closeLinkModal = useCallback8(() => {
|
|
4055
|
+
setLinkModal({ isOpen: false });
|
|
4056
|
+
}, []);
|
|
4057
|
+
const getAccessToken = useCallback8(() => auth.accessToken, [auth.accessToken]);
|
|
4058
|
+
const value = useMemo3(() => ({
|
|
952
4059
|
auth,
|
|
953
4060
|
modal,
|
|
954
4061
|
config,
|
|
@@ -959,189 +4066,367 @@ function ForgeConnectProvider({ config, children, onLogin, onLogout }) {
|
|
|
959
4066
|
verifyOtp,
|
|
960
4067
|
loginWithWallet,
|
|
961
4068
|
loginWithOAuth,
|
|
4069
|
+
loginWithPasskey,
|
|
962
4070
|
logout,
|
|
963
4071
|
logoutAll,
|
|
4072
|
+
forgotPassword,
|
|
4073
|
+
resetPassword,
|
|
4074
|
+
verifyEmailToken,
|
|
4075
|
+
challengeToken,
|
|
4076
|
+
verify2FA,
|
|
4077
|
+
verifyRecoveryCode,
|
|
964
4078
|
openModal,
|
|
965
4079
|
closeModal,
|
|
966
4080
|
setModalStep,
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
4081
|
+
accountModal,
|
|
4082
|
+
openAccountModal,
|
|
4083
|
+
closeAccountModal,
|
|
4084
|
+
linkModal,
|
|
4085
|
+
openLinkModal,
|
|
4086
|
+
closeLinkModal,
|
|
4087
|
+
getAccessToken,
|
|
4088
|
+
walletAdapter: walletAdapter ?? null
|
|
4089
|
+
}), [
|
|
4090
|
+
auth,
|
|
4091
|
+
modal,
|
|
4092
|
+
config,
|
|
4093
|
+
api,
|
|
4094
|
+
challengeToken,
|
|
4095
|
+
accountModal,
|
|
4096
|
+
linkModal,
|
|
4097
|
+
loginWithEmail,
|
|
4098
|
+
register,
|
|
4099
|
+
sendOtp,
|
|
4100
|
+
verifyOtp,
|
|
4101
|
+
loginWithWallet,
|
|
4102
|
+
loginWithOAuth,
|
|
4103
|
+
loginWithPasskey,
|
|
4104
|
+
logout,
|
|
4105
|
+
logoutAll,
|
|
4106
|
+
forgotPassword,
|
|
4107
|
+
resetPassword,
|
|
4108
|
+
verifyEmailToken,
|
|
4109
|
+
verify2FA,
|
|
4110
|
+
verifyRecoveryCode,
|
|
4111
|
+
openModal,
|
|
4112
|
+
closeModal,
|
|
4113
|
+
setModalStep,
|
|
4114
|
+
openAccountModal,
|
|
4115
|
+
closeAccountModal,
|
|
4116
|
+
openLinkModal,
|
|
4117
|
+
closeLinkModal,
|
|
4118
|
+
getAccessToken,
|
|
4119
|
+
walletAdapter
|
|
4120
|
+
]);
|
|
4121
|
+
return /* @__PURE__ */ jsxs16(ForgeConnectContext.Provider, { value, children: [
|
|
970
4122
|
children,
|
|
971
|
-
/* @__PURE__ */
|
|
4123
|
+
/* @__PURE__ */ jsx17(LoginModal, {}),
|
|
4124
|
+
/* @__PURE__ */ jsx17(AccountModal, {}),
|
|
4125
|
+
/* @__PURE__ */ jsx17(LinkAuthModal, {})
|
|
972
4126
|
] });
|
|
973
4127
|
}
|
|
974
|
-
var BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
975
|
-
function uint8ArrayToBase58(bytes) {
|
|
976
|
-
const digits = [0];
|
|
977
|
-
for (const byte of bytes) {
|
|
978
|
-
let carry = byte;
|
|
979
|
-
for (let j = 0; j < digits.length; j++) {
|
|
980
|
-
carry += digits[j] << 8;
|
|
981
|
-
digits[j] = carry % 58;
|
|
982
|
-
carry = carry / 58 | 0;
|
|
983
|
-
}
|
|
984
|
-
while (carry > 0) {
|
|
985
|
-
digits.push(carry % 58);
|
|
986
|
-
carry = carry / 58 | 0;
|
|
987
|
-
}
|
|
988
|
-
}
|
|
989
|
-
let str = "";
|
|
990
|
-
for (let i = 0; i < bytes.length && bytes[i] === 0; i++) {
|
|
991
|
-
str += "1";
|
|
992
|
-
}
|
|
993
|
-
for (let i = digits.length - 1; i >= 0; i--) {
|
|
994
|
-
str += BASE58_ALPHABET[digits[i]];
|
|
995
|
-
}
|
|
996
|
-
return str;
|
|
997
|
-
}
|
|
998
4128
|
|
|
999
4129
|
// src/components/login-button.tsx
|
|
1000
|
-
import { jsx as
|
|
4130
|
+
import { jsx as jsx18 } from "react/jsx-runtime";
|
|
1001
4131
|
function LoginButton({ className, label }) {
|
|
1002
4132
|
const { auth, openModal, logout } = useForgeConnect();
|
|
1003
4133
|
if (auth.status === "loading") {
|
|
1004
|
-
return /* @__PURE__ */
|
|
4134
|
+
return /* @__PURE__ */ jsx18("button", { type: "button", className: className ?? "fc-btn fc-btn-login", disabled: true, children: "Loading..." });
|
|
1005
4135
|
}
|
|
1006
4136
|
if (auth.status === "authenticated") {
|
|
1007
|
-
return /* @__PURE__ */
|
|
4137
|
+
return /* @__PURE__ */ jsx18("button", { type: "button", className: className ?? "fc-btn fc-btn-login", onClick: logout, children: label ?? "Log out" });
|
|
1008
4138
|
}
|
|
1009
|
-
return /* @__PURE__ */
|
|
4139
|
+
return /* @__PURE__ */ jsx18("button", { type: "button", className: className ?? "fc-btn fc-btn-login", onClick: openModal, children: label ?? "Sign in" });
|
|
1010
4140
|
}
|
|
1011
4141
|
|
|
1012
|
-
// src/
|
|
1013
|
-
import {
|
|
1014
|
-
function
|
|
1015
|
-
const { auth,
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
4142
|
+
// src/components/account-button.tsx
|
|
4143
|
+
import { jsx as jsx19, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
4144
|
+
function AccountButton({ className, loginLabel, compact }) {
|
|
4145
|
+
const { auth, openModal, openAccountModal } = useForgeConnect();
|
|
4146
|
+
if (auth.status === "loading") {
|
|
4147
|
+
return /* @__PURE__ */ jsx19("button", { type: "button", className: className ?? "fc-btn fc-btn-login", disabled: true, children: "Loading..." });
|
|
4148
|
+
}
|
|
4149
|
+
if (auth.status === "authenticated" && auth.user) {
|
|
4150
|
+
const user = auth.user;
|
|
4151
|
+
const initial = (user.displayName ?? user.primaryEmail ?? "?").charAt(0).toUpperCase();
|
|
4152
|
+
return /* @__PURE__ */ jsxs17("button", { type: "button", className: className ?? "fc-btn-account", onClick: openAccountModal, children: [
|
|
4153
|
+
user.avatarUrl ? /* @__PURE__ */ jsx19("img", { src: user.avatarUrl, alt: "", className: "fc-account-avatar" }) : /* @__PURE__ */ jsx19("span", { className: "fc-account-avatar-placeholder", children: initial }),
|
|
4154
|
+
!compact && /* @__PURE__ */ jsx19("span", { children: user.displayName ?? user.primaryEmail ?? "Account" })
|
|
4155
|
+
] });
|
|
4156
|
+
}
|
|
4157
|
+
return /* @__PURE__ */ jsx19("button", { type: "button", className: className ?? "fc-btn fc-btn-login", onClick: openModal, children: loginLabel ?? "Sign in" });
|
|
4158
|
+
}
|
|
4159
|
+
|
|
4160
|
+
// src/hooks/use-admin.ts
|
|
4161
|
+
import { useState as useState17, useCallback as useCallback9 } from "react";
|
|
4162
|
+
function useAdmin() {
|
|
4163
|
+
const { api, getAccessToken } = useForgeConnect();
|
|
4164
|
+
const [users, setUsers] = useState17(null);
|
|
4165
|
+
const [selectedUser, setSelectedUser] = useState17(null);
|
|
4166
|
+
const [userSessions, setUserSessions] = useState17(null);
|
|
4167
|
+
const [loading, setLoading] = useState17(false);
|
|
4168
|
+
const listUsers = useCallback9(
|
|
4169
|
+
async (params) => {
|
|
1020
4170
|
const token = getAccessToken();
|
|
1021
|
-
if (!token) throw new Error("
|
|
1022
|
-
|
|
4171
|
+
if (!token) throw new Error("Please sign in to continue.");
|
|
4172
|
+
setLoading(true);
|
|
4173
|
+
try {
|
|
4174
|
+
const data = await api.adminListUsers(token, params);
|
|
4175
|
+
setUsers(data);
|
|
4176
|
+
return data;
|
|
4177
|
+
} finally {
|
|
4178
|
+
setLoading(false);
|
|
4179
|
+
}
|
|
1023
4180
|
},
|
|
1024
4181
|
[api, getAccessToken]
|
|
1025
4182
|
);
|
|
1026
|
-
const
|
|
1027
|
-
|
|
1028
|
-
if (!token) throw new Error("Not authenticated");
|
|
1029
|
-
setLoading(true);
|
|
1030
|
-
try {
|
|
1031
|
-
const methods = await api.getAuthMethods(token);
|
|
1032
|
-
setAuthMethods(methods);
|
|
1033
|
-
return methods;
|
|
1034
|
-
} finally {
|
|
1035
|
-
setLoading(false);
|
|
1036
|
-
}
|
|
1037
|
-
}, [api, getAccessToken]);
|
|
1038
|
-
const linkAuthMethod = useCallback2(
|
|
1039
|
-
async (data) => {
|
|
4183
|
+
const getUser = useCallback9(
|
|
4184
|
+
async (id) => {
|
|
1040
4185
|
const token = getAccessToken();
|
|
1041
|
-
if (!token) throw new Error("
|
|
1042
|
-
|
|
1043
|
-
|
|
4186
|
+
if (!token) throw new Error("Please sign in to continue.");
|
|
4187
|
+
setLoading(true);
|
|
4188
|
+
try {
|
|
4189
|
+
const data = await api.adminGetUser(token, id);
|
|
4190
|
+
setSelectedUser(data);
|
|
4191
|
+
return data;
|
|
4192
|
+
} finally {
|
|
4193
|
+
setLoading(false);
|
|
4194
|
+
}
|
|
1044
4195
|
},
|
|
1045
|
-
[api, getAccessToken
|
|
4196
|
+
[api, getAccessToken]
|
|
4197
|
+
);
|
|
4198
|
+
const updateUserStatus = useCallback9(
|
|
4199
|
+
async (id, status) => {
|
|
4200
|
+
const token = getAccessToken();
|
|
4201
|
+
if (!token) throw new Error("Please sign in to continue.");
|
|
4202
|
+
await api.adminUpdateUserStatus(token, id, status);
|
|
4203
|
+
if (selectedUser?.id === id) {
|
|
4204
|
+
await getUser(id);
|
|
4205
|
+
}
|
|
4206
|
+
},
|
|
4207
|
+
[api, getAccessToken, selectedUser?.id, getUser]
|
|
1046
4208
|
);
|
|
1047
|
-
const
|
|
4209
|
+
const getUserSessions = useCallback9(
|
|
1048
4210
|
async (id) => {
|
|
1049
4211
|
const token = getAccessToken();
|
|
1050
|
-
if (!token) throw new Error("
|
|
1051
|
-
|
|
1052
|
-
|
|
4212
|
+
if (!token) throw new Error("Please sign in to continue.");
|
|
4213
|
+
setLoading(true);
|
|
4214
|
+
try {
|
|
4215
|
+
const data = await api.adminGetUserSessions(token, id);
|
|
4216
|
+
setUserSessions(data);
|
|
4217
|
+
return data;
|
|
4218
|
+
} finally {
|
|
4219
|
+
setLoading(false);
|
|
4220
|
+
}
|
|
1053
4221
|
},
|
|
1054
|
-
[api, getAccessToken
|
|
4222
|
+
[api, getAccessToken]
|
|
4223
|
+
);
|
|
4224
|
+
const revokeUserSessions = useCallback9(
|
|
4225
|
+
async (id) => {
|
|
4226
|
+
const token = getAccessToken();
|
|
4227
|
+
if (!token) throw new Error("Please sign in to continue.");
|
|
4228
|
+
await api.adminRevokeUserSessions(token, id);
|
|
4229
|
+
setUserSessions([]);
|
|
4230
|
+
},
|
|
4231
|
+
[api, getAccessToken]
|
|
1055
4232
|
);
|
|
1056
4233
|
return {
|
|
1057
|
-
|
|
1058
|
-
|
|
4234
|
+
users,
|
|
4235
|
+
selectedUser,
|
|
4236
|
+
userSessions,
|
|
1059
4237
|
loading,
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
4238
|
+
listUsers,
|
|
4239
|
+
getUser,
|
|
4240
|
+
updateUserStatus,
|
|
4241
|
+
getUserSessions,
|
|
4242
|
+
revokeUserSessions
|
|
1064
4243
|
};
|
|
1065
4244
|
}
|
|
1066
4245
|
|
|
1067
|
-
// src/hooks/use-
|
|
1068
|
-
import { useState as
|
|
1069
|
-
function
|
|
4246
|
+
// src/hooks/use-roles.ts
|
|
4247
|
+
import { useState as useState18, useCallback as useCallback10 } from "react";
|
|
4248
|
+
function useRoles() {
|
|
1070
4249
|
const { api, getAccessToken } = useForgeConnect();
|
|
1071
|
-
const [
|
|
1072
|
-
const [
|
|
1073
|
-
const
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
4250
|
+
const [roles, setRoles] = useState18(null);
|
|
4251
|
+
const [selectedRole, setSelectedRole] = useState18(null);
|
|
4252
|
+
const [userRoleAssignments, setUserRoleAssignments] = useState18(null);
|
|
4253
|
+
const [roleUsers, setRoleUsers] = useState18(null);
|
|
4254
|
+
const [permissionDomains, setPermissionDomains] = useState18(null);
|
|
4255
|
+
const [loading, setLoading] = useState18(false);
|
|
4256
|
+
const listRoles = useCallback10(
|
|
4257
|
+
async (tenantId) => {
|
|
4258
|
+
const token = getAccessToken();
|
|
4259
|
+
if (!token) throw new Error("Please sign in to continue.");
|
|
4260
|
+
setLoading(true);
|
|
4261
|
+
try {
|
|
4262
|
+
const data = await api.adminListRoles(token, tenantId);
|
|
4263
|
+
setRoles(data.data);
|
|
4264
|
+
return data.data;
|
|
4265
|
+
} finally {
|
|
4266
|
+
setLoading(false);
|
|
4267
|
+
}
|
|
4268
|
+
},
|
|
4269
|
+
[api, getAccessToken]
|
|
4270
|
+
);
|
|
4271
|
+
const getRoleUsers = useCallback10(
|
|
4272
|
+
async (id) => {
|
|
4273
|
+
const token = getAccessToken();
|
|
4274
|
+
if (!token) throw new Error("Please sign in to continue.");
|
|
4275
|
+
const data = await api.adminGetRoleUsers(token, id);
|
|
4276
|
+
setRoleUsers(data.data);
|
|
4277
|
+
return data.data;
|
|
4278
|
+
},
|
|
4279
|
+
[api, getAccessToken]
|
|
4280
|
+
);
|
|
4281
|
+
const getRole = useCallback10(
|
|
4282
|
+
async (id) => {
|
|
4283
|
+
const token = getAccessToken();
|
|
4284
|
+
if (!token) throw new Error("Please sign in to continue.");
|
|
4285
|
+
setLoading(true);
|
|
4286
|
+
try {
|
|
4287
|
+
const data = await api.adminGetRole(token, id);
|
|
4288
|
+
setSelectedRole(data);
|
|
4289
|
+
getRoleUsers(id).catch(() => {
|
|
4290
|
+
});
|
|
4291
|
+
return data;
|
|
4292
|
+
} finally {
|
|
4293
|
+
setLoading(false);
|
|
4294
|
+
}
|
|
4295
|
+
},
|
|
4296
|
+
[api, getAccessToken, getRoleUsers]
|
|
4297
|
+
);
|
|
4298
|
+
const createRole = useCallback10(
|
|
4299
|
+
async (data) => {
|
|
4300
|
+
const token = getAccessToken();
|
|
4301
|
+
if (!token) throw new Error("Please sign in to continue.");
|
|
4302
|
+
const role = await api.adminCreateRole(token, data);
|
|
4303
|
+
await listRoles(data.tenantId);
|
|
4304
|
+
return role;
|
|
4305
|
+
},
|
|
4306
|
+
[api, getAccessToken, listRoles]
|
|
4307
|
+
);
|
|
4308
|
+
const updateRole = useCallback10(
|
|
1086
4309
|
async (id, data) => {
|
|
1087
4310
|
const token = getAccessToken();
|
|
1088
|
-
if (!token) throw new Error("
|
|
1089
|
-
await api.
|
|
1090
|
-
|
|
4311
|
+
if (!token) throw new Error("Please sign in to continue.");
|
|
4312
|
+
const role = await api.adminUpdateRole(token, id, data);
|
|
4313
|
+
setSelectedRole(role);
|
|
4314
|
+
return role;
|
|
1091
4315
|
},
|
|
1092
|
-
[api, getAccessToken
|
|
4316
|
+
[api, getAccessToken]
|
|
1093
4317
|
);
|
|
1094
|
-
|
|
1095
|
-
wallets,
|
|
1096
|
-
loading,
|
|
1097
|
-
fetchWallets,
|
|
1098
|
-
updateWallet
|
|
1099
|
-
};
|
|
1100
|
-
}
|
|
1101
|
-
|
|
1102
|
-
// src/hooks/use-sessions.ts
|
|
1103
|
-
import { useState as useState7, useCallback as useCallback4 } from "react";
|
|
1104
|
-
function useSessions() {
|
|
1105
|
-
const { api, getAccessToken } = useForgeConnect();
|
|
1106
|
-
const [sessions, setSessions] = useState7(null);
|
|
1107
|
-
const [loading, setLoading] = useState7(false);
|
|
1108
|
-
const fetchSessions = useCallback4(async () => {
|
|
1109
|
-
const token = getAccessToken();
|
|
1110
|
-
if (!token) throw new Error("Not authenticated");
|
|
1111
|
-
setLoading(true);
|
|
1112
|
-
try {
|
|
1113
|
-
const data = await api.getSessions(token);
|
|
1114
|
-
setSessions(data);
|
|
1115
|
-
return data;
|
|
1116
|
-
} finally {
|
|
1117
|
-
setLoading(false);
|
|
1118
|
-
}
|
|
1119
|
-
}, [api, getAccessToken]);
|
|
1120
|
-
const revokeSession = useCallback4(
|
|
4318
|
+
const deleteRole = useCallback10(
|
|
1121
4319
|
async (id) => {
|
|
1122
4320
|
const token = getAccessToken();
|
|
1123
|
-
if (!token) throw new Error("
|
|
1124
|
-
await api.
|
|
1125
|
-
|
|
4321
|
+
if (!token) throw new Error("Please sign in to continue.");
|
|
4322
|
+
await api.adminDeleteRole(token, id);
|
|
4323
|
+
if (selectedRole?.id === id) setSelectedRole(null);
|
|
1126
4324
|
},
|
|
1127
|
-
[api, getAccessToken,
|
|
4325
|
+
[api, getAccessToken, selectedRole?.id]
|
|
4326
|
+
);
|
|
4327
|
+
const getPermissions = useCallback10(
|
|
4328
|
+
async () => {
|
|
4329
|
+
const token = getAccessToken();
|
|
4330
|
+
if (!token) throw new Error("Please sign in to continue.");
|
|
4331
|
+
const data = await api.adminGetPermissions(token);
|
|
4332
|
+
setPermissionDomains(data.domains);
|
|
4333
|
+
return data.domains;
|
|
4334
|
+
},
|
|
4335
|
+
[api, getAccessToken]
|
|
4336
|
+
);
|
|
4337
|
+
const getUserRoles = useCallback10(
|
|
4338
|
+
async (userId, tenantId) => {
|
|
4339
|
+
const token = getAccessToken();
|
|
4340
|
+
if (!token) throw new Error("Please sign in to continue.");
|
|
4341
|
+
setLoading(true);
|
|
4342
|
+
try {
|
|
4343
|
+
const data = await api.adminGetUserRoles(token, userId, tenantId);
|
|
4344
|
+
setUserRoleAssignments(data.data);
|
|
4345
|
+
return data.data;
|
|
4346
|
+
} finally {
|
|
4347
|
+
setLoading(false);
|
|
4348
|
+
}
|
|
4349
|
+
},
|
|
4350
|
+
[api, getAccessToken]
|
|
4351
|
+
);
|
|
4352
|
+
const assignRole = useCallback10(
|
|
4353
|
+
async (userId, roleId, tenantId) => {
|
|
4354
|
+
const token = getAccessToken();
|
|
4355
|
+
if (!token) throw new Error("Please sign in to continue.");
|
|
4356
|
+
await api.adminAssignRole(token, userId, roleId, tenantId);
|
|
4357
|
+
},
|
|
4358
|
+
[api, getAccessToken]
|
|
4359
|
+
);
|
|
4360
|
+
const revokeRole = useCallback10(
|
|
4361
|
+
async (userId, roleId, tenantId) => {
|
|
4362
|
+
const token = getAccessToken();
|
|
4363
|
+
if (!token) throw new Error("Please sign in to continue.");
|
|
4364
|
+
await api.adminRevokeRole(token, userId, roleId, tenantId);
|
|
4365
|
+
},
|
|
4366
|
+
[api, getAccessToken]
|
|
1128
4367
|
);
|
|
1129
4368
|
return {
|
|
1130
|
-
|
|
4369
|
+
roles,
|
|
4370
|
+
selectedRole,
|
|
4371
|
+
roleUsers,
|
|
4372
|
+
userRoleAssignments,
|
|
4373
|
+
permissionDomains,
|
|
1131
4374
|
loading,
|
|
1132
|
-
|
|
1133
|
-
|
|
4375
|
+
listRoles,
|
|
4376
|
+
getRole,
|
|
4377
|
+
getRoleUsers,
|
|
4378
|
+
createRole,
|
|
4379
|
+
updateRole,
|
|
4380
|
+
deleteRole,
|
|
4381
|
+
getPermissions,
|
|
4382
|
+
getUserRoles,
|
|
4383
|
+
assignRole,
|
|
4384
|
+
revokeRole
|
|
1134
4385
|
};
|
|
1135
4386
|
}
|
|
4387
|
+
|
|
4388
|
+
// src/lib/permissions.ts
|
|
4389
|
+
function hasPermission(required, granted) {
|
|
4390
|
+
for (const g of granted) {
|
|
4391
|
+
if (g === "*") return true;
|
|
4392
|
+
if (g === required) return true;
|
|
4393
|
+
if (g.endsWith(":*")) {
|
|
4394
|
+
const gDomain = g.slice(0, -2);
|
|
4395
|
+
const rDomain = required.split(":")[0];
|
|
4396
|
+
if (gDomain === rDomain) return true;
|
|
4397
|
+
}
|
|
4398
|
+
}
|
|
4399
|
+
return false;
|
|
4400
|
+
}
|
|
4401
|
+
function hasAllPermissions(required, granted) {
|
|
4402
|
+
return required.every((r) => hasPermission(r, granted));
|
|
4403
|
+
}
|
|
4404
|
+
function hasAnyPermission(required, granted) {
|
|
4405
|
+
return required.some((r) => hasPermission(r, granted));
|
|
4406
|
+
}
|
|
1136
4407
|
export {
|
|
4408
|
+
AccountButton,
|
|
4409
|
+
AccountModal,
|
|
4410
|
+
DeleteAccountModal,
|
|
4411
|
+
ErrorView,
|
|
1137
4412
|
ForgeConnectApiError,
|
|
1138
4413
|
ForgeConnectContext,
|
|
1139
4414
|
ForgeConnectProvider,
|
|
4415
|
+
LinkAuthModal,
|
|
1140
4416
|
LoginButton,
|
|
1141
4417
|
LoginModal,
|
|
1142
4418
|
ModalOverlay,
|
|
4419
|
+
PasskeysModal,
|
|
4420
|
+
TwoFactorModal,
|
|
1143
4421
|
createApiClient,
|
|
4422
|
+
hasAllPermissions,
|
|
4423
|
+
hasAnyPermission,
|
|
4424
|
+
hasPermission,
|
|
4425
|
+
isOAuthMethod,
|
|
4426
|
+
resolveLoginMethods,
|
|
4427
|
+
useAdmin,
|
|
1144
4428
|
useForgeConnect,
|
|
4429
|
+
useRoles,
|
|
1145
4430
|
useSessions,
|
|
1146
4431
|
useUser,
|
|
1147
4432
|
useWallets
|