@forge-connect/react 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,1179 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ ForgeConnectApiError: () => ForgeConnectApiError,
24
+ ForgeConnectContext: () => ForgeConnectContext,
25
+ ForgeConnectProvider: () => ForgeConnectProvider,
26
+ LoginButton: () => LoginButton,
27
+ LoginModal: () => LoginModal,
28
+ ModalOverlay: () => ModalOverlay,
29
+ createApiClient: () => createApiClient,
30
+ useForgeConnect: () => useForgeConnect,
31
+ useSessions: () => useSessions,
32
+ useUser: () => useUser,
33
+ useWallets: () => useWallets
34
+ });
35
+ module.exports = __toCommonJS(index_exports);
36
+
37
+ // src/provider.tsx
38
+ var import_react7 = require("react");
39
+
40
+ // src/context.ts
41
+ var import_react = require("react");
42
+ var ForgeConnectContext = (0, import_react.createContext)(null);
43
+
44
+ // src/api/client.ts
45
+ var ForgeConnectApiError = class extends Error {
46
+ status;
47
+ code;
48
+ constructor(status, code, message) {
49
+ super(message);
50
+ this.name = "ForgeConnectApiError";
51
+ this.status = status;
52
+ this.code = code;
53
+ }
54
+ };
55
+ function createApiClient(apiUrl) {
56
+ const base = apiUrl.replace(/\/+$/, "");
57
+ async function request(path, options = {}) {
58
+ const { method = "GET", body, token } = options;
59
+ const headers = {
60
+ "Content-Type": "application/json"
61
+ };
62
+ if (token) {
63
+ headers["Authorization"] = `Bearer ${token}`;
64
+ }
65
+ const res = await fetch(`${base}${path}`, {
66
+ method,
67
+ headers,
68
+ credentials: "include",
69
+ body: body ? JSON.stringify(body) : void 0
70
+ });
71
+ if (!res.ok) {
72
+ let errorBody;
73
+ try {
74
+ errorBody = await res.json();
75
+ } catch {
76
+ throw new ForgeConnectApiError(res.status, "UNKNOWN", res.statusText);
77
+ }
78
+ throw new ForgeConnectApiError(res.status, errorBody.error.code, errorBody.error.message);
79
+ }
80
+ return res.json();
81
+ }
82
+ return {
83
+ // ── Auth: Email ──
84
+ register(email, password, displayName) {
85
+ return request("/auth/email/register", {
86
+ method: "POST",
87
+ body: { email, password, displayName }
88
+ });
89
+ },
90
+ login(email, password) {
91
+ return request(
92
+ "/auth/email/login",
93
+ { method: "POST", body: { email, password } }
94
+ );
95
+ },
96
+ sendOtp(email) {
97
+ return request("/auth/email/send-code", {
98
+ method: "POST",
99
+ body: { email }
100
+ });
101
+ },
102
+ verifyOtp(email, code) {
103
+ return request(
104
+ "/auth/email/verify-code",
105
+ { method: "POST", body: { email, code } }
106
+ );
107
+ },
108
+ // ── Auth: Wallet ──
109
+ walletChallenge(walletAddress, chain = "solana") {
110
+ return request("/auth/wallet/challenge", {
111
+ method: "POST",
112
+ body: { walletAddress, chain }
113
+ });
114
+ },
115
+ walletVerify(challengeId, signature, walletAddress) {
116
+ return request("/auth/wallet/verify", {
117
+ method: "POST",
118
+ body: { challengeId, signature, walletAddress }
119
+ });
120
+ },
121
+ // ── Auth: Session ──
122
+ refresh() {
123
+ return request("/auth/refresh", { method: "POST" });
124
+ },
125
+ logout(token) {
126
+ return request("/auth/logout", { method: "POST", token });
127
+ },
128
+ logoutAll(token) {
129
+ return request("/auth/logout-all", { method: "POST", token });
130
+ },
131
+ // ── User ──
132
+ getMe(token) {
133
+ return request(
134
+ "/users/me",
135
+ { token }
136
+ );
137
+ },
138
+ updateMe(token, data) {
139
+ return request(
140
+ "/users/me",
141
+ { method: "PATCH", token, body: data }
142
+ );
143
+ },
144
+ getAuthMethods(token) {
145
+ return request(
146
+ "/users/me/auth-methods",
147
+ { token }
148
+ );
149
+ },
150
+ linkAuthMethod(token, data) {
151
+ return request("/users/me/auth-methods", {
152
+ method: "POST",
153
+ token,
154
+ body: data
155
+ });
156
+ },
157
+ unlinkAuthMethod(token, id) {
158
+ return request(`/users/me/auth-methods/${id}`, {
159
+ method: "DELETE",
160
+ token
161
+ });
162
+ },
163
+ getWallets(token) {
164
+ return request(
165
+ "/users/me/wallets",
166
+ { token }
167
+ );
168
+ },
169
+ updateWallet(token, id, data) {
170
+ return request(
171
+ `/users/me/wallets/${id}`,
172
+ { method: "PATCH", token, body: data }
173
+ );
174
+ },
175
+ getSessions(token) {
176
+ return request(
177
+ "/users/me/sessions",
178
+ { token }
179
+ );
180
+ },
181
+ revokeSession(token, id) {
182
+ return request(`/users/me/sessions/${id}`, {
183
+ method: "DELETE",
184
+ token
185
+ });
186
+ }
187
+ };
188
+ }
189
+
190
+ // src/utils.ts
191
+ var TOKEN_KEY = "fc_access_token";
192
+ function getStoredToken() {
193
+ try {
194
+ return localStorage.getItem(TOKEN_KEY);
195
+ } catch {
196
+ return null;
197
+ }
198
+ }
199
+ function setStoredToken(token) {
200
+ try {
201
+ localStorage.setItem(TOKEN_KEY, token);
202
+ } catch {
203
+ }
204
+ }
205
+ function removeStoredToken() {
206
+ try {
207
+ localStorage.removeItem(TOKEN_KEY);
208
+ } catch {
209
+ }
210
+ }
211
+ function decodeJWT(token) {
212
+ try {
213
+ const parts = token.split(".");
214
+ if (parts.length !== 3) return null;
215
+ const payload = JSON.parse(atob(parts[1].replace(/-/g, "+").replace(/_/g, "/")));
216
+ return payload;
217
+ } catch {
218
+ return null;
219
+ }
220
+ }
221
+ function getRefreshDelay(token) {
222
+ const decoded = decodeJWT(token);
223
+ if (!decoded?.exp || !decoded.iat) return null;
224
+ const ttl = (decoded.exp - decoded.iat) * 1e3;
225
+ const elapsed = Date.now() - decoded.iat * 1e3;
226
+ const delay = ttl * 0.8 - elapsed;
227
+ return delay > 0 ? delay : 0;
228
+ }
229
+
230
+ // src/hooks/use-forge-connect.ts
231
+ var import_react2 = require("react");
232
+ function useForgeConnect() {
233
+ const ctx = (0, import_react2.useContext)(ForgeConnectContext);
234
+ if (!ctx) {
235
+ throw new Error("useForgeConnect must be used within a <ForgeConnectProvider>");
236
+ }
237
+ return ctx;
238
+ }
239
+
240
+ // src/components/modal-overlay.tsx
241
+ var import_react3 = require("react");
242
+ var import_react_dom = require("react-dom");
243
+ var import_jsx_runtime = require("react/jsx-runtime");
244
+ function ModalOverlay({ isOpen, onClose, children }) {
245
+ const overlayRef = (0, import_react3.useRef)(null);
246
+ (0, import_react3.useEffect)(() => {
247
+ if (!isOpen) return;
248
+ const handleKeyDown = (e) => {
249
+ if (e.key === "Escape") onClose();
250
+ };
251
+ document.addEventListener("keydown", handleKeyDown);
252
+ document.body.style.overflow = "hidden";
253
+ return () => {
254
+ document.removeEventListener("keydown", handleKeyDown);
255
+ document.body.style.overflow = "";
256
+ };
257
+ }, [isOpen, onClose]);
258
+ if (!isOpen) return null;
259
+ const handleBackdropClick = (e) => {
260
+ if (e.target === overlayRef.current) onClose();
261
+ };
262
+ return (0, import_react_dom.createPortal)(
263
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "fc-overlay", ref: overlayRef, onClick: handleBackdropClick, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "fc-modal", role: "dialog", "aria-modal": "true", children: [
264
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "fc-modal-close", onClick: onClose, "aria-label": "Close", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M15 5L5 15M5 5l10 10", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }) }) }),
265
+ children
266
+ ] }) }),
267
+ document.body
268
+ );
269
+ }
270
+
271
+ // src/components/tabs/email-password.tsx
272
+ var import_react4 = require("react");
273
+ var import_jsx_runtime2 = require("react/jsx-runtime");
274
+ function EmailLoginForm() {
275
+ const { loginWithEmail, setModalStep } = useForgeConnect();
276
+ const [email, setEmail] = (0, import_react4.useState)("");
277
+ const [password, setPassword] = (0, import_react4.useState)("");
278
+ const [error, setError] = (0, import_react4.useState)("");
279
+ const [loading, setLoading] = (0, import_react4.useState)(false);
280
+ const handleSubmit = async (e) => {
281
+ e.preventDefault();
282
+ setError("");
283
+ setLoading(true);
284
+ try {
285
+ await loginWithEmail(email, password);
286
+ } catch (err) {
287
+ setError(err instanceof Error ? err.message : "Login failed");
288
+ } finally {
289
+ setLoading(false);
290
+ }
291
+ };
292
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "fc-tab", children: [
293
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { className: "fc-tab-title", children: "Sign in with email" }),
294
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("form", { onSubmit: handleSubmit, className: "fc-form", children: [
295
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "fc-label", children: [
296
+ "Email",
297
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
298
+ "input",
299
+ {
300
+ type: "email",
301
+ className: "fc-input",
302
+ value: email,
303
+ onChange: (e) => setEmail(e.target.value),
304
+ placeholder: "you@example.com",
305
+ required: true,
306
+ autoComplete: "email"
307
+ }
308
+ )
309
+ ] }),
310
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "fc-label", children: [
311
+ "Password",
312
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
313
+ "input",
314
+ {
315
+ type: "password",
316
+ className: "fc-input",
317
+ value: password,
318
+ onChange: (e) => setPassword(e.target.value),
319
+ placeholder: "Enter your password",
320
+ required: true,
321
+ autoComplete: "current-password",
322
+ minLength: 8
323
+ }
324
+ )
325
+ ] }),
326
+ error && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "fc-error", children: error }),
327
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "submit", className: "fc-btn fc-btn-primary", disabled: loading, children: loading ? "Signing in..." : "Sign in" })
328
+ ] }),
329
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { className: "fc-switch", children: [
330
+ "Don't have an account?",
331
+ " ",
332
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: "fc-link", onClick: () => setModalStep("email-register"), children: "Sign up" })
333
+ ] })
334
+ ] });
335
+ }
336
+ function EmailRegisterForm() {
337
+ const { register, setModalStep } = useForgeConnect();
338
+ const [email, setEmail] = (0, import_react4.useState)("");
339
+ const [password, setPassword] = (0, import_react4.useState)("");
340
+ const [displayName, setDisplayName] = (0, import_react4.useState)("");
341
+ const [error, setError] = (0, import_react4.useState)("");
342
+ const [success, setSuccess] = (0, import_react4.useState)(false);
343
+ const [loading, setLoading] = (0, import_react4.useState)(false);
344
+ const handleSubmit = async (e) => {
345
+ e.preventDefault();
346
+ setError("");
347
+ setLoading(true);
348
+ try {
349
+ await register(email, password, displayName || void 0);
350
+ setSuccess(true);
351
+ } catch (err) {
352
+ setError(err instanceof Error ? err.message : "Registration failed");
353
+ } finally {
354
+ setLoading(false);
355
+ }
356
+ };
357
+ if (success) {
358
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "fc-tab", children: [
359
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { className: "fc-tab-title", children: "Check your email" }),
360
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { className: "fc-text", children: [
361
+ "We sent a verification link to ",
362
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("strong", { children: email }),
363
+ ". Click it to activate your account, then sign in."
364
+ ] }),
365
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: "fc-btn fc-btn-secondary", onClick: () => setModalStep("email-login"), children: "Back to sign in" })
366
+ ] });
367
+ }
368
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "fc-tab", children: [
369
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { className: "fc-tab-title", children: "Create an account" }),
370
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("form", { onSubmit: handleSubmit, className: "fc-form", children: [
371
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "fc-label", children: [
372
+ "Display name",
373
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
374
+ "input",
375
+ {
376
+ type: "text",
377
+ className: "fc-input",
378
+ value: displayName,
379
+ onChange: (e) => setDisplayName(e.target.value),
380
+ placeholder: "Your name",
381
+ autoComplete: "name"
382
+ }
383
+ )
384
+ ] }),
385
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "fc-label", children: [
386
+ "Email",
387
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
388
+ "input",
389
+ {
390
+ type: "email",
391
+ className: "fc-input",
392
+ value: email,
393
+ onChange: (e) => setEmail(e.target.value),
394
+ placeholder: "you@example.com",
395
+ required: true,
396
+ autoComplete: "email"
397
+ }
398
+ )
399
+ ] }),
400
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "fc-label", children: [
401
+ "Password",
402
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
403
+ "input",
404
+ {
405
+ type: "password",
406
+ className: "fc-input",
407
+ value: password,
408
+ onChange: (e) => setPassword(e.target.value),
409
+ placeholder: "8+ characters",
410
+ required: true,
411
+ autoComplete: "new-password",
412
+ minLength: 8
413
+ }
414
+ )
415
+ ] }),
416
+ error && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "fc-error", children: error }),
417
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "submit", className: "fc-btn fc-btn-primary", disabled: loading, children: loading ? "Creating account..." : "Create account" })
418
+ ] }),
419
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { className: "fc-switch", children: [
420
+ "Already have an account?",
421
+ " ",
422
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: "fc-link", onClick: () => setModalStep("email-login"), children: "Sign in" })
423
+ ] })
424
+ ] });
425
+ }
426
+
427
+ // src/components/tabs/email-otp.tsx
428
+ var import_react5 = require("react");
429
+ var import_jsx_runtime3 = require("react/jsx-runtime");
430
+ function EmailOtpForm() {
431
+ const { sendOtp, verifyOtp, setModalStep } = useForgeConnect();
432
+ const [email, setEmail] = (0, import_react5.useState)("");
433
+ const [code, setCode] = (0, import_react5.useState)(["", "", "", "", "", ""]);
434
+ const [step, setStep] = (0, import_react5.useState)("email");
435
+ const [error, setError] = (0, import_react5.useState)("");
436
+ const [loading, setLoading] = (0, import_react5.useState)(false);
437
+ const inputRefs = (0, import_react5.useRef)([]);
438
+ const handleSendCode = async (e) => {
439
+ e.preventDefault();
440
+ setError("");
441
+ setLoading(true);
442
+ try {
443
+ await sendOtp(email);
444
+ setStep("code");
445
+ } catch (err) {
446
+ setError(err instanceof Error ? err.message : "Failed to send code");
447
+ } finally {
448
+ setLoading(false);
449
+ }
450
+ };
451
+ const handleVerify = async (digits) => {
452
+ const codeStr = digits.join("");
453
+ if (codeStr.length !== 6) return;
454
+ setError("");
455
+ setLoading(true);
456
+ try {
457
+ await verifyOtp(email, codeStr);
458
+ } catch (err) {
459
+ setError(err instanceof Error ? err.message : "Invalid code");
460
+ setLoading(false);
461
+ }
462
+ };
463
+ const handleDigitChange = (index, value) => {
464
+ if (!/^\d*$/.test(value)) return;
465
+ const digit = value.slice(-1);
466
+ const newCode = [...code];
467
+ newCode[index] = digit;
468
+ setCode(newCode);
469
+ if (digit && index < 5) {
470
+ inputRefs.current[index + 1]?.focus();
471
+ }
472
+ if (newCode.every((d) => d !== "")) {
473
+ handleVerify(newCode);
474
+ }
475
+ };
476
+ const handleKeyDown = (index, e) => {
477
+ if (e.key === "Backspace" && !code[index] && index > 0) {
478
+ inputRefs.current[index - 1]?.focus();
479
+ }
480
+ };
481
+ const handlePaste = (e) => {
482
+ e.preventDefault();
483
+ const pasted = e.clipboardData.getData("text").replace(/\D/g, "").slice(0, 6);
484
+ if (!pasted) return;
485
+ const newCode = [...code];
486
+ for (let i = 0; i < pasted.length; i++) {
487
+ newCode[i] = pasted[i];
488
+ }
489
+ setCode(newCode);
490
+ if (newCode.every((d) => d !== "")) {
491
+ handleVerify(newCode);
492
+ }
493
+ };
494
+ if (step === "email") {
495
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "fc-tab", children: [
496
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { className: "fc-tab-title", children: "Passwordless sign in" }),
497
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("form", { onSubmit: handleSendCode, className: "fc-form", children: [
498
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { className: "fc-label", children: [
499
+ "Email",
500
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
501
+ "input",
502
+ {
503
+ type: "email",
504
+ className: "fc-input",
505
+ value: email,
506
+ onChange: (e) => setEmail(e.target.value),
507
+ placeholder: "you@example.com",
508
+ required: true,
509
+ autoComplete: "email"
510
+ }
511
+ )
512
+ ] }),
513
+ error && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "fc-error", children: error }),
514
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "submit", className: "fc-btn fc-btn-primary", disabled: loading, children: loading ? "Sending..." : "Send code" })
515
+ ] }),
516
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "fc-switch", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: "fc-link", onClick: () => setModalStep("method-select"), children: "Back" }) })
517
+ ] });
518
+ }
519
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "fc-tab", children: [
520
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { className: "fc-tab-title", children: "Enter verification code" }),
521
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { className: "fc-text", children: [
522
+ "We sent a 6-digit code to ",
523
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: email })
524
+ ] }),
525
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "fc-otp-inputs", children: code.map((digit, i) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
526
+ "input",
527
+ {
528
+ ref: (el) => {
529
+ inputRefs.current[i] = el;
530
+ },
531
+ type: "text",
532
+ inputMode: "numeric",
533
+ maxLength: 1,
534
+ className: "fc-otp-digit",
535
+ value: digit,
536
+ onChange: (e) => handleDigitChange(i, e.target.value),
537
+ onKeyDown: (e) => handleKeyDown(i, e),
538
+ onPaste: i === 0 ? handlePaste : void 0,
539
+ autoFocus: i === 0
540
+ },
541
+ i
542
+ )) }),
543
+ error && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "fc-error", children: error }),
544
+ loading && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "fc-text", children: "Verifying..." }),
545
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "fc-switch", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: "fc-link", onClick: () => {
546
+ setStep("email");
547
+ setCode(["", "", "", "", "", ""]);
548
+ setError("");
549
+ }, children: "Use a different email" }) })
550
+ ] });
551
+ }
552
+
553
+ // src/components/tabs/wallet-connect.tsx
554
+ var import_react6 = require("react");
555
+ var import_jsx_runtime4 = require("react/jsx-runtime");
556
+ function tryGetUseWallet() {
557
+ try {
558
+ const mod = require("@solana/wallet-adapter-react");
559
+ return mod.useWallet;
560
+ } catch {
561
+ return void 0;
562
+ }
563
+ }
564
+ var resolvedUseWallet = tryGetUseWallet();
565
+ function WalletConnectForm() {
566
+ const { setModalStep } = useForgeConnect();
567
+ if (resolvedUseWallet) {
568
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(WalletAdapterFlow, { useWallet: resolvedUseWallet });
569
+ }
570
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "fc-tab", children: [
571
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h3", { className: "fc-tab-title", children: "Connect wallet" }),
572
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { className: "fc-text", children: [
573
+ "Install a Solana wallet extension (Phantom, Solflare, etc.) and ensure",
574
+ " ",
575
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("code", { children: "@solana/wallet-adapter-react" }),
576
+ " is configured in your app."
577
+ ] }),
578
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "fc-text fc-text-muted", children: "Wallet connection requires the Solana wallet adapter provider in your app." }),
579
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "fc-switch", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", className: "fc-link", onClick: () => setModalStep("method-select"), children: "Back" }) })
580
+ ] });
581
+ }
582
+ function WalletAdapterFlow({ useWallet }) {
583
+ const { loginWithWallet, setModalStep } = useForgeConnect();
584
+ const wallet = useWallet();
585
+ const [error, setError] = (0, import_react6.useState)("");
586
+ const [loading, setLoading] = (0, import_react6.useState)(false);
587
+ const handleConnect = async () => {
588
+ if (!wallet.publicKey || !wallet.signMessage) {
589
+ setError("Please connect your wallet first");
590
+ return;
591
+ }
592
+ setError("");
593
+ setLoading(true);
594
+ try {
595
+ await loginWithWallet(
596
+ wallet.publicKey.toBase58(),
597
+ wallet.signMessage,
598
+ "solana"
599
+ );
600
+ } catch (err) {
601
+ setError(err instanceof Error ? err.message : "Wallet verification failed");
602
+ } finally {
603
+ setLoading(false);
604
+ }
605
+ };
606
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "fc-tab", children: [
607
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h3", { className: "fc-tab-title", children: "Connect wallet" }),
608
+ wallet.publicKey ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
609
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { className: "fc-text fc-wallet-address", children: [
610
+ wallet.publicKey.toBase58().slice(0, 4),
611
+ "...",
612
+ wallet.publicKey.toBase58().slice(-4)
613
+ ] }),
614
+ error && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "fc-error", children: error }),
615
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
616
+ "button",
617
+ {
618
+ type: "button",
619
+ className: "fc-btn fc-btn-primary",
620
+ onClick: handleConnect,
621
+ disabled: loading,
622
+ children: loading ? "Signing..." : "Sign to verify"
623
+ }
624
+ )
625
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
626
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "fc-wallet-list", children: wallet.wallets.filter((w) => w.readyState === "Installed").map((w) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
627
+ "button",
628
+ {
629
+ type: "button",
630
+ className: "fc-btn fc-btn-wallet",
631
+ onClick: async () => {
632
+ wallet.select(w.adapter.name);
633
+ try {
634
+ await wallet.connect();
635
+ } catch {
636
+ setError("Failed to connect wallet");
637
+ }
638
+ },
639
+ children: [
640
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("img", { src: w.adapter.icon, alt: "", className: "fc-wallet-icon" }),
641
+ w.adapter.name
642
+ ]
643
+ },
644
+ w.adapter.name
645
+ )) }),
646
+ wallet.wallets.filter((w) => w.readyState === "Installed").length === 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "fc-text", children: "No wallets detected. Install a Solana wallet extension." }),
647
+ error && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "fc-error", children: error })
648
+ ] }),
649
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "fc-switch", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", className: "fc-link", onClick: () => setModalStep("method-select"), children: "Back" }) })
650
+ ] });
651
+ }
652
+
653
+ // src/components/tabs/oauth-buttons.tsx
654
+ var import_jsx_runtime5 = require("react/jsx-runtime");
655
+ var PROVIDER_INFO = {
656
+ google: {
657
+ label: "Google",
658
+ 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>'
659
+ },
660
+ discord: {
661
+ label: "Discord",
662
+ 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>'
663
+ },
664
+ twitter: {
665
+ label: "Twitter",
666
+ 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>'
667
+ },
668
+ apple: {
669
+ label: "Apple",
670
+ 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>'
671
+ }
672
+ };
673
+ function OAuthButtons() {
674
+ const { config, loginWithOAuth } = useForgeConnect();
675
+ const providers = config.oauthProviders ?? [];
676
+ if (providers.length === 0) return null;
677
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "fc-oauth-buttons", children: providers.map((provider) => {
678
+ const info = PROVIDER_INFO[provider];
679
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
680
+ "button",
681
+ {
682
+ type: "button",
683
+ className: "fc-btn fc-btn-oauth",
684
+ onClick: () => loginWithOAuth(provider),
685
+ children: [
686
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "fc-oauth-icon", dangerouslySetInnerHTML: { __html: info.icon } }),
687
+ "Continue with ",
688
+ info.label
689
+ ]
690
+ },
691
+ provider
692
+ );
693
+ }) });
694
+ }
695
+
696
+ // src/components/login-modal.tsx
697
+ var import_jsx_runtime6 = require("react/jsx-runtime");
698
+ function LoginModal() {
699
+ const { modal, closeModal, setModalStep, config } = useForgeConnect();
700
+ const renderStep = () => {
701
+ switch (modal.step) {
702
+ case "email-login":
703
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(EmailLoginForm, {});
704
+ case "email-register":
705
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(EmailRegisterForm, {});
706
+ case "email-otp":
707
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(EmailOtpForm, {});
708
+ case "wallet-connect":
709
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(WalletConnectForm, {});
710
+ case "method-select":
711
+ default:
712
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MethodSelect, {});
713
+ }
714
+ };
715
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ModalOverlay, { isOpen: modal.isOpen, onClose: closeModal, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
716
+ "div",
717
+ {
718
+ className: "fc-modal-content",
719
+ style: {
720
+ "--fc-accent": config.appearance?.accentColor ?? "#8b5cf6"
721
+ },
722
+ "data-theme": config.appearance?.theme ?? "light",
723
+ children: [
724
+ config.appearance?.logo && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("img", { src: config.appearance.logo, alt: "", className: "fc-logo" }),
725
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h2", { className: "fc-modal-title", children: config.appearance?.title ?? "Sign in" }),
726
+ renderStep()
727
+ ]
728
+ }
729
+ ) });
730
+ }
731
+ function MethodSelect() {
732
+ const { setModalStep, config } = useForgeConnect();
733
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "fc-method-select", children: [
734
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(OAuthButtons, {}),
735
+ (config.oauthProviders?.length ?? 0) > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "fc-divider", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: "or" }) }),
736
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
737
+ "button",
738
+ {
739
+ type: "button",
740
+ className: "fc-btn fc-btn-method",
741
+ onClick: () => setModalStep("email-login"),
742
+ children: [
743
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", className: "fc-method-icon", children: [
744
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("rect", { x: "2", y: "4", width: "16", height: "12", rx: "2", stroke: "currentColor", strokeWidth: "1.5" }),
745
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M2 6l8 5 8-5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
746
+ ] }),
747
+ "Continue with email"
748
+ ]
749
+ }
750
+ ),
751
+ config.passwordlessLogin !== false && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
752
+ "button",
753
+ {
754
+ type: "button",
755
+ className: "fc-btn fc-btn-method",
756
+ onClick: () => setModalStep("email-otp"),
757
+ children: [
758
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", className: "fc-method-icon", children: [
759
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("rect", { x: "3", y: "6", width: "14", height: "9", rx: "1.5", stroke: "currentColor", strokeWidth: "1.5" }),
760
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("circle", { cx: "6.5", cy: "10.5", r: "1", fill: "currentColor" }),
761
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("circle", { cx: "10", cy: "10.5", r: "1", fill: "currentColor" }),
762
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("circle", { cx: "13.5", cy: "10.5", r: "1", fill: "currentColor" })
763
+ ] }),
764
+ "Continue with code"
765
+ ]
766
+ }
767
+ ),
768
+ config.walletLogin !== false && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
769
+ "button",
770
+ {
771
+ type: "button",
772
+ className: "fc-btn fc-btn-method",
773
+ onClick: () => setModalStep("wallet-connect"),
774
+ children: [
775
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", className: "fc-method-icon", children: [
776
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("rect", { x: "2", y: "5", width: "16", height: "11", rx: "2", stroke: "currentColor", strokeWidth: "1.5" }),
777
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("rect", { x: "13", y: "9", width: "5", height: "3", rx: "1", stroke: "currentColor", strokeWidth: "1.5" })
778
+ ] }),
779
+ "Continue with wallet"
780
+ ]
781
+ }
782
+ )
783
+ ] });
784
+ }
785
+
786
+ // src/provider.tsx
787
+ var import_jsx_runtime7 = require("react/jsx-runtime");
788
+ function ForgeConnectProvider({ config, children, onLogin, onLogout }) {
789
+ const [auth, setAuth] = (0, import_react7.useState)({
790
+ status: "loading",
791
+ user: null,
792
+ accessToken: null
793
+ });
794
+ const [modal, setModal] = (0, import_react7.useState)({
795
+ isOpen: false,
796
+ step: "method-select"
797
+ });
798
+ const apiRef = (0, import_react7.useRef)(createApiClient(config.apiUrl));
799
+ const refreshTimerRef = (0, import_react7.useRef)(null);
800
+ const api = apiRef.current;
801
+ const scheduleRefresh = (0, import_react7.useCallback)((token) => {
802
+ if (refreshTimerRef.current) clearTimeout(refreshTimerRef.current);
803
+ const delay = getRefreshDelay(token);
804
+ if (delay === null) return;
805
+ refreshTimerRef.current = setTimeout(async () => {
806
+ try {
807
+ const { accessToken } = await api.refresh();
808
+ setStoredToken(accessToken);
809
+ setAuth((prev) => ({ ...prev, accessToken }));
810
+ scheduleRefresh(accessToken);
811
+ } catch {
812
+ removeStoredToken();
813
+ setAuth({ status: "unauthenticated", user: null, accessToken: null });
814
+ }
815
+ }, delay);
816
+ }, [api]);
817
+ (0, import_react7.useEffect)(() => {
818
+ const init = async () => {
819
+ const token = getStoredToken();
820
+ if (!token) {
821
+ setAuth({ status: "unauthenticated", user: null, accessToken: null });
822
+ return;
823
+ }
824
+ try {
825
+ const user = await api.getMe(token);
826
+ setAuth({ status: "authenticated", user, accessToken: token });
827
+ scheduleRefresh(token);
828
+ } catch (err) {
829
+ if (err instanceof ForgeConnectApiError && err.status === 401) {
830
+ try {
831
+ const { accessToken } = await api.refresh();
832
+ const user = await api.getMe(accessToken);
833
+ setStoredToken(accessToken);
834
+ setAuth({ status: "authenticated", user, accessToken });
835
+ scheduleRefresh(accessToken);
836
+ } catch {
837
+ removeStoredToken();
838
+ setAuth({ status: "unauthenticated", user: null, accessToken: null });
839
+ }
840
+ } else {
841
+ removeStoredToken();
842
+ setAuth({ status: "unauthenticated", user: null, accessToken: null });
843
+ }
844
+ }
845
+ };
846
+ init();
847
+ return () => {
848
+ if (refreshTimerRef.current) clearTimeout(refreshTimerRef.current);
849
+ };
850
+ }, [api, scheduleRefresh]);
851
+ (0, import_react7.useEffect)(() => {
852
+ if (window.location.pathname === "/__fc_oauth_callback") {
853
+ const params = new URLSearchParams(window.location.search);
854
+ const token = params.get("fc_token");
855
+ if (token && window.opener) {
856
+ window.opener.postMessage(
857
+ { type: "fc_oauth_callback", accessToken: token },
858
+ window.location.origin
859
+ );
860
+ window.close();
861
+ return;
862
+ }
863
+ }
864
+ const handleMessage = async (event) => {
865
+ if (event.origin !== window.location.origin) return;
866
+ if (event.data?.type !== "fc_oauth_callback") return;
867
+ const token = event.data.accessToken;
868
+ if (!token) return;
869
+ setStoredToken(token);
870
+ try {
871
+ const user = await api.getMe(token);
872
+ setAuth({ status: "authenticated", user, accessToken: token });
873
+ scheduleRefresh(token);
874
+ setModal({ isOpen: false, step: "method-select" });
875
+ onLogin?.(user);
876
+ } catch {
877
+ removeStoredToken();
878
+ }
879
+ };
880
+ window.addEventListener("message", handleMessage);
881
+ return () => window.removeEventListener("message", handleMessage);
882
+ }, [api, config.apiUrl, scheduleRefresh, onLogin]);
883
+ const handleAuthSuccess = (0, import_react7.useCallback)(
884
+ async (token) => {
885
+ setStoredToken(token);
886
+ const user = await api.getMe(token);
887
+ setAuth({ status: "authenticated", user, accessToken: token });
888
+ scheduleRefresh(token);
889
+ setModal({ isOpen: false, step: "method-select" });
890
+ onLogin?.(user);
891
+ },
892
+ [api, scheduleRefresh, onLogin]
893
+ );
894
+ const loginWithEmail = (0, import_react7.useCallback)(
895
+ async (email, password) => {
896
+ const { accessToken } = await api.login(email, password);
897
+ await handleAuthSuccess(accessToken);
898
+ },
899
+ [api, handleAuthSuccess]
900
+ );
901
+ const register = (0, import_react7.useCallback)(
902
+ async (email, password, displayName) => {
903
+ await api.register(email, password, displayName);
904
+ },
905
+ [api]
906
+ );
907
+ const sendOtp = (0, import_react7.useCallback)(
908
+ async (email) => {
909
+ await api.sendOtp(email);
910
+ },
911
+ [api]
912
+ );
913
+ const verifyOtp = (0, import_react7.useCallback)(
914
+ async (email, code) => {
915
+ const { accessToken } = await api.verifyOtp(email, code);
916
+ await handleAuthSuccess(accessToken);
917
+ },
918
+ [api, handleAuthSuccess]
919
+ );
920
+ const loginWithWallet = (0, import_react7.useCallback)(
921
+ async (walletAddress, signMessage, chain = "solana") => {
922
+ const { challengeId, nonce } = await api.walletChallenge(walletAddress, chain);
923
+ const message = new TextEncoder().encode(nonce);
924
+ const signatureBytes = await signMessage(message);
925
+ const signature = chain === "solana" ? uint8ArrayToBase58(signatureBytes) : Array.from(signatureBytes).map((b) => b.toString(16).padStart(2, "0")).join("");
926
+ const { accessToken } = await api.walletVerify(challengeId, signature, walletAddress);
927
+ await handleAuthSuccess(accessToken);
928
+ },
929
+ [api, handleAuthSuccess]
930
+ );
931
+ const loginWithOAuth = (0, import_react7.useCallback)(
932
+ (provider) => {
933
+ const callbackUrl = `${config.apiUrl}/auth/oauth/${provider}`;
934
+ const redirectUri = encodeURIComponent(window.location.origin + "/__fc_oauth_callback");
935
+ const url = `${callbackUrl}?redirect_uri=${redirectUri}`;
936
+ const width = 500;
937
+ const height = 600;
938
+ const left = window.screenX + (window.innerWidth - width) / 2;
939
+ const top = window.screenY + (window.innerHeight - height) / 2;
940
+ window.open(url, "fc_oauth", `width=${width},height=${height},left=${left},top=${top}`);
941
+ },
942
+ [config.apiUrl]
943
+ );
944
+ const logout = (0, import_react7.useCallback)(async () => {
945
+ const token = auth.accessToken;
946
+ removeStoredToken();
947
+ if (refreshTimerRef.current) clearTimeout(refreshTimerRef.current);
948
+ setAuth({ status: "unauthenticated", user: null, accessToken: null });
949
+ onLogout?.();
950
+ if (token) {
951
+ try {
952
+ await api.logout(token);
953
+ } catch {
954
+ }
955
+ }
956
+ }, [auth.accessToken, api, onLogout]);
957
+ const logoutAll = (0, import_react7.useCallback)(async () => {
958
+ const token = auth.accessToken;
959
+ removeStoredToken();
960
+ if (refreshTimerRef.current) clearTimeout(refreshTimerRef.current);
961
+ setAuth({ status: "unauthenticated", user: null, accessToken: null });
962
+ onLogout?.();
963
+ if (token) {
964
+ try {
965
+ await api.logoutAll(token);
966
+ } catch {
967
+ }
968
+ }
969
+ }, [auth.accessToken, api, onLogout]);
970
+ const openModal = (0, import_react7.useCallback)(() => {
971
+ setModal({ isOpen: true, step: "method-select" });
972
+ }, []);
973
+ const closeModal = (0, import_react7.useCallback)(() => {
974
+ setModal({ isOpen: false, step: "method-select" });
975
+ }, []);
976
+ const setModalStep = (0, import_react7.useCallback)((step) => {
977
+ setModal((prev) => ({ ...prev, step }));
978
+ }, []);
979
+ const getAccessToken = (0, import_react7.useCallback)(() => auth.accessToken, [auth.accessToken]);
980
+ const value = {
981
+ auth,
982
+ modal,
983
+ config,
984
+ api,
985
+ loginWithEmail,
986
+ register,
987
+ sendOtp,
988
+ verifyOtp,
989
+ loginWithWallet,
990
+ loginWithOAuth,
991
+ logout,
992
+ logoutAll,
993
+ openModal,
994
+ closeModal,
995
+ setModalStep,
996
+ getAccessToken
997
+ };
998
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(ForgeConnectContext.Provider, { value, children: [
999
+ children,
1000
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(LoginModal, {})
1001
+ ] });
1002
+ }
1003
+ var BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
1004
+ function uint8ArrayToBase58(bytes) {
1005
+ const digits = [0];
1006
+ for (const byte of bytes) {
1007
+ let carry = byte;
1008
+ for (let j = 0; j < digits.length; j++) {
1009
+ carry += digits[j] << 8;
1010
+ digits[j] = carry % 58;
1011
+ carry = carry / 58 | 0;
1012
+ }
1013
+ while (carry > 0) {
1014
+ digits.push(carry % 58);
1015
+ carry = carry / 58 | 0;
1016
+ }
1017
+ }
1018
+ let str = "";
1019
+ for (let i = 0; i < bytes.length && bytes[i] === 0; i++) {
1020
+ str += "1";
1021
+ }
1022
+ for (let i = digits.length - 1; i >= 0; i--) {
1023
+ str += BASE58_ALPHABET[digits[i]];
1024
+ }
1025
+ return str;
1026
+ }
1027
+
1028
+ // src/components/login-button.tsx
1029
+ var import_jsx_runtime8 = require("react/jsx-runtime");
1030
+ function LoginButton({ className, label }) {
1031
+ const { auth, openModal, logout } = useForgeConnect();
1032
+ if (auth.status === "loading") {
1033
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: className ?? "fc-btn fc-btn-login", disabled: true, children: "Loading..." });
1034
+ }
1035
+ if (auth.status === "authenticated") {
1036
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: className ?? "fc-btn fc-btn-login", onClick: logout, children: label ?? "Log out" });
1037
+ }
1038
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: className ?? "fc-btn fc-btn-login", onClick: openModal, children: label ?? "Sign in" });
1039
+ }
1040
+
1041
+ // src/hooks/use-user.ts
1042
+ var import_react8 = require("react");
1043
+ function useUser() {
1044
+ const { auth, api, getAccessToken } = useForgeConnect();
1045
+ const [authMethods, setAuthMethods] = (0, import_react8.useState)(null);
1046
+ const [loading, setLoading] = (0, import_react8.useState)(false);
1047
+ const updateProfile = (0, import_react8.useCallback)(
1048
+ async (data) => {
1049
+ const token = getAccessToken();
1050
+ if (!token) throw new Error("Not authenticated");
1051
+ return api.updateMe(token, data);
1052
+ },
1053
+ [api, getAccessToken]
1054
+ );
1055
+ const fetchAuthMethods = (0, import_react8.useCallback)(async () => {
1056
+ const token = getAccessToken();
1057
+ if (!token) throw new Error("Not authenticated");
1058
+ setLoading(true);
1059
+ try {
1060
+ const methods = await api.getAuthMethods(token);
1061
+ setAuthMethods(methods);
1062
+ return methods;
1063
+ } finally {
1064
+ setLoading(false);
1065
+ }
1066
+ }, [api, getAccessToken]);
1067
+ const linkAuthMethod = (0, import_react8.useCallback)(
1068
+ async (data) => {
1069
+ const token = getAccessToken();
1070
+ if (!token) throw new Error("Not authenticated");
1071
+ await api.linkAuthMethod(token, data);
1072
+ await fetchAuthMethods();
1073
+ },
1074
+ [api, getAccessToken, fetchAuthMethods]
1075
+ );
1076
+ const unlinkAuthMethod = (0, import_react8.useCallback)(
1077
+ async (id) => {
1078
+ const token = getAccessToken();
1079
+ if (!token) throw new Error("Not authenticated");
1080
+ await api.unlinkAuthMethod(token, id);
1081
+ await fetchAuthMethods();
1082
+ },
1083
+ [api, getAccessToken, fetchAuthMethods]
1084
+ );
1085
+ return {
1086
+ user: auth.user,
1087
+ authMethods,
1088
+ loading,
1089
+ updateProfile,
1090
+ fetchAuthMethods,
1091
+ linkAuthMethod,
1092
+ unlinkAuthMethod
1093
+ };
1094
+ }
1095
+
1096
+ // src/hooks/use-wallets.ts
1097
+ var import_react9 = require("react");
1098
+ function useWallets() {
1099
+ const { api, getAccessToken } = useForgeConnect();
1100
+ const [wallets, setWallets] = (0, import_react9.useState)(null);
1101
+ const [loading, setLoading] = (0, import_react9.useState)(false);
1102
+ const fetchWallets = (0, import_react9.useCallback)(async () => {
1103
+ const token = getAccessToken();
1104
+ if (!token) throw new Error("Not authenticated");
1105
+ setLoading(true);
1106
+ try {
1107
+ const data = await api.getWallets(token);
1108
+ setWallets(data);
1109
+ return data;
1110
+ } finally {
1111
+ setLoading(false);
1112
+ }
1113
+ }, [api, getAccessToken]);
1114
+ const updateWallet = (0, import_react9.useCallback)(
1115
+ async (id, data) => {
1116
+ const token = getAccessToken();
1117
+ if (!token) throw new Error("Not authenticated");
1118
+ await api.updateWallet(token, id, data);
1119
+ await fetchWallets();
1120
+ },
1121
+ [api, getAccessToken, fetchWallets]
1122
+ );
1123
+ return {
1124
+ wallets,
1125
+ loading,
1126
+ fetchWallets,
1127
+ updateWallet
1128
+ };
1129
+ }
1130
+
1131
+ // src/hooks/use-sessions.ts
1132
+ var import_react10 = require("react");
1133
+ function useSessions() {
1134
+ const { api, getAccessToken } = useForgeConnect();
1135
+ const [sessions, setSessions] = (0, import_react10.useState)(null);
1136
+ const [loading, setLoading] = (0, import_react10.useState)(false);
1137
+ const fetchSessions = (0, import_react10.useCallback)(async () => {
1138
+ const token = getAccessToken();
1139
+ if (!token) throw new Error("Not authenticated");
1140
+ setLoading(true);
1141
+ try {
1142
+ const data = await api.getSessions(token);
1143
+ setSessions(data);
1144
+ return data;
1145
+ } finally {
1146
+ setLoading(false);
1147
+ }
1148
+ }, [api, getAccessToken]);
1149
+ const revokeSession = (0, import_react10.useCallback)(
1150
+ async (id) => {
1151
+ const token = getAccessToken();
1152
+ if (!token) throw new Error("Not authenticated");
1153
+ await api.revokeSession(token, id);
1154
+ await fetchSessions();
1155
+ },
1156
+ [api, getAccessToken, fetchSessions]
1157
+ );
1158
+ return {
1159
+ sessions,
1160
+ loading,
1161
+ fetchSessions,
1162
+ revokeSession
1163
+ };
1164
+ }
1165
+ // Annotate the CommonJS export names for ESM import in node:
1166
+ 0 && (module.exports = {
1167
+ ForgeConnectApiError,
1168
+ ForgeConnectContext,
1169
+ ForgeConnectProvider,
1170
+ LoginButton,
1171
+ LoginModal,
1172
+ ModalOverlay,
1173
+ createApiClient,
1174
+ useForgeConnect,
1175
+ useSessions,
1176
+ useUser,
1177
+ useWallets
1178
+ });
1179
+ //# sourceMappingURL=index.cjs.map