@forge-connect/react 0.1.0 → 1.0.1

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