@vylth/nexus-react 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +110 -0
- package/dist/index.js +25 -5
- package/dist/index.mjs +26 -6
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# @vylth/nexus-react
|
|
2
|
+
|
|
3
|
+
Nexus SSO SDK for React — add authentication to any VYLTH app in minutes.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @vylth/nexus-react
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
### 1. Wrap with NexusProvider
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
import { BrowserRouter } from 'react-router-dom';
|
|
17
|
+
import { NexusProvider } from '@vylth/nexus-react';
|
|
18
|
+
|
|
19
|
+
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
20
|
+
<BrowserRouter>
|
|
21
|
+
<NexusProvider
|
|
22
|
+
nexusUrl="https://accounts.vylth.com"
|
|
23
|
+
clientId="YOUR_CLIENT_ID"
|
|
24
|
+
>
|
|
25
|
+
<App />
|
|
26
|
+
</NexusProvider>
|
|
27
|
+
</BrowserRouter>
|
|
28
|
+
);
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### 2. Add Callback Route
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
import { NexusCallback } from '@vylth/nexus-react';
|
|
35
|
+
|
|
36
|
+
<Route path="/auth/callback" element={
|
|
37
|
+
<NexusCallback
|
|
38
|
+
onSuccess={() => window.location.replace('/')}
|
|
39
|
+
onError={() => window.location.replace('/login')}
|
|
40
|
+
/>
|
|
41
|
+
} />
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The callback route must match the redirect URI registered in Nexus.
|
|
45
|
+
|
|
46
|
+
### 3. Use Auth in Components
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
import { useNexus } from '@vylth/nexus-react';
|
|
50
|
+
|
|
51
|
+
function MyComponent() {
|
|
52
|
+
const { user, isAuthenticated, isLoading, login, logout } = useNexus();
|
|
53
|
+
|
|
54
|
+
if (isLoading) return <Spinner />;
|
|
55
|
+
if (!isAuthenticated) { login(); return null; }
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<div>
|
|
59
|
+
<p>Welcome, {user.username}</p>
|
|
60
|
+
<p>Role: {user.globalRole}</p>
|
|
61
|
+
<button onClick={logout}>Logout</button>
|
|
62
|
+
</div>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### 4. Login Button
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
import { NexusLoginButton } from '@vylth/nexus-react';
|
|
71
|
+
|
|
72
|
+
<NexusLoginButton />
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Exports
|
|
76
|
+
|
|
77
|
+
| Export | Type | Description |
|
|
78
|
+
|--------|------|-------------|
|
|
79
|
+
| `NexusProvider` | Component | Context provider — wrap your app |
|
|
80
|
+
| `NexusCallback` | Component | OAuth callback handler |
|
|
81
|
+
| `NexusLoginButton` | Component | Pre-styled login button |
|
|
82
|
+
| `useNexus` | Hook | Access auth state and user info |
|
|
83
|
+
|
|
84
|
+
## User Fields
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
interface NexusUser {
|
|
88
|
+
id: string;
|
|
89
|
+
email: string;
|
|
90
|
+
username?: string;
|
|
91
|
+
firstName: string;
|
|
92
|
+
lastName: string;
|
|
93
|
+
avatar?: string;
|
|
94
|
+
globalRole: string; // citizen, pioneer, navigator, commander, executor
|
|
95
|
+
emailVerified: boolean;
|
|
96
|
+
affiliateCode?: string;
|
|
97
|
+
appRoles?: Record<string, string>;
|
|
98
|
+
twoFactorVerified?: boolean;
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Prerequisites
|
|
103
|
+
|
|
104
|
+
1. Register your app in Nexus (accounts.vylth.com → Admin → Manage Apps)
|
|
105
|
+
2. Save the `client_id` from registration
|
|
106
|
+
3. Set your redirect URI (e.g., `https://yourapp.vylth.com/auth/callback`)
|
|
107
|
+
|
|
108
|
+
## Full Docs
|
|
109
|
+
|
|
110
|
+
See the complete integration guide at [accounts.vylth.com/docs](https://accounts.vylth.com/docs)
|
package/dist/index.js
CHANGED
|
@@ -285,8 +285,10 @@ var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
|
285
285
|
function NexusCallback({ onSuccess, onError }) {
|
|
286
286
|
const config = (0, import_react2.useContext)(NexusConfigContext);
|
|
287
287
|
const [error, setError] = (0, import_react2.useState)("");
|
|
288
|
+
const processed = (0, import_react2.useRef)(false);
|
|
288
289
|
(0, import_react2.useEffect)(() => {
|
|
289
|
-
if (!config) return;
|
|
290
|
+
if (!config || processed.current) return;
|
|
291
|
+
processed.current = true;
|
|
290
292
|
const params = new URLSearchParams(window.location.search);
|
|
291
293
|
const code = params.get("code");
|
|
292
294
|
const state = params.get("state");
|
|
@@ -339,11 +341,29 @@ function NexusCallback({ onSuccess, onError }) {
|
|
|
339
341
|
)
|
|
340
342
|
] });
|
|
341
343
|
}
|
|
342
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.
|
|
343
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { width: "
|
|
344
|
-
|
|
344
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", minHeight: "100vh", background: "#0a0a0a", color: "#fff", gap: "20px" }, children: [
|
|
345
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { width: "48px", height: "48px", animation: "nexus-spin 2s linear infinite" }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { viewBox: "0 0 64 64", fill: "none", xmlns: "http://www.w3.org/2000/svg", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("g", { stroke: "rgba(255,255,255,0.5)", strokeWidth: "2", strokeLinecap: "round", children: [
|
|
346
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "32", cy: "32", r: "4", fill: "rgba(255,255,255,0.7)" }),
|
|
347
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "32", cy: "12", r: "3", fill: "rgba(255,255,255,0.5)" }),
|
|
348
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "32", y1: "15", x2: "32", y2: "28", opacity: "0.4" }),
|
|
349
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "32", cy: "52", r: "3", fill: "rgba(255,255,255,0.5)" }),
|
|
350
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "32", y1: "36", x2: "32", y2: "49", opacity: "0.4" }),
|
|
351
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "12", cy: "32", r: "3", fill: "rgba(255,255,255,0.5)" }),
|
|
352
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "15", y1: "32", x2: "28", y2: "32", opacity: "0.4" }),
|
|
353
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "52", cy: "32", r: "3", fill: "rgba(255,255,255,0.5)" }),
|
|
354
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "36", y1: "32", x2: "49", y2: "32", opacity: "0.4" }),
|
|
355
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "18", cy: "18", r: "2.5", fill: "rgba(255,255,255,0.35)" }),
|
|
356
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "20", y1: "20", x2: "29", y2: "29", opacity: "0.25" }),
|
|
357
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "46", cy: "18", r: "2.5", fill: "rgba(255,255,255,0.35)" }),
|
|
358
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "44", y1: "20", x2: "35", y2: "29", opacity: "0.25" }),
|
|
359
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "18", cy: "46", r: "2.5", fill: "rgba(255,255,255,0.35)" }),
|
|
360
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "20", y1: "44", x2: "29", y2: "35", opacity: "0.25" }),
|
|
361
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "46", cy: "46", r: "2.5", fill: "rgba(255,255,255,0.35)" }),
|
|
362
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "44", y1: "44", x2: "35", y2: "35", opacity: "0.25" })
|
|
363
|
+
] }) }) }),
|
|
364
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: { fontFamily: "monospace", fontSize: "14px", opacity: 0.5 }, children: "Authenticating with Nexus..." }),
|
|
345
365
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("style", { children: `@keyframes nexus-spin { to { transform: rotate(360deg) } }` })
|
|
346
|
-
] })
|
|
366
|
+
] });
|
|
347
367
|
}
|
|
348
368
|
|
|
349
369
|
// src/NexusLoginButton.tsx
|
package/dist/index.mjs
CHANGED
|
@@ -243,13 +243,15 @@ function NexusProvider({
|
|
|
243
243
|
var NexusConfigContext = createContext(null);
|
|
244
244
|
|
|
245
245
|
// src/NexusCallback.tsx
|
|
246
|
-
import { useContext, useEffect as useEffect2, useState as useState2 } from "react";
|
|
246
|
+
import { useContext, useEffect as useEffect2, useRef, useState as useState2 } from "react";
|
|
247
247
|
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
248
248
|
function NexusCallback({ onSuccess, onError }) {
|
|
249
249
|
const config = useContext(NexusConfigContext);
|
|
250
250
|
const [error, setError] = useState2("");
|
|
251
|
+
const processed = useRef(false);
|
|
251
252
|
useEffect2(() => {
|
|
252
|
-
if (!config) return;
|
|
253
|
+
if (!config || processed.current) return;
|
|
254
|
+
processed.current = true;
|
|
253
255
|
const params = new URLSearchParams(window.location.search);
|
|
254
256
|
const code = params.get("code");
|
|
255
257
|
const state = params.get("state");
|
|
@@ -302,11 +304,29 @@ function NexusCallback({ onSuccess, onError }) {
|
|
|
302
304
|
)
|
|
303
305
|
] });
|
|
304
306
|
}
|
|
305
|
-
return /* @__PURE__ */
|
|
306
|
-
/* @__PURE__ */ jsx2("div", { style: { width: "
|
|
307
|
-
|
|
307
|
+
return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", minHeight: "100vh", background: "#0a0a0a", color: "#fff", gap: "20px" }, children: [
|
|
308
|
+
/* @__PURE__ */ jsx2("div", { style: { width: "48px", height: "48px", animation: "nexus-spin 2s linear infinite" }, children: /* @__PURE__ */ jsx2("svg", { viewBox: "0 0 64 64", fill: "none", xmlns: "http://www.w3.org/2000/svg", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsxs("g", { stroke: "rgba(255,255,255,0.5)", strokeWidth: "2", strokeLinecap: "round", children: [
|
|
309
|
+
/* @__PURE__ */ jsx2("circle", { cx: "32", cy: "32", r: "4", fill: "rgba(255,255,255,0.7)" }),
|
|
310
|
+
/* @__PURE__ */ jsx2("circle", { cx: "32", cy: "12", r: "3", fill: "rgba(255,255,255,0.5)" }),
|
|
311
|
+
/* @__PURE__ */ jsx2("line", { x1: "32", y1: "15", x2: "32", y2: "28", opacity: "0.4" }),
|
|
312
|
+
/* @__PURE__ */ jsx2("circle", { cx: "32", cy: "52", r: "3", fill: "rgba(255,255,255,0.5)" }),
|
|
313
|
+
/* @__PURE__ */ jsx2("line", { x1: "32", y1: "36", x2: "32", y2: "49", opacity: "0.4" }),
|
|
314
|
+
/* @__PURE__ */ jsx2("circle", { cx: "12", cy: "32", r: "3", fill: "rgba(255,255,255,0.5)" }),
|
|
315
|
+
/* @__PURE__ */ jsx2("line", { x1: "15", y1: "32", x2: "28", y2: "32", opacity: "0.4" }),
|
|
316
|
+
/* @__PURE__ */ jsx2("circle", { cx: "52", cy: "32", r: "3", fill: "rgba(255,255,255,0.5)" }),
|
|
317
|
+
/* @__PURE__ */ jsx2("line", { x1: "36", y1: "32", x2: "49", y2: "32", opacity: "0.4" }),
|
|
318
|
+
/* @__PURE__ */ jsx2("circle", { cx: "18", cy: "18", r: "2.5", fill: "rgba(255,255,255,0.35)" }),
|
|
319
|
+
/* @__PURE__ */ jsx2("line", { x1: "20", y1: "20", x2: "29", y2: "29", opacity: "0.25" }),
|
|
320
|
+
/* @__PURE__ */ jsx2("circle", { cx: "46", cy: "18", r: "2.5", fill: "rgba(255,255,255,0.35)" }),
|
|
321
|
+
/* @__PURE__ */ jsx2("line", { x1: "44", y1: "20", x2: "35", y2: "29", opacity: "0.25" }),
|
|
322
|
+
/* @__PURE__ */ jsx2("circle", { cx: "18", cy: "46", r: "2.5", fill: "rgba(255,255,255,0.35)" }),
|
|
323
|
+
/* @__PURE__ */ jsx2("line", { x1: "20", y1: "44", x2: "29", y2: "35", opacity: "0.25" }),
|
|
324
|
+
/* @__PURE__ */ jsx2("circle", { cx: "46", cy: "46", r: "2.5", fill: "rgba(255,255,255,0.35)" }),
|
|
325
|
+
/* @__PURE__ */ jsx2("line", { x1: "44", y1: "44", x2: "35", y2: "35", opacity: "0.25" })
|
|
326
|
+
] }) }) }),
|
|
327
|
+
/* @__PURE__ */ jsx2("p", { style: { fontFamily: "monospace", fontSize: "14px", opacity: 0.5 }, children: "Authenticating with Nexus..." }),
|
|
308
328
|
/* @__PURE__ */ jsx2("style", { children: `@keyframes nexus-spin { to { transform: rotate(360deg) } }` })
|
|
309
|
-
] })
|
|
329
|
+
] });
|
|
310
330
|
}
|
|
311
331
|
|
|
312
332
|
// src/NexusLoginButton.tsx
|