@forge-connect/react 0.1.0 → 1.0.0

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