shogun-button-react 1.0.0 → 1.3.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/components/ShogunButton.d.ts +20 -3
- package/dist/components/ShogunButton.js +443 -57
- package/dist/connector.d.ts +2 -0
- package/dist/connector.js +65 -0
- package/dist/index.css +756 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/styles/index.css +36 -3
- package/dist/types/index.d.ts +1 -2
- package/dist/types/index.js +2 -2
- package/package.json +13 -14
- package/src/styles/index.css +36 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import React, { useContext, useState, createContext, useEffect } from "react";
|
|
1
|
+
import React, { useContext, useState, createContext, useEffect, useRef } from "react";
|
|
2
2
|
import { Observable } from "rxjs";
|
|
3
|
-
import "../types.js"; // Import type file to extend definitions
|
|
3
|
+
import "../types/index.js"; // Import type file to extend definitions
|
|
4
4
|
import "../styles/index.css";
|
|
5
5
|
// Default context
|
|
6
6
|
const defaultShogunContext = {
|
|
@@ -16,6 +16,8 @@ const defaultShogunContext = {
|
|
|
16
16
|
setProvider: () => false,
|
|
17
17
|
hasPlugin: () => false,
|
|
18
18
|
getPlugin: () => undefined,
|
|
19
|
+
exportGunPair: async () => "",
|
|
20
|
+
importGunPair: async () => false,
|
|
19
21
|
};
|
|
20
22
|
// Create context using React's createContext directly
|
|
21
23
|
const ShogunContext = createContext(defaultShogunContext);
|
|
@@ -24,19 +26,48 @@ export const useShogun = () => useContext(ShogunContext);
|
|
|
24
26
|
// Provider component
|
|
25
27
|
export function ShogunButtonProvider({ children, sdk, options, onLoginSuccess, onSignupSuccess, onError, }) {
|
|
26
28
|
// Use React's useState directly
|
|
27
|
-
const [isLoggedIn, setIsLoggedIn] = useState(
|
|
29
|
+
const [isLoggedIn, setIsLoggedIn] = useState(false);
|
|
28
30
|
const [userPub, setUserPub] = useState(null);
|
|
29
31
|
const [username, setUsername] = useState(null);
|
|
30
32
|
// Effetto per gestire l'inizializzazione e pulizia
|
|
31
33
|
useEffect(() => {
|
|
32
|
-
|
|
33
|
-
if (sdk
|
|
34
|
-
|
|
34
|
+
var _a, _b;
|
|
35
|
+
if (!sdk)
|
|
36
|
+
return;
|
|
37
|
+
const handleLogin = (authResult) => {
|
|
38
|
+
var _a, _b;
|
|
39
|
+
const pub = authResult.pub || ((_b = (_a = sdk.gun.user()) === null || _a === void 0 ? void 0 : _a.is) === null || _b === void 0 ? void 0 : _b.pub);
|
|
40
|
+
if (pub) {
|
|
41
|
+
setIsLoggedIn(true);
|
|
42
|
+
setUserPub(pub);
|
|
43
|
+
setUsername(authResult.alias || pub.slice(0, 8) + '...');
|
|
44
|
+
if (onLoginSuccess && authResult.method !== 'recall') {
|
|
45
|
+
onLoginSuccess({
|
|
46
|
+
userPub: pub,
|
|
47
|
+
username: authResult.alias || pub.slice(0, 8) + '...',
|
|
48
|
+
authMethod: authResult.method,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
const handleLogout = () => {
|
|
54
|
+
setIsLoggedIn(false);
|
|
55
|
+
setUserPub(null);
|
|
56
|
+
setUsername(null);
|
|
57
|
+
};
|
|
58
|
+
if (sdk.isLoggedIn()) {
|
|
59
|
+
const pub = (_b = (_a = sdk.gun.user()) === null || _a === void 0 ? void 0 : _a.is) === null || _b === void 0 ? void 0 : _b.pub;
|
|
60
|
+
if (pub) {
|
|
61
|
+
handleLogin({ pub, method: 'recall' });
|
|
62
|
+
}
|
|
35
63
|
}
|
|
64
|
+
sdk.on('auth:login', handleLogin);
|
|
65
|
+
sdk.on('auth:logout', handleLogout);
|
|
36
66
|
return () => {
|
|
37
|
-
|
|
67
|
+
sdk.off('auth:login', handleLogin);
|
|
68
|
+
sdk.off('auth:logout', handleLogout);
|
|
38
69
|
};
|
|
39
|
-
}, [sdk]);
|
|
70
|
+
}, [sdk, onLoginSuccess]);
|
|
40
71
|
// RxJS observe method
|
|
41
72
|
const observe = (path) => {
|
|
42
73
|
if (!sdk) {
|
|
@@ -58,6 +89,31 @@ export function ShogunButtonProvider({ children, sdk, options, onLoginSuccess, o
|
|
|
58
89
|
username = args[0];
|
|
59
90
|
result = await sdk.login(args[0], args[1]);
|
|
60
91
|
break;
|
|
92
|
+
case "pair":
|
|
93
|
+
// New pair authentication method
|
|
94
|
+
const pair = args[0];
|
|
95
|
+
if (!pair || typeof pair !== 'object') {
|
|
96
|
+
throw new Error("Invalid pair data provided");
|
|
97
|
+
}
|
|
98
|
+
result = await new Promise((resolve, reject) => {
|
|
99
|
+
sdk.gun.user().auth(pair, (ack) => {
|
|
100
|
+
if (ack.err) {
|
|
101
|
+
reject(new Error(`Pair authentication failed: ${ack.err}`));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const pub = ack.pub || pair.pub;
|
|
105
|
+
const alias = ack.alias || `user_${pub === null || pub === void 0 ? void 0 : pub.substring(0, 8)}`;
|
|
106
|
+
resolve({
|
|
107
|
+
success: true,
|
|
108
|
+
userPub: pub,
|
|
109
|
+
alias: alias,
|
|
110
|
+
method: 'pair'
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
username = result.alias;
|
|
115
|
+
authMethod = "pair";
|
|
116
|
+
break;
|
|
61
117
|
case "webauthn":
|
|
62
118
|
username = args[0];
|
|
63
119
|
const webauthn = sdk.getPlugin("webauthn");
|
|
@@ -246,6 +302,70 @@ export function ShogunButtonProvider({ children, sdk, options, onLoginSuccess, o
|
|
|
246
302
|
const getPlugin = (name) => {
|
|
247
303
|
return sdk ? sdk.getPlugin(name) : undefined;
|
|
248
304
|
};
|
|
305
|
+
// Export Gun pair functionality
|
|
306
|
+
const exportGunPair = async (password) => {
|
|
307
|
+
var _a;
|
|
308
|
+
if (!sdk) {
|
|
309
|
+
throw new Error("SDK not initialized");
|
|
310
|
+
}
|
|
311
|
+
if (!isLoggedIn) {
|
|
312
|
+
throw new Error("User not authenticated");
|
|
313
|
+
}
|
|
314
|
+
try {
|
|
315
|
+
const user = sdk.gun.user();
|
|
316
|
+
const pair = (_a = user === null || user === void 0 ? void 0 : user._) === null || _a === void 0 ? void 0 : _a.sea;
|
|
317
|
+
if (!pair) {
|
|
318
|
+
throw new Error("No Gun pair available for current user");
|
|
319
|
+
}
|
|
320
|
+
let pairData = JSON.stringify(pair);
|
|
321
|
+
// If password provided, encrypt the pair
|
|
322
|
+
if (password && password.trim()) {
|
|
323
|
+
// Use Gun's SEA for encryption if available
|
|
324
|
+
if (window.SEA && window.SEA.encrypt) {
|
|
325
|
+
pairData = await window.SEA.encrypt(pairData, password);
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
console.warn("SEA encryption not available, exporting unencrypted");
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return pairData;
|
|
332
|
+
}
|
|
333
|
+
catch (error) {
|
|
334
|
+
throw new Error(`Failed to export Gun pair: ${error.message}`);
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
// Import Gun pair functionality
|
|
338
|
+
const importGunPair = async (pairData, password) => {
|
|
339
|
+
if (!sdk) {
|
|
340
|
+
throw new Error("SDK not initialized");
|
|
341
|
+
}
|
|
342
|
+
try {
|
|
343
|
+
let dataString = pairData;
|
|
344
|
+
// If password provided, decrypt the pair
|
|
345
|
+
if (password && password.trim()) {
|
|
346
|
+
if (window.SEA && window.SEA.decrypt) {
|
|
347
|
+
dataString = await window.SEA.decrypt(pairData, password);
|
|
348
|
+
if (!dataString) {
|
|
349
|
+
throw new Error("Failed to decrypt pair data - wrong password?");
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
else {
|
|
353
|
+
console.warn("SEA decryption not available, assuming unencrypted data");
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
const pair = JSON.parse(dataString);
|
|
357
|
+
// Validate pair structure
|
|
358
|
+
if (!pair.pub || !pair.priv || !pair.epub || !pair.epriv) {
|
|
359
|
+
throw new Error("Invalid pair structure - missing required keys");
|
|
360
|
+
}
|
|
361
|
+
// Authenticate with the imported pair
|
|
362
|
+
const result = await login("pair", pair);
|
|
363
|
+
return result.success;
|
|
364
|
+
}
|
|
365
|
+
catch (error) {
|
|
366
|
+
throw new Error(`Failed to import Gun pair: ${error.message}`);
|
|
367
|
+
}
|
|
368
|
+
};
|
|
249
369
|
// Provide the context value to children
|
|
250
370
|
return (React.createElement(ShogunContext.Provider, { value: {
|
|
251
371
|
sdk,
|
|
@@ -260,6 +380,8 @@ export function ShogunButtonProvider({ children, sdk, options, onLoginSuccess, o
|
|
|
260
380
|
setProvider,
|
|
261
381
|
hasPlugin,
|
|
262
382
|
getPlugin,
|
|
383
|
+
exportGunPair,
|
|
384
|
+
importGunPair,
|
|
263
385
|
} }, children));
|
|
264
386
|
}
|
|
265
387
|
// SVG Icons Components
|
|
@@ -298,20 +420,44 @@ const CloseIcon = () => (React.createElement("svg", { xmlns: "http://www.w3.org/
|
|
|
298
420
|
// Component for Shogun login button
|
|
299
421
|
export const ShogunButton = (() => {
|
|
300
422
|
const Button = () => {
|
|
301
|
-
const { isLoggedIn, username, logout, login, signUp, sdk, options } = useShogun();
|
|
423
|
+
const { isLoggedIn, username, logout, login, signUp, sdk, options, exportGunPair, importGunPair } = useShogun();
|
|
302
424
|
// Form states
|
|
303
425
|
const [modalIsOpen, setModalIsOpen] = useState(false);
|
|
304
426
|
const [formUsername, setFormUsername] = useState("");
|
|
305
427
|
const [formPassword, setFormPassword] = useState("");
|
|
306
428
|
const [formPasswordConfirm, setFormPasswordConfirm] = useState("");
|
|
429
|
+
const [formHint, setFormHint] = useState("");
|
|
430
|
+
const [formSecurityQuestion] = useState("What is your favorite color?"); // Hardcoded for now
|
|
431
|
+
const [formSecurityAnswer, setFormSecurityAnswer] = useState("");
|
|
307
432
|
const [formMode, setFormMode] = useState("login");
|
|
433
|
+
const [authView, setAuthView] = useState("options");
|
|
308
434
|
const [error, setError] = useState("");
|
|
309
435
|
const [loading, setLoading] = useState(false);
|
|
310
436
|
const [dropdownOpen, setDropdownOpen] = useState(false);
|
|
437
|
+
const [recoveredHint, setRecoveredHint] = useState("");
|
|
438
|
+
const [exportPassword, setExportPassword] = useState("");
|
|
439
|
+
const [importPassword, setImportPassword] = useState("");
|
|
440
|
+
const [importPairData, setImportPairData] = useState("");
|
|
441
|
+
const [exportedPair, setExportedPair] = useState("");
|
|
442
|
+
const dropdownRef = useRef(null);
|
|
443
|
+
// Handle click outside to close dropdown
|
|
444
|
+
useEffect(() => {
|
|
445
|
+
const handleClickOutside = (event) => {
|
|
446
|
+
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
|
|
447
|
+
setDropdownOpen(false);
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
if (dropdownOpen) {
|
|
451
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
452
|
+
return () => {
|
|
453
|
+
document.removeEventListener('mousedown', handleClickOutside);
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
}, [dropdownOpen]);
|
|
311
457
|
// If already logged in, show only logout button
|
|
312
458
|
if (isLoggedIn && username) {
|
|
313
459
|
return (React.createElement("div", { className: "shogun-logged-in-container" },
|
|
314
|
-
React.createElement("div", { className: "shogun-dropdown" },
|
|
460
|
+
React.createElement("div", { className: "shogun-dropdown", ref: dropdownRef },
|
|
315
461
|
React.createElement("button", { className: "shogun-button shogun-logged-in", onClick: () => setDropdownOpen(!dropdownOpen) },
|
|
316
462
|
React.createElement("div", { className: "shogun-avatar" }, username.substring(0, 2).toUpperCase()),
|
|
317
463
|
React.createElement("span", { className: "shogun-username" }, username.length > 12
|
|
@@ -326,13 +472,21 @@ export const ShogunButton = (() => {
|
|
|
326
472
|
: username))),
|
|
327
473
|
React.createElement("div", { className: "shogun-dropdown-item", onClick: logout },
|
|
328
474
|
React.createElement(LogoutIcon, null),
|
|
329
|
-
React.createElement("span", null, "Disconnect"))
|
|
475
|
+
React.createElement("span", null, "Disconnect")),
|
|
476
|
+
React.createElement("div", { className: "shogun-dropdown-item", onClick: () => {
|
|
477
|
+
setDropdownOpen(false);
|
|
478
|
+
setAuthView("export");
|
|
479
|
+
setModalIsOpen(true);
|
|
480
|
+
} },
|
|
481
|
+
React.createElement("span", null, "\uD83D\uDCE4"),
|
|
482
|
+
React.createElement("span", null, "Export Pair")))))));
|
|
330
483
|
}
|
|
331
484
|
// Event handlers
|
|
332
485
|
const handleAuth = async (method, ...args) => {
|
|
333
486
|
setError("");
|
|
334
487
|
setLoading(true);
|
|
335
488
|
try {
|
|
489
|
+
// Use formMode to determine whether to call login or signUp
|
|
336
490
|
const action = formMode === "login" ? login : signUp;
|
|
337
491
|
const result = await action(method, ...args);
|
|
338
492
|
if (result && !result.success && result.error) {
|
|
@@ -352,9 +506,33 @@ export const ShogunButton = (() => {
|
|
|
352
506
|
setLoading(false);
|
|
353
507
|
}
|
|
354
508
|
};
|
|
355
|
-
const handleSubmit = (e) => {
|
|
509
|
+
const handleSubmit = async (e) => {
|
|
356
510
|
e.preventDefault();
|
|
357
|
-
|
|
511
|
+
setError("");
|
|
512
|
+
setLoading(true);
|
|
513
|
+
try {
|
|
514
|
+
if (formMode === "signup") {
|
|
515
|
+
const result = await signUp("password", formUsername, formPassword, formPasswordConfirm);
|
|
516
|
+
if (result && result.success) {
|
|
517
|
+
if (sdk === null || sdk === void 0 ? void 0 : sdk.gundb) {
|
|
518
|
+
await sdk.gundb.setPasswordHint(formUsername, formPassword, formHint, [formSecurityQuestion], [formSecurityAnswer]);
|
|
519
|
+
}
|
|
520
|
+
setModalIsOpen(false);
|
|
521
|
+
}
|
|
522
|
+
else if (result && result.error) {
|
|
523
|
+
setError(result.error);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
else {
|
|
527
|
+
await handleAuth("password", formUsername, formPassword);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
catch (e) {
|
|
531
|
+
setError(e.message || "An unexpected error occurred.");
|
|
532
|
+
}
|
|
533
|
+
finally {
|
|
534
|
+
setLoading(false);
|
|
535
|
+
}
|
|
358
536
|
};
|
|
359
537
|
const handleWeb3Auth = () => handleAuth("web3");
|
|
360
538
|
const handleWebAuthnAuth = () => {
|
|
@@ -370,15 +548,89 @@ export const ShogunButton = (() => {
|
|
|
370
548
|
};
|
|
371
549
|
const handleNostrAuth = () => handleAuth("nostr");
|
|
372
550
|
const handleOAuth = (provider) => handleAuth("oauth", provider);
|
|
551
|
+
const handleRecover = async () => {
|
|
552
|
+
setError("");
|
|
553
|
+
setLoading(true);
|
|
554
|
+
try {
|
|
555
|
+
if (!(sdk === null || sdk === void 0 ? void 0 : sdk.gundb)) {
|
|
556
|
+
throw new Error("SDK not ready");
|
|
557
|
+
}
|
|
558
|
+
const result = await sdk.gundb.forgotPassword(formUsername, [
|
|
559
|
+
formSecurityAnswer,
|
|
560
|
+
]);
|
|
561
|
+
if (result.success && result.hint) {
|
|
562
|
+
setRecoveredHint(result.hint);
|
|
563
|
+
setAuthView("showHint");
|
|
564
|
+
}
|
|
565
|
+
else {
|
|
566
|
+
setError(result.error || "Could not recover hint.");
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
catch (e) {
|
|
570
|
+
setError(e.message || "An unexpected error occurred.");
|
|
571
|
+
}
|
|
572
|
+
finally {
|
|
573
|
+
setLoading(false);
|
|
574
|
+
}
|
|
575
|
+
};
|
|
576
|
+
const handleExportPair = async () => {
|
|
577
|
+
setError("");
|
|
578
|
+
setLoading(true);
|
|
579
|
+
try {
|
|
580
|
+
const pairData = await exportGunPair(exportPassword || undefined);
|
|
581
|
+
setExportedPair(pairData);
|
|
582
|
+
// Copy to clipboard
|
|
583
|
+
if (navigator.clipboard) {
|
|
584
|
+
await navigator.clipboard.writeText(pairData);
|
|
585
|
+
alert("Pair exported and copied to clipboard!");
|
|
586
|
+
}
|
|
587
|
+
else {
|
|
588
|
+
alert("Pair exported! Please copy it manually from the text area.");
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
catch (e) {
|
|
592
|
+
setError(e.message || "Failed to export pair");
|
|
593
|
+
}
|
|
594
|
+
finally {
|
|
595
|
+
setLoading(false);
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
const handleImportPair = async () => {
|
|
599
|
+
setError("");
|
|
600
|
+
setLoading(true);
|
|
601
|
+
try {
|
|
602
|
+
if (!importPairData.trim()) {
|
|
603
|
+
throw new Error("Please enter pair data");
|
|
604
|
+
}
|
|
605
|
+
const success = await importGunPair(importPairData, importPassword || undefined);
|
|
606
|
+
if (success) {
|
|
607
|
+
setModalIsOpen(false);
|
|
608
|
+
alert("Pair imported successfully! You are now logged in.");
|
|
609
|
+
}
|
|
610
|
+
else {
|
|
611
|
+
throw new Error("Failed to import pair");
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
catch (e) {
|
|
615
|
+
setError(e.message || "Failed to import pair");
|
|
616
|
+
}
|
|
617
|
+
finally {
|
|
618
|
+
setLoading(false);
|
|
619
|
+
}
|
|
620
|
+
};
|
|
373
621
|
const resetForm = () => {
|
|
374
622
|
setFormUsername("");
|
|
375
623
|
setFormPassword("");
|
|
376
624
|
setFormPasswordConfirm("");
|
|
625
|
+
setFormHint("");
|
|
626
|
+
setFormSecurityAnswer("");
|
|
377
627
|
setError("");
|
|
378
628
|
setLoading(false);
|
|
629
|
+
setAuthView("options");
|
|
379
630
|
};
|
|
380
631
|
const openModal = () => {
|
|
381
632
|
resetForm();
|
|
633
|
+
setAuthView("options");
|
|
382
634
|
setModalIsOpen(true);
|
|
383
635
|
};
|
|
384
636
|
const closeModal = () => {
|
|
@@ -386,62 +638,196 @@ export const ShogunButton = (() => {
|
|
|
386
638
|
};
|
|
387
639
|
const toggleMode = () => {
|
|
388
640
|
resetForm();
|
|
641
|
+
setAuthView("password");
|
|
389
642
|
setFormMode((prev) => (prev === "login" ? "signup" : "login"));
|
|
390
643
|
};
|
|
644
|
+
// Add buttons for both login and signup for alternative auth methods
|
|
645
|
+
const renderAuthOptions = () => (React.createElement("div", { className: "shogun-auth-options" },
|
|
646
|
+
options.showMetamask !== false && (sdk === null || sdk === void 0 ? void 0 : sdk.hasPlugin("web3")) && (React.createElement("div", { className: "shogun-auth-option-group" },
|
|
647
|
+
React.createElement("button", { type: "button", className: "shogun-auth-option-button", onClick: () => handleAuth("web3"), disabled: loading },
|
|
648
|
+
React.createElement(WalletIcon, null),
|
|
649
|
+
formMode === "login" ? "Login with MetaMask" : "Signup with MetaMask"))),
|
|
650
|
+
options.showWebauthn !== false && (sdk === null || sdk === void 0 ? void 0 : sdk.hasPlugin("webauthn")) && (React.createElement("div", { className: "shogun-auth-option-group" },
|
|
651
|
+
React.createElement("button", { type: "button", className: "shogun-auth-option-button", onClick: () => {
|
|
652
|
+
if (!formUsername) {
|
|
653
|
+
setError("Username required for WebAuthn");
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
handleAuth("webauthn", formUsername);
|
|
657
|
+
}, disabled: loading },
|
|
658
|
+
React.createElement(WebAuthnIcon, null),
|
|
659
|
+
formMode === "login" ? "Login with WebAuthn" : "Signup with WebAuthn"))),
|
|
660
|
+
options.showNostr !== false && (sdk === null || sdk === void 0 ? void 0 : sdk.hasPlugin("nostr")) && (React.createElement("div", { className: "shogun-auth-option-group" },
|
|
661
|
+
React.createElement("button", { type: "button", className: "shogun-auth-option-button", onClick: () => handleAuth("nostr"), disabled: loading },
|
|
662
|
+
React.createElement(NostrIcon, null),
|
|
663
|
+
formMode === "login" ? "Login with Nostr" : "Signup with Nostr"))),
|
|
664
|
+
options.showOauth !== false && (sdk === null || sdk === void 0 ? void 0 : sdk.hasPlugin("oauth")) && (React.createElement("div", { className: "shogun-auth-option-group" },
|
|
665
|
+
React.createElement("button", { type: "button", className: "shogun-auth-option-button shogun-google-button", onClick: () => handleAuth("oauth", "google"), disabled: loading },
|
|
666
|
+
React.createElement(GoogleIcon, null),
|
|
667
|
+
formMode === "login" ? "Login with Google" : "Signup with Google"))),
|
|
668
|
+
React.createElement("div", { className: "shogun-divider" },
|
|
669
|
+
React.createElement("span", null, "or")),
|
|
670
|
+
React.createElement("button", { type: "button", className: "shogun-auth-option-button", onClick: () => setAuthView("password"), disabled: loading },
|
|
671
|
+
React.createElement(LockIcon, null),
|
|
672
|
+
formMode === "login" ? "Login with Password" : "Signup with Password"),
|
|
673
|
+
formMode === "login" && (React.createElement("button", { type: "button", className: "shogun-auth-option-button", onClick: () => setAuthView("import"), disabled: loading },
|
|
674
|
+
React.createElement("span", null, "\uD83D\uDCE5"),
|
|
675
|
+
"Import Gun Pair"))));
|
|
676
|
+
const renderPasswordForm = () => (React.createElement("form", { onSubmit: handleSubmit, className: "shogun-auth-form" },
|
|
677
|
+
React.createElement("div", { className: "shogun-form-group" },
|
|
678
|
+
React.createElement("label", { htmlFor: "username" },
|
|
679
|
+
React.createElement(UserIcon, null),
|
|
680
|
+
React.createElement("span", null, "Username")),
|
|
681
|
+
React.createElement("input", { type: "text", id: "username", value: formUsername, onChange: (e) => setFormUsername(e.target.value), disabled: loading, required: true, placeholder: "Enter your username" })),
|
|
682
|
+
React.createElement("div", { className: "shogun-form-group" },
|
|
683
|
+
React.createElement("label", { htmlFor: "password" },
|
|
684
|
+
React.createElement(LockIcon, null),
|
|
685
|
+
React.createElement("span", null, "Password")),
|
|
686
|
+
React.createElement("input", { type: "password", id: "password", value: formPassword, onChange: (e) => setFormPassword(e.target.value), disabled: loading, required: true, placeholder: "Enter your password" })),
|
|
687
|
+
formMode === "signup" && (React.createElement(React.Fragment, null,
|
|
688
|
+
React.createElement("div", { className: "shogun-form-group" },
|
|
689
|
+
React.createElement("label", { htmlFor: "passwordConfirm" },
|
|
690
|
+
React.createElement(KeyIcon, null),
|
|
691
|
+
React.createElement("span", null, "Confirm Password")),
|
|
692
|
+
React.createElement("input", { type: "password", id: "passwordConfirm", value: formPasswordConfirm, onChange: (e) => setFormPasswordConfirm(e.target.value), disabled: loading, required: true, placeholder: "Confirm your password" })),
|
|
693
|
+
React.createElement("div", { className: "shogun-form-group" },
|
|
694
|
+
React.createElement("label", { htmlFor: "hint" },
|
|
695
|
+
React.createElement(UserIcon, null),
|
|
696
|
+
React.createElement("span", null, "Password Hint")),
|
|
697
|
+
React.createElement("input", { type: "text", id: "hint", value: formHint, onChange: (e) => setFormHint(e.target.value), disabled: loading, required: true, placeholder: "Enter your password hint" })),
|
|
698
|
+
React.createElement("div", { className: "shogun-form-group" },
|
|
699
|
+
React.createElement("label", { htmlFor: "securityQuestion" },
|
|
700
|
+
React.createElement(UserIcon, null),
|
|
701
|
+
React.createElement("span", null, "Security Question")),
|
|
702
|
+
React.createElement("input", { type: "text", id: "securityQuestion", value: formSecurityQuestion, disabled: true })),
|
|
703
|
+
React.createElement("div", { className: "shogun-form-group" },
|
|
704
|
+
React.createElement("label", { htmlFor: "securityAnswer" },
|
|
705
|
+
React.createElement(UserIcon, null),
|
|
706
|
+
React.createElement("span", null, "Security Answer")),
|
|
707
|
+
React.createElement("input", { type: "text", id: "securityAnswer", value: formSecurityAnswer, onChange: (e) => setFormSecurityAnswer(e.target.value), disabled: loading, required: true, placeholder: "Enter your security answer" })))),
|
|
708
|
+
React.createElement("button", { type: "submit", className: "shogun-submit-button", disabled: loading }, loading
|
|
709
|
+
? "Processing..."
|
|
710
|
+
: formMode === "login"
|
|
711
|
+
? "Sign In"
|
|
712
|
+
: "Create Account"),
|
|
713
|
+
React.createElement("div", { className: "shogun-form-footer" },
|
|
714
|
+
React.createElement("button", { type: "button", className: "shogun-toggle-mode shogun-prominent-toggle", onClick: toggleMode, disabled: loading }, formMode === "login"
|
|
715
|
+
? "Don't have an account? Sign up"
|
|
716
|
+
: "Already have an account? Log in"),
|
|
717
|
+
formMode === "login" && (React.createElement("button", { type: "button", className: "shogun-toggle-mode", onClick: () => setAuthView("recover"), disabled: loading }, "Forgot password?")))));
|
|
718
|
+
const renderRecoveryForm = () => (React.createElement("div", { className: "shogun-auth-form" },
|
|
719
|
+
React.createElement("div", { className: "shogun-form-group" },
|
|
720
|
+
React.createElement("label", { htmlFor: "username" },
|
|
721
|
+
React.createElement(UserIcon, null),
|
|
722
|
+
React.createElement("span", null, "Username")),
|
|
723
|
+
React.createElement("input", { type: "text", id: "username", value: formUsername, onChange: (e) => setFormUsername(e.target.value), disabled: loading, required: true, placeholder: "Enter your username" })),
|
|
724
|
+
React.createElement("div", { className: "shogun-form-group" },
|
|
725
|
+
React.createElement("label", null, "Security Question"),
|
|
726
|
+
React.createElement("p", null, formSecurityQuestion)),
|
|
727
|
+
React.createElement("div", { className: "shogun-form-group" },
|
|
728
|
+
React.createElement("label", { htmlFor: "securityAnswer" },
|
|
729
|
+
React.createElement(KeyIcon, null),
|
|
730
|
+
React.createElement("span", null, "Answer")),
|
|
731
|
+
React.createElement("input", { type: "text", id: "securityAnswer", value: formSecurityAnswer, onChange: (e) => setFormSecurityAnswer(e.target.value), disabled: loading, required: true, placeholder: "Enter your answer" })),
|
|
732
|
+
React.createElement("button", { type: "button", className: "shogun-submit-button", onClick: handleRecover, disabled: loading }, loading ? "Recovering..." : "Get Hint"),
|
|
733
|
+
React.createElement("div", { className: "shogun-form-footer" },
|
|
734
|
+
React.createElement("button", { className: "shogun-toggle-mode", onClick: () => setAuthView("password"), disabled: loading }, "Back to Login"))));
|
|
735
|
+
const renderHint = () => (React.createElement("div", { className: "shogun-auth-form" },
|
|
736
|
+
React.createElement("h3", null, "Your Password Hint"),
|
|
737
|
+
React.createElement("p", { className: "shogun-hint" }, recoveredHint),
|
|
738
|
+
React.createElement("button", { className: "shogun-submit-button", onClick: () => {
|
|
739
|
+
setAuthView("password");
|
|
740
|
+
resetForm();
|
|
741
|
+
setFormMode("login");
|
|
742
|
+
} }, "Back to Login")));
|
|
743
|
+
const renderExportForm = () => (React.createElement("div", { className: "shogun-auth-form" },
|
|
744
|
+
React.createElement("h3", null, "Export Gun Pair"),
|
|
745
|
+
React.createElement("p", { style: { fontSize: '14px', color: '#666', marginBottom: '16px' } }, "Export your Gun pair to backup your account. You can use this to login from another device."),
|
|
746
|
+
React.createElement("div", { className: "shogun-form-group" },
|
|
747
|
+
React.createElement("label", { htmlFor: "exportPassword" },
|
|
748
|
+
React.createElement(LockIcon, null),
|
|
749
|
+
React.createElement("span", null, "Encryption Password (optional but recommended)")),
|
|
750
|
+
React.createElement("input", { type: "password", id: "exportPassword", value: exportPassword, onChange: (e) => setExportPassword(e.target.value), disabled: loading, placeholder: "Leave empty to export unencrypted" })),
|
|
751
|
+
exportedPair && (React.createElement("div", { className: "shogun-form-group" },
|
|
752
|
+
React.createElement("label", null, "Your Gun Pair (copy this safely):"),
|
|
753
|
+
React.createElement("textarea", { value: exportedPair, readOnly: true, rows: 6, style: {
|
|
754
|
+
fontFamily: 'monospace',
|
|
755
|
+
fontSize: '12px',
|
|
756
|
+
width: '100%',
|
|
757
|
+
padding: '8px',
|
|
758
|
+
border: '1px solid #ccc',
|
|
759
|
+
borderRadius: '4px'
|
|
760
|
+
} }))),
|
|
761
|
+
React.createElement("button", { type: "button", className: "shogun-submit-button", onClick: handleExportPair, disabled: loading }, loading ? "Exporting..." : "Export Pair"),
|
|
762
|
+
React.createElement("div", { className: "shogun-form-footer" },
|
|
763
|
+
React.createElement("button", { className: "shogun-toggle-mode", onClick: () => {
|
|
764
|
+
setAuthView("options");
|
|
765
|
+
setExportPassword("");
|
|
766
|
+
setExportedPair("");
|
|
767
|
+
}, disabled: loading }, "Back"))));
|
|
768
|
+
const renderImportForm = () => (React.createElement("div", { className: "shogun-auth-form" },
|
|
769
|
+
React.createElement("h3", null, "Import Gun Pair"),
|
|
770
|
+
React.createElement("p", { style: { fontSize: '14px', color: '#666', marginBottom: '16px' } }, "Import a Gun pair to login with your existing account from another device."),
|
|
771
|
+
React.createElement("div", { className: "shogun-form-group" },
|
|
772
|
+
React.createElement("label", { htmlFor: "importPairData" },
|
|
773
|
+
React.createElement("span", null, "\uD83D\uDCC4"),
|
|
774
|
+
React.createElement("span", null, "Gun Pair Data")),
|
|
775
|
+
React.createElement("textarea", { id: "importPairData", value: importPairData, onChange: (e) => setImportPairData(e.target.value), disabled: loading, placeholder: "Paste your Gun pair JSON here...", rows: 6, style: {
|
|
776
|
+
fontFamily: 'monospace',
|
|
777
|
+
fontSize: '12px',
|
|
778
|
+
width: '100%',
|
|
779
|
+
padding: '8px',
|
|
780
|
+
border: '1px solid #ccc',
|
|
781
|
+
borderRadius: '4px'
|
|
782
|
+
} })),
|
|
783
|
+
React.createElement("div", { className: "shogun-form-group" },
|
|
784
|
+
React.createElement("label", { htmlFor: "importPassword" },
|
|
785
|
+
React.createElement(LockIcon, null),
|
|
786
|
+
React.createElement("span", null, "Decryption Password (if encrypted)")),
|
|
787
|
+
React.createElement("input", { type: "password", id: "importPassword", value: importPassword, onChange: (e) => setImportPassword(e.target.value), disabled: loading, placeholder: "Enter password if pair was encrypted" })),
|
|
788
|
+
React.createElement("button", { type: "button", className: "shogun-submit-button", onClick: handleImportPair, disabled: loading }, loading ? "Importing..." : "Import and Login"),
|
|
789
|
+
React.createElement("div", { className: "shogun-form-footer" },
|
|
790
|
+
React.createElement("button", { className: "shogun-toggle-mode", onClick: () => {
|
|
791
|
+
setAuthView("options");
|
|
792
|
+
setImportPassword("");
|
|
793
|
+
setImportPairData("");
|
|
794
|
+
}, disabled: loading }, "Back to Login Options"))));
|
|
391
795
|
// Render logic
|
|
392
796
|
return (React.createElement(React.Fragment, null,
|
|
393
797
|
React.createElement("button", { className: "shogun-connect-button", onClick: openModal },
|
|
394
798
|
React.createElement(WalletIcon, null),
|
|
395
|
-
React.createElement("span", null, "
|
|
799
|
+
React.createElement("span", null, "Login / Sign Up")),
|
|
396
800
|
modalIsOpen && (React.createElement("div", { className: "shogun-modal-overlay", onClick: closeModal },
|
|
397
801
|
React.createElement("div", { className: "shogun-modal", onClick: (e) => e.stopPropagation() },
|
|
398
802
|
React.createElement("div", { className: "shogun-modal-header" },
|
|
399
|
-
React.createElement("h2", null,
|
|
400
|
-
|
|
803
|
+
React.createElement("h2", null, authView === "recover"
|
|
804
|
+
? "Recover Password"
|
|
805
|
+
: authView === "showHint"
|
|
806
|
+
? "Password Hint"
|
|
807
|
+
: authView === "export"
|
|
808
|
+
? "Export Gun Pair"
|
|
809
|
+
: authView === "import"
|
|
810
|
+
? "Import Gun Pair"
|
|
811
|
+
: formMode === "login"
|
|
812
|
+
? "Login"
|
|
813
|
+
: "Sign Up"),
|
|
814
|
+
React.createElement("button", { className: "shogun-close-button", onClick: closeModal, "aria-label": "Close" },
|
|
401
815
|
React.createElement(CloseIcon, null))),
|
|
402
816
|
React.createElement("div", { className: "shogun-modal-content" },
|
|
403
817
|
error && React.createElement("div", { className: "shogun-error-message" }, error),
|
|
404
|
-
|
|
405
|
-
(
|
|
406
|
-
|
|
407
|
-
React.createElement("
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
React.createElement("div", { className: "shogun-divider" },
|
|
418
|
-
React.createElement("span", null, "or continue with password")),
|
|
419
|
-
React.createElement("form", { onSubmit: handleSubmit, className: "shogun-auth-form" },
|
|
420
|
-
React.createElement("div", { className: "shogun-form-group" },
|
|
421
|
-
React.createElement("label", { htmlFor: "username" },
|
|
422
|
-
React.createElement(UserIcon, null),
|
|
423
|
-
React.createElement("span", null, "Username")),
|
|
424
|
-
React.createElement("input", { type: "text", id: "username", value: formUsername, onChange: (e) => setFormUsername(e.target.value), disabled: loading, required: true, placeholder: "Enter your username" })),
|
|
425
|
-
React.createElement("div", { className: "shogun-form-group" },
|
|
426
|
-
React.createElement("label", { htmlFor: "password" },
|
|
427
|
-
React.createElement(LockIcon, null),
|
|
428
|
-
React.createElement("span", null, "Password")),
|
|
429
|
-
React.createElement("input", { type: "password", id: "password", value: formPassword, onChange: (e) => setFormPassword(e.target.value), disabled: loading, required: true, placeholder: "Enter your password" })),
|
|
430
|
-
formMode === "signup" && (React.createElement("div", { className: "shogun-form-group" },
|
|
431
|
-
React.createElement("label", { htmlFor: "passwordConfirm" },
|
|
432
|
-
React.createElement(KeyIcon, null),
|
|
433
|
-
React.createElement("span", null, "Confirm Password")),
|
|
434
|
-
React.createElement("input", { type: "password", id: "passwordConfirm", value: formPasswordConfirm, onChange: (e) => setFormPasswordConfirm(e.target.value), disabled: loading, required: true, placeholder: "Confirm your password" }))),
|
|
435
|
-
React.createElement("button", { type: "submit", className: "shogun-submit-button", disabled: loading }, loading
|
|
436
|
-
? "Processing..."
|
|
437
|
-
: formMode === "login"
|
|
438
|
-
? "Sign In"
|
|
439
|
-
: "Create Account")),
|
|
440
|
-
React.createElement("div", { className: "shogun-form-footer" },
|
|
441
|
-
formMode === "login"
|
|
442
|
-
? "Don't have an account?"
|
|
443
|
-
: "Already have an account?",
|
|
444
|
-
React.createElement("button", { className: "shogun-toggle-mode", onClick: toggleMode, disabled: loading }, formMode === "login" ? "Sign Up" : "Sign In"))))))));
|
|
818
|
+
authView === "options" && (React.createElement(React.Fragment, null,
|
|
819
|
+
renderAuthOptions(),
|
|
820
|
+
React.createElement("div", { className: "shogun-form-footer" },
|
|
821
|
+
React.createElement("button", { type: "button", className: "shogun-toggle-mode shogun-prominent-toggle", onClick: toggleMode, disabled: loading }, formMode === "login"
|
|
822
|
+
? "Don't have an account? Sign up"
|
|
823
|
+
: "Already have an account? Log in")))),
|
|
824
|
+
authView === "password" && (React.createElement(React.Fragment, null,
|
|
825
|
+
React.createElement("button", { className: "shogun-back-button", onClick: () => setAuthView("options") }, "\u2190 Back"),
|
|
826
|
+
renderPasswordForm())),
|
|
827
|
+
authView === "recover" && renderRecoveryForm(),
|
|
828
|
+
authView === "showHint" && renderHint(),
|
|
829
|
+
authView === "export" && renderExportForm(),
|
|
830
|
+
authView === "import" && renderImportForm()))))));
|
|
445
831
|
};
|
|
446
832
|
Button.displayName = "ShogunButton";
|
|
447
833
|
return Object.assign(Button, {
|