@nubitio/admin 0.5.22 → 0.5.23
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 +125 -5
- package/dist/index.d.cts +44 -1
- package/dist/index.d.mts +44 -1
- package/dist/index.mjs +126 -7
- package/package.json +5 -5
package/dist/index.cjs
CHANGED
|
@@ -389,14 +389,14 @@ const AdminShell = ({ title, menuItems, headerActions, renderUserMenu, renderThe
|
|
|
389
389
|
//#endregion
|
|
390
390
|
//#region packages/admin/auth/SessionContext.tsx
|
|
391
391
|
const SessionContext = (0, react.createContext)(null);
|
|
392
|
-
function joinApiPath$
|
|
392
|
+
function joinApiPath$3(apiBaseUrl, path) {
|
|
393
393
|
return `${apiBaseUrl.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
|
|
394
394
|
}
|
|
395
395
|
function SessionProvider({ apiBaseUrl = "/api/", mePath = "me", logoutPath = "auth/logout", children }) {
|
|
396
396
|
const [session, setSession] = (0, react.useState)({ status: "loading" });
|
|
397
397
|
const refresh = (0, react.useCallback)(async () => {
|
|
398
398
|
try {
|
|
399
|
-
const response = await fetch(joinApiPath$
|
|
399
|
+
const response = await fetch(joinApiPath$3(apiBaseUrl, mePath), { credentials: "include" });
|
|
400
400
|
if (!response.ok) {
|
|
401
401
|
setSession({ status: "anonymous" });
|
|
402
402
|
return;
|
|
@@ -413,7 +413,7 @@ function SessionProvider({ apiBaseUrl = "/api/", mePath = "me", logoutPath = "au
|
|
|
413
413
|
refresh();
|
|
414
414
|
}, [refresh]);
|
|
415
415
|
const logout = (0, react.useCallback)(async () => {
|
|
416
|
-
await fetch(joinApiPath$
|
|
416
|
+
await fetch(joinApiPath$3(apiBaseUrl, logoutPath), {
|
|
417
417
|
method: "POST",
|
|
418
418
|
credentials: "include"
|
|
419
419
|
});
|
|
@@ -482,7 +482,7 @@ function FeatureGate({ featureKey, ...props }) {
|
|
|
482
482
|
}
|
|
483
483
|
//#endregion
|
|
484
484
|
//#region packages/admin/auth/LoginPage.tsx
|
|
485
|
-
function joinApiPath$
|
|
485
|
+
function joinApiPath$2(apiBaseUrl, path) {
|
|
486
486
|
return `${apiBaseUrl.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
|
|
487
487
|
}
|
|
488
488
|
function LoginPage({ onLoggedIn, apiBaseUrl = "/api/", loginPath = "auth/login", title = "Nubit Admin", hint, defaultUsername = "" }) {
|
|
@@ -495,7 +495,7 @@ function LoginPage({ onLoggedIn, apiBaseUrl = "/api/", loginPath = "auth/login",
|
|
|
495
495
|
setBusy(true);
|
|
496
496
|
setError(null);
|
|
497
497
|
try {
|
|
498
|
-
const response = await fetch(joinApiPath$
|
|
498
|
+
const response = await fetch(joinApiPath$2(apiBaseUrl, loginPath), {
|
|
499
499
|
method: "POST",
|
|
500
500
|
headers: { "Content-Type": "application/json" },
|
|
501
501
|
credentials: "include",
|
|
@@ -573,6 +573,125 @@ function LoginPage({ onLoggedIn, apiBaseUrl = "/api/", loginPath = "auth/login",
|
|
|
573
573
|
});
|
|
574
574
|
}
|
|
575
575
|
//#endregion
|
|
576
|
+
//#region packages/admin/auth/RegisterPage.tsx
|
|
577
|
+
function joinApiPath$1(apiBaseUrl, path) {
|
|
578
|
+
return `${apiBaseUrl.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
|
|
579
|
+
}
|
|
580
|
+
function initialValues(fields) {
|
|
581
|
+
return Object.fromEntries(fields.map((field) => [field.name, field.defaultValue ?? ""]));
|
|
582
|
+
}
|
|
583
|
+
function RegisterPage({ fields, onRegistered, apiBaseUrl = "/api/", registerPath = "auth/register", title = "Create account", hint, submitLabel = "Create account", busyLabel = "Creating…", loginLink, loginPrompt = "Already have an account?" }) {
|
|
584
|
+
const [values, setValues] = (0, react.useState)((0, react.useMemo)(() => initialValues(fields), [fields]));
|
|
585
|
+
const [error, setError] = (0, react.useState)(null);
|
|
586
|
+
const [busy, setBusy] = (0, react.useState)(false);
|
|
587
|
+
const setValue = (name, value) => {
|
|
588
|
+
setValues((current) => ({
|
|
589
|
+
...current,
|
|
590
|
+
[name]: value
|
|
591
|
+
}));
|
|
592
|
+
};
|
|
593
|
+
const submit = async (event) => {
|
|
594
|
+
event.preventDefault();
|
|
595
|
+
setBusy(true);
|
|
596
|
+
setError(null);
|
|
597
|
+
try {
|
|
598
|
+
const response = await fetch(joinApiPath$1(apiBaseUrl, registerPath), {
|
|
599
|
+
method: "POST",
|
|
600
|
+
headers: { "Content-Type": "application/json" },
|
|
601
|
+
credentials: "include",
|
|
602
|
+
body: JSON.stringify(values)
|
|
603
|
+
});
|
|
604
|
+
const body = await response.json().catch(() => null);
|
|
605
|
+
if (!response.ok) {
|
|
606
|
+
setError(body?.error ?? body?.message ?? "Registration failed");
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
onRegistered();
|
|
610
|
+
} catch {
|
|
611
|
+
setError("Network error");
|
|
612
|
+
} finally {
|
|
613
|
+
setBusy(false);
|
|
614
|
+
}
|
|
615
|
+
};
|
|
616
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
617
|
+
style: {
|
|
618
|
+
display: "grid",
|
|
619
|
+
placeItems: "center",
|
|
620
|
+
minHeight: "100vh"
|
|
621
|
+
},
|
|
622
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.Card, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
|
|
623
|
+
onSubmit: submit,
|
|
624
|
+
style: {
|
|
625
|
+
display: "flex",
|
|
626
|
+
flexDirection: "column",
|
|
627
|
+
gap: 12,
|
|
628
|
+
width: 360,
|
|
629
|
+
padding: 8
|
|
630
|
+
},
|
|
631
|
+
children: [
|
|
632
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
633
|
+
style: { margin: 0 },
|
|
634
|
+
children: title
|
|
635
|
+
}),
|
|
636
|
+
hint && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
637
|
+
style: {
|
|
638
|
+
margin: 0,
|
|
639
|
+
color: "var(--text-secondary)"
|
|
640
|
+
},
|
|
641
|
+
children: hint
|
|
642
|
+
}),
|
|
643
|
+
fields.map((field) => {
|
|
644
|
+
const type = field.type ?? "text";
|
|
645
|
+
if (type === "select") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
646
|
+
value: values[field.name] ?? "",
|
|
647
|
+
onChange: (e) => setValue(field.name, e.target.value),
|
|
648
|
+
style: { width: "100%" },
|
|
649
|
+
"aria-label": field.placeholder ?? field.name,
|
|
650
|
+
children: (field.options ?? []).map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
651
|
+
value: option.value,
|
|
652
|
+
children: option.label
|
|
653
|
+
}, option.value))
|
|
654
|
+
}, field.name);
|
|
655
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.TextField, {
|
|
656
|
+
placeholder: field.placeholder,
|
|
657
|
+
type: type === "password" ? "password" : type === "email" ? "email" : "text",
|
|
658
|
+
value: values[field.name] ?? "",
|
|
659
|
+
autoComplete: field.autoComplete,
|
|
660
|
+
onChange: (e) => setValue(field.name, e.target.value)
|
|
661
|
+
}, field.name);
|
|
662
|
+
}),
|
|
663
|
+
error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
664
|
+
style: {
|
|
665
|
+
margin: 0,
|
|
666
|
+
color: "var(--error-color, #dc2626)"
|
|
667
|
+
},
|
|
668
|
+
children: error
|
|
669
|
+
}),
|
|
670
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.Button, {
|
|
671
|
+
variant: "primary",
|
|
672
|
+
type: "submit",
|
|
673
|
+
disabled: busy,
|
|
674
|
+
children: busy ? busyLabel : submitLabel
|
|
675
|
+
}),
|
|
676
|
+
loginLink && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
677
|
+
style: {
|
|
678
|
+
margin: 0,
|
|
679
|
+
color: "var(--text-secondary)"
|
|
680
|
+
},
|
|
681
|
+
children: [
|
|
682
|
+
loginPrompt,
|
|
683
|
+
" ",
|
|
684
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_router_dom.Link, {
|
|
685
|
+
to: loginLink.to,
|
|
686
|
+
children: loginLink.label
|
|
687
|
+
})
|
|
688
|
+
]
|
|
689
|
+
})
|
|
690
|
+
]
|
|
691
|
+
}) })
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
//#endregion
|
|
576
695
|
//#region packages/admin/runtime/useAppRuntime.ts
|
|
577
696
|
function useAppRuntime() {
|
|
578
697
|
const [toasts, setToasts] = (0, react.useState)([]);
|
|
@@ -842,6 +961,7 @@ exports.AdminShell = AdminShell;
|
|
|
842
961
|
exports.AdminSidebarMenu = AdminSidebarMenu;
|
|
843
962
|
exports.FeatureGate = FeatureGate;
|
|
844
963
|
exports.LoginPage = LoginPage;
|
|
964
|
+
exports.RegisterPage = RegisterPage;
|
|
845
965
|
exports.SessionProvider = SessionProvider;
|
|
846
966
|
exports.StaticSessionProvider = StaticSessionProvider;
|
|
847
967
|
exports.ToastHost = ToastHost;
|
package/dist/index.d.cts
CHANGED
|
@@ -186,6 +186,49 @@ declare function LoginPage({
|
|
|
186
186
|
defaultUsername
|
|
187
187
|
}: LoginPageProps): import("react").JSX.Element;
|
|
188
188
|
//#endregion
|
|
189
|
+
//#region packages/admin/auth/RegisterPage.d.ts
|
|
190
|
+
type RegisterFieldType = 'text' | 'email' | 'password' | 'select';
|
|
191
|
+
interface RegisterSelectOption {
|
|
192
|
+
value: string;
|
|
193
|
+
label: string;
|
|
194
|
+
}
|
|
195
|
+
interface RegisterField {
|
|
196
|
+
/** JSON body key sent to the register endpoint. */
|
|
197
|
+
name: string;
|
|
198
|
+
placeholder?: string;
|
|
199
|
+
type?: RegisterFieldType;
|
|
200
|
+
defaultValue?: string;
|
|
201
|
+
autoComplete?: string;
|
|
202
|
+
options?: RegisterSelectOption[];
|
|
203
|
+
}
|
|
204
|
+
interface RegisterPageProps {
|
|
205
|
+
fields: RegisterField[];
|
|
206
|
+
onRegistered: () => void;
|
|
207
|
+
apiBaseUrl?: string;
|
|
208
|
+
registerPath?: string;
|
|
209
|
+
title?: string;
|
|
210
|
+
hint?: string;
|
|
211
|
+
submitLabel?: string;
|
|
212
|
+
busyLabel?: string;
|
|
213
|
+
loginLink?: {
|
|
214
|
+
to: string;
|
|
215
|
+
label: string;
|
|
216
|
+
};
|
|
217
|
+
loginPrompt?: string;
|
|
218
|
+
}
|
|
219
|
+
declare function RegisterPage({
|
|
220
|
+
fields,
|
|
221
|
+
onRegistered,
|
|
222
|
+
apiBaseUrl,
|
|
223
|
+
registerPath,
|
|
224
|
+
title,
|
|
225
|
+
hint,
|
|
226
|
+
submitLabel,
|
|
227
|
+
busyLabel,
|
|
228
|
+
loginLink,
|
|
229
|
+
loginPrompt
|
|
230
|
+
}: RegisterPageProps): import("react").JSX.Element;
|
|
231
|
+
//#endregion
|
|
189
232
|
//#region packages/admin/runtime/useAppRuntime.d.ts
|
|
190
233
|
type NotificationType = 'success' | 'error' | 'warning' | 'info';
|
|
191
234
|
type ToastItem = {
|
|
@@ -307,4 +350,4 @@ declare function hasAnyRole(required: string | string[] | undefined, roles: stri
|
|
|
307
350
|
*/
|
|
308
351
|
declare function filterMenuByRoles(items: NubitAppMenuItem[], roles: string[]): AdminMenuItem[];
|
|
309
352
|
//#endregion
|
|
310
|
-
export { AdminHeader, type AdminHeaderAction, type AdminHeaderProps, type AdminMenuItem, type AdminMenuSubItem, AdminShell, type AdminShellProps, AdminSidebarMenu, type AdminSidebarMenuProps, type AdminSidebarMenuSelectEvent, type AppProfile, type CreateNubitAppConfig, FeatureGate, type FeatureGateProps, LoginPage, type LoginPageProps, type NotificationType, type NubitApp, type NubitAppMenuContext, type NubitAppMenuItem, type NubitAppMenuSubItem, type NubitAppRoute, type NubitAppUserMenuContext, type RuntimeConfig, type RuntimeConfigState, type SessionContextValue, type SessionFeatureEntitlement, type SessionProfile, SessionProvider, type SessionProviderConfig, type SessionState, type SessionTenant, StaticSessionProvider, ToastHost, type ToastHostProps, type ToastItem, type UseRuntimeConfigOptions, createNubitApp, filterMenuByRoles, hasAnyRole, useAppRuntime, useFeature, useFeatureConfig, useRuntimeConfig, useScreenSize, useScreenSizeClass, useSession };
|
|
353
|
+
export { AdminHeader, type AdminHeaderAction, type AdminHeaderProps, type AdminMenuItem, type AdminMenuSubItem, AdminShell, type AdminShellProps, AdminSidebarMenu, type AdminSidebarMenuProps, type AdminSidebarMenuSelectEvent, type AppProfile, type CreateNubitAppConfig, FeatureGate, type FeatureGateProps, LoginPage, type LoginPageProps, type NotificationType, type NubitApp, type NubitAppMenuContext, type NubitAppMenuItem, type NubitAppMenuSubItem, type NubitAppRoute, type NubitAppUserMenuContext, type RegisterField, type RegisterFieldType, RegisterPage, type RegisterPageProps, type RegisterSelectOption, type RuntimeConfig, type RuntimeConfigState, type SessionContextValue, type SessionFeatureEntitlement, type SessionProfile, SessionProvider, type SessionProviderConfig, type SessionState, type SessionTenant, StaticSessionProvider, ToastHost, type ToastHostProps, type ToastItem, type UseRuntimeConfigOptions, createNubitApp, filterMenuByRoles, hasAnyRole, useAppRuntime, useFeature, useFeatureConfig, useRuntimeConfig, useScreenSize, useScreenSizeClass, useSession };
|
package/dist/index.d.mts
CHANGED
|
@@ -186,6 +186,49 @@ declare function LoginPage({
|
|
|
186
186
|
defaultUsername
|
|
187
187
|
}: LoginPageProps): import("react").JSX.Element;
|
|
188
188
|
//#endregion
|
|
189
|
+
//#region packages/admin/auth/RegisterPage.d.ts
|
|
190
|
+
type RegisterFieldType = 'text' | 'email' | 'password' | 'select';
|
|
191
|
+
interface RegisterSelectOption {
|
|
192
|
+
value: string;
|
|
193
|
+
label: string;
|
|
194
|
+
}
|
|
195
|
+
interface RegisterField {
|
|
196
|
+
/** JSON body key sent to the register endpoint. */
|
|
197
|
+
name: string;
|
|
198
|
+
placeholder?: string;
|
|
199
|
+
type?: RegisterFieldType;
|
|
200
|
+
defaultValue?: string;
|
|
201
|
+
autoComplete?: string;
|
|
202
|
+
options?: RegisterSelectOption[];
|
|
203
|
+
}
|
|
204
|
+
interface RegisterPageProps {
|
|
205
|
+
fields: RegisterField[];
|
|
206
|
+
onRegistered: () => void;
|
|
207
|
+
apiBaseUrl?: string;
|
|
208
|
+
registerPath?: string;
|
|
209
|
+
title?: string;
|
|
210
|
+
hint?: string;
|
|
211
|
+
submitLabel?: string;
|
|
212
|
+
busyLabel?: string;
|
|
213
|
+
loginLink?: {
|
|
214
|
+
to: string;
|
|
215
|
+
label: string;
|
|
216
|
+
};
|
|
217
|
+
loginPrompt?: string;
|
|
218
|
+
}
|
|
219
|
+
declare function RegisterPage({
|
|
220
|
+
fields,
|
|
221
|
+
onRegistered,
|
|
222
|
+
apiBaseUrl,
|
|
223
|
+
registerPath,
|
|
224
|
+
title,
|
|
225
|
+
hint,
|
|
226
|
+
submitLabel,
|
|
227
|
+
busyLabel,
|
|
228
|
+
loginLink,
|
|
229
|
+
loginPrompt
|
|
230
|
+
}: RegisterPageProps): import("react").JSX.Element;
|
|
231
|
+
//#endregion
|
|
189
232
|
//#region packages/admin/runtime/useAppRuntime.d.ts
|
|
190
233
|
type NotificationType = 'success' | 'error' | 'warning' | 'info';
|
|
191
234
|
type ToastItem = {
|
|
@@ -307,4 +350,4 @@ declare function hasAnyRole(required: string | string[] | undefined, roles: stri
|
|
|
307
350
|
*/
|
|
308
351
|
declare function filterMenuByRoles(items: NubitAppMenuItem[], roles: string[]): AdminMenuItem[];
|
|
309
352
|
//#endregion
|
|
310
|
-
export { AdminHeader, type AdminHeaderAction, type AdminHeaderProps, type AdminMenuItem, type AdminMenuSubItem, AdminShell, type AdminShellProps, AdminSidebarMenu, type AdminSidebarMenuProps, type AdminSidebarMenuSelectEvent, type AppProfile, type CreateNubitAppConfig, FeatureGate, type FeatureGateProps, LoginPage, type LoginPageProps, type NotificationType, type NubitApp, type NubitAppMenuContext, type NubitAppMenuItem, type NubitAppMenuSubItem, type NubitAppRoute, type NubitAppUserMenuContext, type RuntimeConfig, type RuntimeConfigState, type SessionContextValue, type SessionFeatureEntitlement, type SessionProfile, SessionProvider, type SessionProviderConfig, type SessionState, type SessionTenant, StaticSessionProvider, ToastHost, type ToastHostProps, type ToastItem, type UseRuntimeConfigOptions, createNubitApp, filterMenuByRoles, hasAnyRole, useAppRuntime, useFeature, useFeatureConfig, useRuntimeConfig, useScreenSize, useScreenSizeClass, useSession };
|
|
353
|
+
export { AdminHeader, type AdminHeaderAction, type AdminHeaderProps, type AdminMenuItem, type AdminMenuSubItem, AdminShell, type AdminShellProps, AdminSidebarMenu, type AdminSidebarMenuProps, type AdminSidebarMenuSelectEvent, type AppProfile, type CreateNubitAppConfig, FeatureGate, type FeatureGateProps, LoginPage, type LoginPageProps, type NotificationType, type NubitApp, type NubitAppMenuContext, type NubitAppMenuItem, type NubitAppMenuSubItem, type NubitAppRoute, type NubitAppUserMenuContext, type RegisterField, type RegisterFieldType, RegisterPage, type RegisterPageProps, type RegisterSelectOption, type RuntimeConfig, type RuntimeConfigState, type SessionContextValue, type SessionFeatureEntitlement, type SessionProfile, SessionProvider, type SessionProviderConfig, type SessionState, type SessionTenant, StaticSessionProvider, ToastHost, type ToastHostProps, type ToastItem, type UseRuntimeConfigOptions, createNubitApp, filterMenuByRoles, hasAnyRole, useAppRuntime, useFeature, useFeatureConfig, useRuntimeConfig, useScreenSize, useScreenSizeClass, useSession };
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react";
|
|
2
|
-
import { BrowserRouter, Navigate, Route, Routes, useLocation, useNavigate } from "react-router-dom";
|
|
2
|
+
import { BrowserRouter, Link, Navigate, Route, Routes, useLocation, useNavigate } from "react-router-dom";
|
|
3
3
|
import { Badge, Button, Card, FeatureGate as FeatureGate$1, IconButton, TextField, ThemeProvider, ThemeSwitcher, useFloatingPanel } from "@nubitio/ui";
|
|
4
4
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
5
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
@@ -365,14 +365,14 @@ const AdminShell = ({ title, menuItems, headerActions, renderUserMenu, renderThe
|
|
|
365
365
|
//#endregion
|
|
366
366
|
//#region packages/admin/auth/SessionContext.tsx
|
|
367
367
|
const SessionContext = createContext(null);
|
|
368
|
-
function joinApiPath$
|
|
368
|
+
function joinApiPath$3(apiBaseUrl, path) {
|
|
369
369
|
return `${apiBaseUrl.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
|
|
370
370
|
}
|
|
371
371
|
function SessionProvider({ apiBaseUrl = "/api/", mePath = "me", logoutPath = "auth/logout", children }) {
|
|
372
372
|
const [session, setSession] = useState({ status: "loading" });
|
|
373
373
|
const refresh = useCallback(async () => {
|
|
374
374
|
try {
|
|
375
|
-
const response = await fetch(joinApiPath$
|
|
375
|
+
const response = await fetch(joinApiPath$3(apiBaseUrl, mePath), { credentials: "include" });
|
|
376
376
|
if (!response.ok) {
|
|
377
377
|
setSession({ status: "anonymous" });
|
|
378
378
|
return;
|
|
@@ -389,7 +389,7 @@ function SessionProvider({ apiBaseUrl = "/api/", mePath = "me", logoutPath = "au
|
|
|
389
389
|
refresh();
|
|
390
390
|
}, [refresh]);
|
|
391
391
|
const logout = useCallback(async () => {
|
|
392
|
-
await fetch(joinApiPath$
|
|
392
|
+
await fetch(joinApiPath$3(apiBaseUrl, logoutPath), {
|
|
393
393
|
method: "POST",
|
|
394
394
|
credentials: "include"
|
|
395
395
|
});
|
|
@@ -458,7 +458,7 @@ function FeatureGate({ featureKey, ...props }) {
|
|
|
458
458
|
}
|
|
459
459
|
//#endregion
|
|
460
460
|
//#region packages/admin/auth/LoginPage.tsx
|
|
461
|
-
function joinApiPath$
|
|
461
|
+
function joinApiPath$2(apiBaseUrl, path) {
|
|
462
462
|
return `${apiBaseUrl.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
|
|
463
463
|
}
|
|
464
464
|
function LoginPage({ onLoggedIn, apiBaseUrl = "/api/", loginPath = "auth/login", title = "Nubit Admin", hint, defaultUsername = "" }) {
|
|
@@ -471,7 +471,7 @@ function LoginPage({ onLoggedIn, apiBaseUrl = "/api/", loginPath = "auth/login",
|
|
|
471
471
|
setBusy(true);
|
|
472
472
|
setError(null);
|
|
473
473
|
try {
|
|
474
|
-
const response = await fetch(joinApiPath$
|
|
474
|
+
const response = await fetch(joinApiPath$2(apiBaseUrl, loginPath), {
|
|
475
475
|
method: "POST",
|
|
476
476
|
headers: { "Content-Type": "application/json" },
|
|
477
477
|
credentials: "include",
|
|
@@ -549,6 +549,125 @@ function LoginPage({ onLoggedIn, apiBaseUrl = "/api/", loginPath = "auth/login",
|
|
|
549
549
|
});
|
|
550
550
|
}
|
|
551
551
|
//#endregion
|
|
552
|
+
//#region packages/admin/auth/RegisterPage.tsx
|
|
553
|
+
function joinApiPath$1(apiBaseUrl, path) {
|
|
554
|
+
return `${apiBaseUrl.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
|
|
555
|
+
}
|
|
556
|
+
function initialValues(fields) {
|
|
557
|
+
return Object.fromEntries(fields.map((field) => [field.name, field.defaultValue ?? ""]));
|
|
558
|
+
}
|
|
559
|
+
function RegisterPage({ fields, onRegistered, apiBaseUrl = "/api/", registerPath = "auth/register", title = "Create account", hint, submitLabel = "Create account", busyLabel = "Creating…", loginLink, loginPrompt = "Already have an account?" }) {
|
|
560
|
+
const [values, setValues] = useState(useMemo(() => initialValues(fields), [fields]));
|
|
561
|
+
const [error, setError] = useState(null);
|
|
562
|
+
const [busy, setBusy] = useState(false);
|
|
563
|
+
const setValue = (name, value) => {
|
|
564
|
+
setValues((current) => ({
|
|
565
|
+
...current,
|
|
566
|
+
[name]: value
|
|
567
|
+
}));
|
|
568
|
+
};
|
|
569
|
+
const submit = async (event) => {
|
|
570
|
+
event.preventDefault();
|
|
571
|
+
setBusy(true);
|
|
572
|
+
setError(null);
|
|
573
|
+
try {
|
|
574
|
+
const response = await fetch(joinApiPath$1(apiBaseUrl, registerPath), {
|
|
575
|
+
method: "POST",
|
|
576
|
+
headers: { "Content-Type": "application/json" },
|
|
577
|
+
credentials: "include",
|
|
578
|
+
body: JSON.stringify(values)
|
|
579
|
+
});
|
|
580
|
+
const body = await response.json().catch(() => null);
|
|
581
|
+
if (!response.ok) {
|
|
582
|
+
setError(body?.error ?? body?.message ?? "Registration failed");
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
onRegistered();
|
|
586
|
+
} catch {
|
|
587
|
+
setError("Network error");
|
|
588
|
+
} finally {
|
|
589
|
+
setBusy(false);
|
|
590
|
+
}
|
|
591
|
+
};
|
|
592
|
+
return /* @__PURE__ */ jsx("div", {
|
|
593
|
+
style: {
|
|
594
|
+
display: "grid",
|
|
595
|
+
placeItems: "center",
|
|
596
|
+
minHeight: "100vh"
|
|
597
|
+
},
|
|
598
|
+
children: /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsxs("form", {
|
|
599
|
+
onSubmit: submit,
|
|
600
|
+
style: {
|
|
601
|
+
display: "flex",
|
|
602
|
+
flexDirection: "column",
|
|
603
|
+
gap: 12,
|
|
604
|
+
width: 360,
|
|
605
|
+
padding: 8
|
|
606
|
+
},
|
|
607
|
+
children: [
|
|
608
|
+
/* @__PURE__ */ jsx("h2", {
|
|
609
|
+
style: { margin: 0 },
|
|
610
|
+
children: title
|
|
611
|
+
}),
|
|
612
|
+
hint && /* @__PURE__ */ jsx("p", {
|
|
613
|
+
style: {
|
|
614
|
+
margin: 0,
|
|
615
|
+
color: "var(--text-secondary)"
|
|
616
|
+
},
|
|
617
|
+
children: hint
|
|
618
|
+
}),
|
|
619
|
+
fields.map((field) => {
|
|
620
|
+
const type = field.type ?? "text";
|
|
621
|
+
if (type === "select") return /* @__PURE__ */ jsx("select", {
|
|
622
|
+
value: values[field.name] ?? "",
|
|
623
|
+
onChange: (e) => setValue(field.name, e.target.value),
|
|
624
|
+
style: { width: "100%" },
|
|
625
|
+
"aria-label": field.placeholder ?? field.name,
|
|
626
|
+
children: (field.options ?? []).map((option) => /* @__PURE__ */ jsx("option", {
|
|
627
|
+
value: option.value,
|
|
628
|
+
children: option.label
|
|
629
|
+
}, option.value))
|
|
630
|
+
}, field.name);
|
|
631
|
+
return /* @__PURE__ */ jsx(TextField, {
|
|
632
|
+
placeholder: field.placeholder,
|
|
633
|
+
type: type === "password" ? "password" : type === "email" ? "email" : "text",
|
|
634
|
+
value: values[field.name] ?? "",
|
|
635
|
+
autoComplete: field.autoComplete,
|
|
636
|
+
onChange: (e) => setValue(field.name, e.target.value)
|
|
637
|
+
}, field.name);
|
|
638
|
+
}),
|
|
639
|
+
error && /* @__PURE__ */ jsx("p", {
|
|
640
|
+
style: {
|
|
641
|
+
margin: 0,
|
|
642
|
+
color: "var(--error-color, #dc2626)"
|
|
643
|
+
},
|
|
644
|
+
children: error
|
|
645
|
+
}),
|
|
646
|
+
/* @__PURE__ */ jsx(Button, {
|
|
647
|
+
variant: "primary",
|
|
648
|
+
type: "submit",
|
|
649
|
+
disabled: busy,
|
|
650
|
+
children: busy ? busyLabel : submitLabel
|
|
651
|
+
}),
|
|
652
|
+
loginLink && /* @__PURE__ */ jsxs("p", {
|
|
653
|
+
style: {
|
|
654
|
+
margin: 0,
|
|
655
|
+
color: "var(--text-secondary)"
|
|
656
|
+
},
|
|
657
|
+
children: [
|
|
658
|
+
loginPrompt,
|
|
659
|
+
" ",
|
|
660
|
+
/* @__PURE__ */ jsx(Link, {
|
|
661
|
+
to: loginLink.to,
|
|
662
|
+
children: loginLink.label
|
|
663
|
+
})
|
|
664
|
+
]
|
|
665
|
+
})
|
|
666
|
+
]
|
|
667
|
+
}) })
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
//#endregion
|
|
552
671
|
//#region packages/admin/runtime/useAppRuntime.ts
|
|
553
672
|
function useAppRuntime() {
|
|
554
673
|
const [toasts, setToasts] = useState([]);
|
|
@@ -813,4 +932,4 @@ function createNubitApp(config) {
|
|
|
813
932
|
return { App };
|
|
814
933
|
}
|
|
815
934
|
//#endregion
|
|
816
|
-
export { AdminHeader, AdminShell, AdminSidebarMenu, FeatureGate, LoginPage, SessionProvider, StaticSessionProvider, ToastHost, createNubitApp, filterMenuByRoles, hasAnyRole, useAppRuntime, useFeature, useFeatureConfig, useRuntimeConfig, useScreenSize, useScreenSizeClass, useSession };
|
|
935
|
+
export { AdminHeader, AdminShell, AdminSidebarMenu, FeatureGate, LoginPage, RegisterPage, SessionProvider, StaticSessionProvider, ToastHost, createNubitApp, filterMenuByRoles, hasAnyRole, useAppRuntime, useFeature, useFeatureConfig, useRuntimeConfig, useScreenSize, useScreenSizeClass, useSession };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nubitio/admin",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.23",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Admin shell layout components: responsive sidebar, header, and screen size utilities for Nubit apps.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -53,9 +53,9 @@
|
|
|
53
53
|
"react": "^19.0.0",
|
|
54
54
|
"react-dom": "^19.0.0",
|
|
55
55
|
"react-router-dom": "^6.0.0",
|
|
56
|
-
"@nubitio/
|
|
57
|
-
"@nubitio/
|
|
58
|
-
"@nubitio/hydra": "^0.5.
|
|
59
|
-
"@nubitio/ui": "^0.5.
|
|
56
|
+
"@nubitio/crud": "^0.5.23",
|
|
57
|
+
"@nubitio/core": "^0.5.23",
|
|
58
|
+
"@nubitio/hydra": "^0.5.23",
|
|
59
|
+
"@nubitio/ui": "^0.5.23"
|
|
60
60
|
}
|
|
61
61
|
}
|