shogun-button-react 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +192 -0
- package/dist/components/ShogunButton.d.ts +30 -0
- package/dist/components/ShogunButton.js +451 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/styles/index.css +723 -0
- package/dist/types/connector-options.d.ts +41 -0
- package/dist/types/connector-options.js +1 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.js +4 -0
- package/dist/types.d.ts +1 -0
- package/dist/types.js +2 -0
- package/package.json +58 -0
- package/src/styles/index.css +723 -0
package/README.md
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# Shogun Button React
|
|
2
|
+
|
|
3
|
+
A React component library for seamless integration of Shogun authentication into your applications. This library provides a simple yet powerful way to add Shogun authentication to your React applications.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🚀 Easy to integrate
|
|
8
|
+
- 🎨 Customizable UI components
|
|
9
|
+
- 🔒 Secure authentication flow
|
|
10
|
+
- 🌓 Dark mode support
|
|
11
|
+
- 🔌 Multiple authentication methods (Username/Password, MetaMask, WebAuthn)
|
|
12
|
+
- 📱 Responsive design
|
|
13
|
+
- 🌍 TypeScript support
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import React from "react";
|
|
19
|
+
import {
|
|
20
|
+
ShogunButton,
|
|
21
|
+
ShogunButtonProvider,
|
|
22
|
+
shogunConnector,
|
|
23
|
+
} from "@shogun/shogun-button-react";
|
|
24
|
+
import "@shogun/shogun-button-react/styles.css";
|
|
25
|
+
|
|
26
|
+
function App() {
|
|
27
|
+
const { sdk, options, setProvider } = shogunConnector({
|
|
28
|
+
appName: "My App",
|
|
29
|
+
appDescription: "An awesome app with Shogun authentication",
|
|
30
|
+
appUrl: "https://myapp.com",
|
|
31
|
+
appIcon: "https://myapp.com/icon.png",
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<ShogunButtonProvider
|
|
36
|
+
sdk={sdk}
|
|
37
|
+
options={options}
|
|
38
|
+
onLoginSuccess={(data) => {
|
|
39
|
+
console.log("Login successful!", data);
|
|
40
|
+
}}
|
|
41
|
+
onSignupSuccess={(data) => {
|
|
42
|
+
console.log("Signup successful!", data);
|
|
43
|
+
}}
|
|
44
|
+
onError={(error) => {
|
|
45
|
+
console.error("An error occurred:", error);
|
|
46
|
+
}}
|
|
47
|
+
>
|
|
48
|
+
<div>
|
|
49
|
+
<h1>Welcome to My App</h1>
|
|
50
|
+
<ShogunButton />
|
|
51
|
+
</div>
|
|
52
|
+
</ShogunButtonProvider>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export default App;
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## API Reference
|
|
60
|
+
|
|
61
|
+
### ShogunButtonProvider
|
|
62
|
+
|
|
63
|
+
The provider component that supplies Shogun context to your application.
|
|
64
|
+
|
|
65
|
+
#### Props
|
|
66
|
+
|
|
67
|
+
| Name | Type | Description |
|
|
68
|
+
| --------------- | ------------------------ | ---------------------------------------------- |
|
|
69
|
+
| sdk | ShogunSDK | Shogun SDK instance created by shogunConnector |
|
|
70
|
+
| options | Object | Configuration options |
|
|
71
|
+
| onLoginSuccess | (data: AuthData) => void | Callback fired on successful login |
|
|
72
|
+
| onSignupSuccess | (data: AuthData) => void | Callback fired on successful signup |
|
|
73
|
+
| onError | (error: Error) => void | Callback fired when an error occurs |
|
|
74
|
+
|
|
75
|
+
### ShogunButton
|
|
76
|
+
|
|
77
|
+
The main button component for triggering Shogun authentication.
|
|
78
|
+
|
|
79
|
+
#### Custom Button
|
|
80
|
+
|
|
81
|
+
You can customize the button appearance using `ShogunButton.Custom`:
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
<ShogunButton.Custom>
|
|
85
|
+
{({ ready, authenticate }) => (
|
|
86
|
+
<button
|
|
87
|
+
className="my-custom-button"
|
|
88
|
+
disabled={!ready}
|
|
89
|
+
onClick={authenticate}
|
|
90
|
+
>
|
|
91
|
+
Connect with Shogun
|
|
92
|
+
</button>
|
|
93
|
+
)}
|
|
94
|
+
</ShogunButton.Custom>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### useShogun Hook
|
|
98
|
+
|
|
99
|
+
A hook to access Shogun authentication state and functions.
|
|
100
|
+
|
|
101
|
+
```tsx
|
|
102
|
+
import { useShogun } from "@shogun/shogun-button-react";
|
|
103
|
+
|
|
104
|
+
function Profile() {
|
|
105
|
+
const {
|
|
106
|
+
isAuthenticated,
|
|
107
|
+
user,
|
|
108
|
+
login,
|
|
109
|
+
signup,
|
|
110
|
+
logout,
|
|
111
|
+
connectWithMetaMask,
|
|
112
|
+
connectWithWebAuthn,
|
|
113
|
+
setProvider,
|
|
114
|
+
} = useShogun();
|
|
115
|
+
|
|
116
|
+
const switchToCustomNetwork = () => {
|
|
117
|
+
setProvider('https://my-custom-rpc.example.com');
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
return isAuthenticated ? (
|
|
121
|
+
<div>
|
|
122
|
+
<h2>Welcome, {user.username}!</h2>
|
|
123
|
+
<button onClick={logout}>Logout</button>
|
|
124
|
+
<button onClick={switchToCustomNetwork}>Switch Network</button>
|
|
125
|
+
</div>
|
|
126
|
+
) : (
|
|
127
|
+
<div>Please login to continue</div>
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Configuration Options
|
|
133
|
+
|
|
134
|
+
The `shogunConnector` accepts the following options:
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
interface ShogunConnectorOptions {
|
|
138
|
+
appName: string;
|
|
139
|
+
appDescription?: string;
|
|
140
|
+
appUrl?: string;
|
|
141
|
+
appIcon?: string;
|
|
142
|
+
showMetamask?: boolean;
|
|
143
|
+
showWebauthn?: boolean;
|
|
144
|
+
darkMode?: boolean;
|
|
145
|
+
websocketSecure?: boolean;
|
|
146
|
+
didRegistryAddress?: string | null;
|
|
147
|
+
providerUrl?: string | null;
|
|
148
|
+
peers?: string[];
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
The `shogunConnector` returns an object with the following properties:
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
interface ShogunConnectorResult {
|
|
156
|
+
sdk: ShogunCore;
|
|
157
|
+
options: ShogunConnectorOptions;
|
|
158
|
+
setProvider: (provider: string | EthersProvider) => boolean;
|
|
159
|
+
getCurrentProviderUrl: () => string | null;
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
> **Note**: The `setProvider` method attempts to update the RPC provider URL used by the SDK. This functionality depends on the specific version of Shogun Core you're using. If the SDK does not have a public `setRpcUrl` method available, the provider URL will still be saved but not applied to the SDK directly. In such cases, the setting will only be available through the `getCurrentProviderUrl` method.
|
|
164
|
+
|
|
165
|
+
## Styling
|
|
166
|
+
|
|
167
|
+
The component comes with default styling that you can override using CSS variables:
|
|
168
|
+
|
|
169
|
+
```css
|
|
170
|
+
:root {
|
|
171
|
+
--shogun-button-primary: #5c6bc0;
|
|
172
|
+
--shogun-button-hover: #3f51b5;
|
|
173
|
+
--shogun-text-primary: #333333;
|
|
174
|
+
--shogun-background: #ffffff;
|
|
175
|
+
/* ... other variables */
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Browser Support
|
|
180
|
+
|
|
181
|
+
- Chrome ≥ 60
|
|
182
|
+
- Firefox ≥ 60
|
|
183
|
+
- Safari ≥ 12
|
|
184
|
+
- Edge ≥ 79
|
|
185
|
+
|
|
186
|
+
## Contributing
|
|
187
|
+
|
|
188
|
+
We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
|
|
189
|
+
|
|
190
|
+
## License
|
|
191
|
+
|
|
192
|
+
MIT © [Shogun](https://github.com/shogun)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { ShogunCore } from "shogun-core";
|
|
3
|
+
import "../types.js";
|
|
4
|
+
import "../styles/index.css";
|
|
5
|
+
export declare const useShogun: () => any;
|
|
6
|
+
type ShogunButtonProviderProps = {
|
|
7
|
+
children: React.ReactNode;
|
|
8
|
+
sdk: ShogunCore;
|
|
9
|
+
options: any;
|
|
10
|
+
onLoginSuccess?: (data: {
|
|
11
|
+
userPub: string;
|
|
12
|
+
username: string;
|
|
13
|
+
password?: string;
|
|
14
|
+
authMethod?: "password" | "web3" | "webauthn" | "nostr" | "oauth";
|
|
15
|
+
}) => void;
|
|
16
|
+
onSignupSuccess?: (data: {
|
|
17
|
+
userPub: string;
|
|
18
|
+
username: string;
|
|
19
|
+
password?: string;
|
|
20
|
+
authMethod?: "password" | "web3" | "webauthn" | "nostr" | "oauth";
|
|
21
|
+
}) => void;
|
|
22
|
+
onError?: (error: string) => void;
|
|
23
|
+
};
|
|
24
|
+
export declare function ShogunButtonProvider({ children, sdk, options, onLoginSuccess, onSignupSuccess, onError, }: ShogunButtonProviderProps): any;
|
|
25
|
+
type ShogunButtonComponent = React.FC & {
|
|
26
|
+
Provider: typeof ShogunButtonProvider;
|
|
27
|
+
useShogun: typeof useShogun;
|
|
28
|
+
};
|
|
29
|
+
export declare const ShogunButton: ShogunButtonComponent;
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
import React, { useContext, useState, createContext, useEffect } from "react";
|
|
2
|
+
import { Observable } from "rxjs";
|
|
3
|
+
import "../types.js"; // Import type file to extend definitions
|
|
4
|
+
import "../styles/index.css";
|
|
5
|
+
// Default context
|
|
6
|
+
const defaultShogunContext = {
|
|
7
|
+
sdk: null,
|
|
8
|
+
options: {},
|
|
9
|
+
isLoggedIn: false,
|
|
10
|
+
userPub: null,
|
|
11
|
+
username: null,
|
|
12
|
+
login: async () => ({}),
|
|
13
|
+
signUp: async () => ({}),
|
|
14
|
+
logout: () => { },
|
|
15
|
+
observe: () => new Observable(),
|
|
16
|
+
setProvider: () => false,
|
|
17
|
+
hasPlugin: () => false,
|
|
18
|
+
getPlugin: () => undefined,
|
|
19
|
+
};
|
|
20
|
+
// Create context using React's createContext directly
|
|
21
|
+
const ShogunContext = createContext(defaultShogunContext);
|
|
22
|
+
// Custom hook to access the context
|
|
23
|
+
export const useShogun = () => useContext(ShogunContext);
|
|
24
|
+
// Provider component
|
|
25
|
+
export function ShogunButtonProvider({ children, sdk, options, onLoginSuccess, onSignupSuccess, onError, }) {
|
|
26
|
+
// Use React's useState directly
|
|
27
|
+
const [isLoggedIn, setIsLoggedIn] = useState((sdk === null || sdk === void 0 ? void 0 : sdk.isLoggedIn()) || false);
|
|
28
|
+
const [userPub, setUserPub] = useState(null);
|
|
29
|
+
const [username, setUsername] = useState(null);
|
|
30
|
+
// Effetto per gestire l'inizializzazione e pulizia
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
// Controlla se l'utente è già autenticato all'avvio
|
|
33
|
+
if (sdk === null || sdk === void 0 ? void 0 : sdk.isLoggedIn()) {
|
|
34
|
+
setIsLoggedIn(true);
|
|
35
|
+
}
|
|
36
|
+
return () => {
|
|
37
|
+
// Pulizia quando il componente si smonta
|
|
38
|
+
};
|
|
39
|
+
}, [sdk]);
|
|
40
|
+
// RxJS observe method
|
|
41
|
+
const observe = (path) => {
|
|
42
|
+
if (!sdk) {
|
|
43
|
+
return new Observable();
|
|
44
|
+
}
|
|
45
|
+
return sdk.observe(path);
|
|
46
|
+
};
|
|
47
|
+
// Unified login
|
|
48
|
+
const login = async (method, ...args) => {
|
|
49
|
+
try {
|
|
50
|
+
if (!sdk) {
|
|
51
|
+
throw new Error("SDK not initialized");
|
|
52
|
+
}
|
|
53
|
+
let result;
|
|
54
|
+
let authMethod = method;
|
|
55
|
+
let username;
|
|
56
|
+
switch (method) {
|
|
57
|
+
case "password":
|
|
58
|
+
username = args[0];
|
|
59
|
+
result = await sdk.login(args[0], args[1]);
|
|
60
|
+
break;
|
|
61
|
+
case "webauthn":
|
|
62
|
+
username = args[0];
|
|
63
|
+
const webauthn = sdk.getPlugin("webauthn");
|
|
64
|
+
if (!webauthn)
|
|
65
|
+
throw new Error("WebAuthn plugin not available");
|
|
66
|
+
result = await webauthn.login(username);
|
|
67
|
+
break;
|
|
68
|
+
case "web3":
|
|
69
|
+
const web3 = sdk.getPlugin("web3");
|
|
70
|
+
if (!web3)
|
|
71
|
+
throw new Error("Web3 plugin not available");
|
|
72
|
+
const connectionResult = await web3.connectMetaMask();
|
|
73
|
+
if (!connectionResult.success || !connectionResult.address) {
|
|
74
|
+
throw new Error(connectionResult.error || "Failed to connect wallet.");
|
|
75
|
+
}
|
|
76
|
+
username = connectionResult.address;
|
|
77
|
+
result = await web3.login(connectionResult.address);
|
|
78
|
+
break;
|
|
79
|
+
case "nostr":
|
|
80
|
+
const nostr = sdk.getPlugin("nostr");
|
|
81
|
+
if (!nostr)
|
|
82
|
+
throw new Error("Nostr plugin not available");
|
|
83
|
+
const nostrResult = await nostr.connectBitcoinWallet();
|
|
84
|
+
if (!nostrResult || !nostrResult.success) {
|
|
85
|
+
throw new Error((nostrResult === null || nostrResult === void 0 ? void 0 : nostrResult.error) || "Connessione al wallet Bitcoin fallita");
|
|
86
|
+
}
|
|
87
|
+
const pubkey = nostrResult.address;
|
|
88
|
+
if (!pubkey)
|
|
89
|
+
throw new Error("Nessuna chiave pubblica ottenuta");
|
|
90
|
+
username = pubkey;
|
|
91
|
+
result = await nostr.login(pubkey);
|
|
92
|
+
break;
|
|
93
|
+
case "oauth":
|
|
94
|
+
const oauth = sdk.getPlugin("oauth");
|
|
95
|
+
if (!oauth)
|
|
96
|
+
throw new Error("OAuth plugin not available");
|
|
97
|
+
const provider = args[0] || "google";
|
|
98
|
+
result = await oauth.login(provider);
|
|
99
|
+
authMethod = "oauth";
|
|
100
|
+
if (result.redirectUrl) {
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
break;
|
|
104
|
+
default:
|
|
105
|
+
throw new Error("Unsupported login method");
|
|
106
|
+
}
|
|
107
|
+
if (result.success) {
|
|
108
|
+
setIsLoggedIn(true);
|
|
109
|
+
setUserPub(result.userPub || "");
|
|
110
|
+
setUsername(username || "");
|
|
111
|
+
onLoginSuccess === null || onLoginSuccess === void 0 ? void 0 : onLoginSuccess({
|
|
112
|
+
userPub: result.userPub || "",
|
|
113
|
+
username: username || "",
|
|
114
|
+
authMethod: authMethod,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
onError === null || onError === void 0 ? void 0 : onError(result.error || "Login failed");
|
|
119
|
+
}
|
|
120
|
+
return result;
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
onError === null || onError === void 0 ? void 0 : onError(error.message || "Error during login");
|
|
124
|
+
return { success: false, error: error.message };
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
// Unified signup
|
|
128
|
+
const signUp = async (method, ...args) => {
|
|
129
|
+
try {
|
|
130
|
+
if (!sdk) {
|
|
131
|
+
throw new Error("SDK not initialized");
|
|
132
|
+
}
|
|
133
|
+
let result;
|
|
134
|
+
let authMethod = method;
|
|
135
|
+
let username;
|
|
136
|
+
switch (method) {
|
|
137
|
+
case "password":
|
|
138
|
+
username = args[0];
|
|
139
|
+
if (args[1] !== args[2]) {
|
|
140
|
+
throw new Error("Passwords do not match");
|
|
141
|
+
}
|
|
142
|
+
result = await sdk.signUp(args[0], args[1]);
|
|
143
|
+
break;
|
|
144
|
+
case "webauthn":
|
|
145
|
+
username = args[0];
|
|
146
|
+
const webauthn = sdk.getPlugin("webauthn");
|
|
147
|
+
if (!webauthn)
|
|
148
|
+
throw new Error("WebAuthn plugin not available");
|
|
149
|
+
result = await webauthn.signUp(username);
|
|
150
|
+
break;
|
|
151
|
+
case "web3":
|
|
152
|
+
const web3 = sdk.getPlugin("web3");
|
|
153
|
+
if (!web3)
|
|
154
|
+
throw new Error("Web3 plugin not available");
|
|
155
|
+
const connectionResult = await web3.connectMetaMask();
|
|
156
|
+
if (!connectionResult.success || !connectionResult.address) {
|
|
157
|
+
throw new Error(connectionResult.error || "Failed to connect wallet.");
|
|
158
|
+
}
|
|
159
|
+
username = connectionResult.address;
|
|
160
|
+
result = await web3.signUp(connectionResult.address);
|
|
161
|
+
break;
|
|
162
|
+
case "nostr":
|
|
163
|
+
const nostr = sdk.getPlugin("nostr");
|
|
164
|
+
if (!nostr)
|
|
165
|
+
throw new Error("Nostr plugin not available");
|
|
166
|
+
const nostrResult = await nostr.connectBitcoinWallet();
|
|
167
|
+
if (!nostrResult || !nostrResult.success) {
|
|
168
|
+
throw new Error((nostrResult === null || nostrResult === void 0 ? void 0 : nostrResult.error) || "Connessione al wallet Bitcoin fallita");
|
|
169
|
+
}
|
|
170
|
+
const pubkey = nostrResult.address;
|
|
171
|
+
if (!pubkey)
|
|
172
|
+
throw new Error("Nessuna chiave pubblica ottenuta");
|
|
173
|
+
username = pubkey;
|
|
174
|
+
result = await nostr.signUp(pubkey);
|
|
175
|
+
break;
|
|
176
|
+
case "oauth":
|
|
177
|
+
const oauth = sdk.getPlugin("oauth");
|
|
178
|
+
if (!oauth)
|
|
179
|
+
throw new Error("OAuth plugin not available");
|
|
180
|
+
const provider = args[0] || "google";
|
|
181
|
+
result = await oauth.signUp(provider);
|
|
182
|
+
authMethod = "oauth";
|
|
183
|
+
if (result.redirectUrl) {
|
|
184
|
+
return result;
|
|
185
|
+
}
|
|
186
|
+
break;
|
|
187
|
+
default:
|
|
188
|
+
throw new Error("Unsupported signup method");
|
|
189
|
+
}
|
|
190
|
+
if (result.success) {
|
|
191
|
+
setIsLoggedIn(true);
|
|
192
|
+
const userPub = result.userPub || "";
|
|
193
|
+
setUserPub(userPub);
|
|
194
|
+
setUsername(username || "");
|
|
195
|
+
onSignupSuccess === null || onSignupSuccess === void 0 ? void 0 : onSignupSuccess({
|
|
196
|
+
userPub: userPub,
|
|
197
|
+
username: username || "",
|
|
198
|
+
authMethod: authMethod,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
onError === null || onError === void 0 ? void 0 : onError(result.error);
|
|
203
|
+
}
|
|
204
|
+
return result;
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
onError === null || onError === void 0 ? void 0 : onError(error.message || "Error during registration");
|
|
208
|
+
return { success: false, error: error.message };
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
// Logout
|
|
212
|
+
const logout = () => {
|
|
213
|
+
sdk.logout();
|
|
214
|
+
setIsLoggedIn(false);
|
|
215
|
+
setUserPub(null);
|
|
216
|
+
setUsername(null);
|
|
217
|
+
};
|
|
218
|
+
// Implementazione del metodo setProvider
|
|
219
|
+
const setProvider = (provider) => {
|
|
220
|
+
if (!sdk) {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
try {
|
|
224
|
+
let newProviderUrl = null;
|
|
225
|
+
if (provider && provider.connection && provider.connection.url) {
|
|
226
|
+
newProviderUrl = provider.connection.url;
|
|
227
|
+
}
|
|
228
|
+
else if (typeof provider === 'string') {
|
|
229
|
+
newProviderUrl = provider;
|
|
230
|
+
}
|
|
231
|
+
if (newProviderUrl) {
|
|
232
|
+
if (typeof sdk.setRpcUrl === 'function') {
|
|
233
|
+
return sdk.setRpcUrl(newProviderUrl);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
console.error("Error setting provider:", error);
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
const hasPlugin = (name) => {
|
|
244
|
+
return sdk ? sdk.hasPlugin(name) : false;
|
|
245
|
+
};
|
|
246
|
+
const getPlugin = (name) => {
|
|
247
|
+
return sdk ? sdk.getPlugin(name) : undefined;
|
|
248
|
+
};
|
|
249
|
+
// Provide the context value to children
|
|
250
|
+
return (React.createElement(ShogunContext.Provider, { value: {
|
|
251
|
+
sdk,
|
|
252
|
+
options,
|
|
253
|
+
isLoggedIn,
|
|
254
|
+
userPub,
|
|
255
|
+
username,
|
|
256
|
+
login,
|
|
257
|
+
signUp,
|
|
258
|
+
logout,
|
|
259
|
+
observe,
|
|
260
|
+
setProvider,
|
|
261
|
+
hasPlugin,
|
|
262
|
+
getPlugin,
|
|
263
|
+
} }, children));
|
|
264
|
+
}
|
|
265
|
+
// SVG Icons Components
|
|
266
|
+
const WalletIcon = () => (React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" },
|
|
267
|
+
React.createElement("path", { d: "M21 12V7H5a2 2 0 0 1 0-4h14v4" }),
|
|
268
|
+
React.createElement("path", { d: "M3 5v14a2 2 0 0 0 2 2h16v-5" }),
|
|
269
|
+
React.createElement("path", { d: "M18 12a2 2 0 0 0 0 4h4v-4Z" })));
|
|
270
|
+
const KeyIcon = () => (React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" },
|
|
271
|
+
React.createElement("circle", { cx: "7.5", cy: "15.5", r: "5.5" }),
|
|
272
|
+
React.createElement("path", { d: "m21 2-9.6 9.6" }),
|
|
273
|
+
React.createElement("path", { d: "m15.5 7.5 3 3L22 7l-3-3" })));
|
|
274
|
+
const GoogleIcon = () => (React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24", fill: "currentColor" },
|
|
275
|
+
React.createElement("path", { d: "M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z", fill: "#4285F4" }),
|
|
276
|
+
React.createElement("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" }),
|
|
277
|
+
React.createElement("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" }),
|
|
278
|
+
React.createElement("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" })));
|
|
279
|
+
const NostrIcon = () => (React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" },
|
|
280
|
+
React.createElement("path", { d: "M19.5 4.5 15 9l-3-3-4.5 4.5L9 12l-1.5 1.5L12 18l4.5-4.5L15 12l1.5-1.5L21 6l-1.5-1.5Z" }),
|
|
281
|
+
React.createElement("path", { d: "M12 12 6 6l-1.5 1.5L9 12l-4.5 4.5L6 18l6-6Z" })));
|
|
282
|
+
const WebAuthnIcon = () => (React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" },
|
|
283
|
+
React.createElement("path", { d: "M7 11v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V7a1 1 0 0 0-1-1h-4" }),
|
|
284
|
+
React.createElement("path", { d: "M14 4V2a1 1 0 0 0-1-1H7a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h2" })));
|
|
285
|
+
const LogoutIcon = () => (React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" },
|
|
286
|
+
React.createElement("path", { d: "M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" }),
|
|
287
|
+
React.createElement("polyline", { points: "16 17 21 12 16 7" }),
|
|
288
|
+
React.createElement("line", { x1: "21", y1: "12", x2: "9", y2: "12" })));
|
|
289
|
+
const UserIcon = () => (React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" },
|
|
290
|
+
React.createElement("path", { d: "M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" }),
|
|
291
|
+
React.createElement("circle", { cx: "12", cy: "7", r: "4" })));
|
|
292
|
+
const LockIcon = () => (React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" },
|
|
293
|
+
React.createElement("rect", { x: "3", y: "11", width: "18", height: "11", rx: "2", ry: "2" }),
|
|
294
|
+
React.createElement("path", { d: "M7 11V7a5 5 0 0 1 10 0v4" })));
|
|
295
|
+
const CloseIcon = () => (React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" },
|
|
296
|
+
React.createElement("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
297
|
+
React.createElement("line", { x1: "6", y1: "6", x2: "18", y2: "18" })));
|
|
298
|
+
// Component for Shogun login button
|
|
299
|
+
export const ShogunButton = (() => {
|
|
300
|
+
const Button = () => {
|
|
301
|
+
const { isLoggedIn, username, logout, login, signUp, sdk, options } = useShogun();
|
|
302
|
+
// Form states
|
|
303
|
+
const [modalIsOpen, setModalIsOpen] = useState(false);
|
|
304
|
+
const [formUsername, setFormUsername] = useState("");
|
|
305
|
+
const [formPassword, setFormPassword] = useState("");
|
|
306
|
+
const [formPasswordConfirm, setFormPasswordConfirm] = useState("");
|
|
307
|
+
const [formMode, setFormMode] = useState("login");
|
|
308
|
+
const [error, setError] = useState("");
|
|
309
|
+
const [loading, setLoading] = useState(false);
|
|
310
|
+
const [dropdownOpen, setDropdownOpen] = useState(false);
|
|
311
|
+
// If already logged in, show only logout button
|
|
312
|
+
if (isLoggedIn && username) {
|
|
313
|
+
return (React.createElement("div", { className: "shogun-logged-in-container" },
|
|
314
|
+
React.createElement("div", { className: "shogun-dropdown" },
|
|
315
|
+
React.createElement("button", { className: "shogun-button shogun-logged-in", onClick: () => setDropdownOpen(!dropdownOpen) },
|
|
316
|
+
React.createElement("div", { className: "shogun-avatar" }, username.substring(0, 2).toUpperCase()),
|
|
317
|
+
React.createElement("span", { className: "shogun-username" }, username.length > 12
|
|
318
|
+
? `${username.substring(0, 6)}...${username.substring(username.length - 4)}`
|
|
319
|
+
: username)),
|
|
320
|
+
dropdownOpen && (React.createElement("div", { className: "shogun-dropdown-menu" },
|
|
321
|
+
React.createElement("div", { className: "shogun-dropdown-header" },
|
|
322
|
+
React.createElement("div", { className: "shogun-avatar-large" }, username.substring(0, 2).toUpperCase()),
|
|
323
|
+
React.createElement("div", { className: "shogun-user-info" },
|
|
324
|
+
React.createElement("span", { className: "shogun-username-full" }, username.length > 20
|
|
325
|
+
? `${username.substring(0, 10)}...${username.substring(username.length - 6)}`
|
|
326
|
+
: username))),
|
|
327
|
+
React.createElement("div", { className: "shogun-dropdown-item", onClick: logout },
|
|
328
|
+
React.createElement(LogoutIcon, null),
|
|
329
|
+
React.createElement("span", null, "Disconnect")))))));
|
|
330
|
+
}
|
|
331
|
+
// Event handlers
|
|
332
|
+
const handleAuth = async (method, ...args) => {
|
|
333
|
+
setError("");
|
|
334
|
+
setLoading(true);
|
|
335
|
+
try {
|
|
336
|
+
const action = formMode === "login" ? login : signUp;
|
|
337
|
+
const result = await action(method, ...args);
|
|
338
|
+
if (result && !result.success && result.error) {
|
|
339
|
+
setError(result.error);
|
|
340
|
+
}
|
|
341
|
+
else if (result && result.redirectUrl) {
|
|
342
|
+
window.location.href = result.redirectUrl;
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
setModalIsOpen(false);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
catch (e) {
|
|
349
|
+
setError(e.message || "An unexpected error occurred.");
|
|
350
|
+
}
|
|
351
|
+
finally {
|
|
352
|
+
setLoading(false);
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
const handleSubmit = (e) => {
|
|
356
|
+
e.preventDefault();
|
|
357
|
+
handleAuth("password", formUsername, formPassword, formPasswordConfirm);
|
|
358
|
+
};
|
|
359
|
+
const handleWeb3Auth = () => handleAuth("web3");
|
|
360
|
+
const handleWebAuthnAuth = () => {
|
|
361
|
+
if (!(sdk === null || sdk === void 0 ? void 0 : sdk.hasPlugin("webauthn"))) {
|
|
362
|
+
setError("WebAuthn is not supported in your browser");
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
if (!formUsername) {
|
|
366
|
+
setError("Username required for WebAuthn");
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
handleAuth("webauthn", formUsername);
|
|
370
|
+
};
|
|
371
|
+
const handleNostrAuth = () => handleAuth("nostr");
|
|
372
|
+
const handleOAuth = (provider) => handleAuth("oauth", provider);
|
|
373
|
+
const resetForm = () => {
|
|
374
|
+
setFormUsername("");
|
|
375
|
+
setFormPassword("");
|
|
376
|
+
setFormPasswordConfirm("");
|
|
377
|
+
setError("");
|
|
378
|
+
setLoading(false);
|
|
379
|
+
};
|
|
380
|
+
const openModal = () => {
|
|
381
|
+
resetForm();
|
|
382
|
+
setModalIsOpen(true);
|
|
383
|
+
};
|
|
384
|
+
const closeModal = () => {
|
|
385
|
+
setModalIsOpen(false);
|
|
386
|
+
};
|
|
387
|
+
const toggleMode = () => {
|
|
388
|
+
resetForm();
|
|
389
|
+
setFormMode((prev) => (prev === "login" ? "signup" : "login"));
|
|
390
|
+
};
|
|
391
|
+
// Render logic
|
|
392
|
+
return (React.createElement(React.Fragment, null,
|
|
393
|
+
React.createElement("button", { className: "shogun-connect-button", onClick: openModal },
|
|
394
|
+
React.createElement(WalletIcon, null),
|
|
395
|
+
React.createElement("span", null, "Connect")),
|
|
396
|
+
modalIsOpen && (React.createElement("div", { className: "shogun-modal-overlay", onClick: closeModal },
|
|
397
|
+
React.createElement("div", { className: "shogun-modal", onClick: (e) => e.stopPropagation() },
|
|
398
|
+
React.createElement("div", { className: "shogun-modal-header" },
|
|
399
|
+
React.createElement("h2", null, formMode === "login" ? "Sign In" : "Create Account"),
|
|
400
|
+
React.createElement("button", { className: "shogun-close-button", onClick: closeModal },
|
|
401
|
+
React.createElement(CloseIcon, null))),
|
|
402
|
+
React.createElement("div", { className: "shogun-modal-content" },
|
|
403
|
+
error && React.createElement("div", { className: "shogun-error-message" }, error),
|
|
404
|
+
React.createElement("div", { className: "shogun-auth-options" },
|
|
405
|
+
(options === null || options === void 0 ? void 0 : options.showMetamask) && (sdk === null || sdk === void 0 ? void 0 : sdk.hasPlugin("web3")) && (React.createElement("button", { className: "shogun-auth-option-button", onClick: handleWeb3Auth, disabled: loading },
|
|
406
|
+
React.createElement(WalletIcon, null),
|
|
407
|
+
React.createElement("span", null, "Continue with Wallet"))),
|
|
408
|
+
(options === null || options === void 0 ? void 0 : options.showWebauthn) && (sdk === null || sdk === void 0 ? void 0 : sdk.hasPlugin("webauthn")) && (React.createElement("button", { className: "shogun-auth-option-button", onClick: handleWebAuthnAuth, disabled: loading },
|
|
409
|
+
React.createElement(WebAuthnIcon, null),
|
|
410
|
+
React.createElement("span", null, "Continue with Passkey"))),
|
|
411
|
+
(options === null || options === void 0 ? void 0 : options.showNostr) && (sdk === null || sdk === void 0 ? void 0 : sdk.hasPlugin("nostr")) && (React.createElement("button", { className: "shogun-auth-option-button", onClick: handleNostrAuth, disabled: loading },
|
|
412
|
+
React.createElement(NostrIcon, null),
|
|
413
|
+
React.createElement("span", null, "Continue with Nostr"))),
|
|
414
|
+
(options === null || options === void 0 ? void 0 : options.showOauth) && (sdk === null || sdk === void 0 ? void 0 : sdk.hasPlugin("oauth")) && (React.createElement("button", { className: "shogun-auth-option-button shogun-google-button", onClick: () => handleOAuth("google"), disabled: loading },
|
|
415
|
+
React.createElement(GoogleIcon, null),
|
|
416
|
+
React.createElement("span", null, "Continue with Google")))),
|
|
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"))))))));
|
|
445
|
+
};
|
|
446
|
+
Button.displayName = "ShogunButton";
|
|
447
|
+
return Object.assign(Button, {
|
|
448
|
+
Provider: ShogunButtonProvider,
|
|
449
|
+
useShogun: useShogun,
|
|
450
|
+
});
|
|
451
|
+
})();
|