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