@alepha/ui 0.15.2 → 0.15.4
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/admin/AdminAudits-BU-p1g7A.js +3 -0
- package/dist/admin/{AdminAudits-C0DPYw0W.js → AdminAudits-Oh7iAfQa.js} +2 -2
- package/dist/admin/AdminAudits-Oh7iAfQa.js.map +1 -0
- package/dist/admin/{AdminUserCreate-DiXi1EWB.js → AdminUserCreate-BVIm4JdN.js} +2 -2
- package/dist/admin/AdminUserCreate-BVIm4JdN.js.map +1 -0
- package/dist/admin/{AdminUserCreate-Chr-7hLk.js → AdminUserCreate-C1aInRDk.js} +1 -1
- package/dist/admin/{AdminUserLayout-D9bqGt6T.js → AdminUserLayout-BnfBC1gD.js} +2 -2
- package/dist/admin/{AdminUserLayout-D9bqGt6T.js.map → AdminUserLayout-BnfBC1gD.js.map} +1 -1
- package/dist/admin/{AdminUserLayout-CfeQHH6e.js → AdminUserLayout-gb-nbggz.js} +1 -1
- package/dist/admin/{AdminUserSettings-BnzRAcqV.js → AdminUserSettings-DZ9iWhJW.js} +2 -2
- package/dist/admin/AdminUserSettings-DZ9iWhJW.js.map +1 -0
- package/dist/admin/AdminUserSettings-Dg-wTRzN.js +3 -0
- package/dist/admin/{AdminUsers-CYkcUWCg.js → AdminUsers-D6Y5K8Am.js} +2 -2
- package/dist/admin/AdminUsers-D6Y5K8Am.js.map +1 -0
- package/dist/admin/AdminUsers-RCaxccEW.js +3 -0
- package/dist/admin/index.d.ts +21 -17
- package/dist/admin/index.d.ts.map +1 -1
- package/dist/admin/index.js +20 -13
- package/dist/admin/index.js.map +1 -1
- package/dist/auth/{Login-BAFVcX_J.js → Login-BBqTosqZ.js} +2 -2
- package/dist/auth/Login-BBqTosqZ.js.map +1 -0
- package/dist/auth/Login-CoU63mMR.js +4 -0
- package/dist/auth/Profile-Bxj8Nwom.js +150 -0
- package/dist/auth/Profile-Bxj8Nwom.js.map +1 -0
- package/dist/auth/Register-BV_oa_AK.js +4 -0
- package/dist/auth/{Register-CZRXEcWy.js → Register-Ce675Crg.js} +4 -4
- package/dist/auth/Register-Ce675Crg.js.map +1 -0
- package/dist/auth/ResetPassword-D5wC8GAA.js +3 -0
- package/dist/auth/{ResetPassword-DTYNsBIj.js → ResetPassword-DWdt7c40.js} +2 -2
- package/dist/auth/{ResetPassword-DTYNsBIj.js.map → ResetPassword-DWdt7c40.js.map} +1 -1
- package/dist/auth/{VerifyEmail-DolENWGn.js → VerifyEmail-CI4JwByV.js} +2 -2
- package/dist/auth/{VerifyEmail-DolENWGn.js.map → VerifyEmail-CI4JwByV.js.map} +1 -1
- package/dist/auth/VerifyEmail-DAfqVm5s.js +3 -0
- package/dist/auth/index.d.ts +6 -1
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +47 -13
- package/dist/auth/index.js.map +1 -1
- package/dist/core/index.d.ts +111 -29
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +324 -251
- package/dist/core/index.js.map +1 -1
- package/dist/demo/{DemoLogin-mtkN6340.js → DemoLogin-S-b15cmE.js} +2 -2
- package/dist/demo/DemoLogin-S-b15cmE.js.map +1 -0
- package/dist/demo/{DemoRegister-C0MW7anp.js → DemoRegister-B29MdAaZ.js} +3 -3
- package/dist/demo/DemoRegister-B29MdAaZ.js.map +1 -0
- package/dist/demo/index.js +2 -2
- package/package.json +3 -2
- package/src/admin/AdminRouter.ts +2 -2
- package/src/admin/components/audits/AdminAudits.tsx +1 -1
- package/src/admin/components/users/AdminUserCreate.tsx +1 -1
- package/src/admin/components/users/AdminUserLayout.tsx +1 -1
- package/src/admin/components/users/AdminUserSettings.tsx +1 -1
- package/src/admin/components/users/AdminUsers.tsx +1 -1
- package/src/admin/index.ts +11 -1
- package/src/auth/AuthRouter.ts +12 -0
- package/src/auth/components/Login.tsx +1 -1
- package/src/auth/components/Profile.tsx +157 -0
- package/src/auth/components/Register.tsx +2 -2
- package/src/auth/components/buttons/UserButton.tsx +34 -2
- package/src/auth/index.ts +11 -1
- package/src/core/UiRouter.ts +15 -0
- package/src/core/atoms/alephaThemeListAtom.ts +3 -1
- package/src/core/components/buttons/ActionButton.tsx +2 -2
- package/src/core/components/layout/AdminShell.tsx +46 -18
- package/src/core/components/layout/AppBar.tsx +235 -18
- package/src/core/components/layout/Omnibar.tsx +2 -2
- package/src/core/components/layout/Sidebar.tsx +1 -5
- package/src/core/index.ts +13 -27
- package/dist/admin/AdminAudits-BlGGKLof.js +0 -3
- package/dist/admin/AdminAudits-C0DPYw0W.js.map +0 -1
- package/dist/admin/AdminUserCreate-DiXi1EWB.js.map +0 -1
- package/dist/admin/AdminUserSettings-BnzRAcqV.js.map +0 -1
- package/dist/admin/AdminUserSettings-CXs-jtRv.js +0 -3
- package/dist/admin/AdminUsers-CYkcUWCg.js.map +0 -1
- package/dist/admin/AdminUsers-DdFXzrEn.js +0 -3
- package/dist/auth/Login-BAFVcX_J.js.map +0 -1
- package/dist/auth/Login-C5PUsp8I.js +0 -4
- package/dist/auth/Register-CZRXEcWy.js.map +0 -1
- package/dist/auth/Register-DMTs5ep_.js +0 -4
- package/dist/auth/ResetPassword-D-mhMtmx.js +0 -3
- package/dist/auth/VerifyEmail-BsrCmncc.js +0 -3
- package/dist/demo/DemoLogin-mtkN6340.js.map +0 -1
- package/dist/demo/DemoRegister-C0MW7anp.js.map +0 -1
- package/src/core/RootRouter.ts +0 -9
|
@@ -5,6 +5,8 @@ import {
|
|
|
5
5
|
type AppShellMainProps,
|
|
6
6
|
type AppShellNavbarProps,
|
|
7
7
|
type AppShellProps,
|
|
8
|
+
Container,
|
|
9
|
+
type ContainerProps,
|
|
8
10
|
Flex,
|
|
9
11
|
} from "@mantine/core";
|
|
10
12
|
import { useEvents, useStore } from "alepha/react";
|
|
@@ -37,7 +39,7 @@ export interface AdminShellProps {
|
|
|
37
39
|
* Enable drag-to-resize for the sidebar.
|
|
38
40
|
* Width and constraints are configured in alephaSidebarAtom.
|
|
39
41
|
*/
|
|
40
|
-
|
|
42
|
+
sidebarResizable?: boolean;
|
|
41
43
|
|
|
42
44
|
noSidebarWhen?: {
|
|
43
45
|
/**
|
|
@@ -45,6 +47,12 @@ export interface AdminShellProps {
|
|
|
45
47
|
*/
|
|
46
48
|
paths?: string[];
|
|
47
49
|
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Wrap AppBar and main content in a Mantine Container.
|
|
53
|
+
* Pass `true` for default Container, or ContainerProps to customize.
|
|
54
|
+
*/
|
|
55
|
+
container?: boolean | ContainerProps;
|
|
48
56
|
}
|
|
49
57
|
|
|
50
58
|
const AdminShell = (props: AdminShellProps) => {
|
|
@@ -79,7 +87,7 @@ const AdminShell = (props: AdminShellProps) => {
|
|
|
79
87
|
|
|
80
88
|
const handleResizeStart = useCallback(
|
|
81
89
|
(e: React.MouseEvent) => {
|
|
82
|
-
if (!props.
|
|
90
|
+
if (!props.sidebarResizable) return;
|
|
83
91
|
e.preventDefault();
|
|
84
92
|
|
|
85
93
|
// If collapsed and hovering, un-collapse first and start from defaultWidth
|
|
@@ -98,7 +106,7 @@ const AdminShell = (props: AdminShellProps) => {
|
|
|
98
106
|
};
|
|
99
107
|
}
|
|
100
108
|
},
|
|
101
|
-
[props.
|
|
109
|
+
[props.sidebarResizable, collapsed, sidebar, setSidebar, defaultWidth],
|
|
102
110
|
);
|
|
103
111
|
|
|
104
112
|
useEffect(() => {
|
|
@@ -218,6 +226,10 @@ const AdminShell = (props: AdminShellProps) => {
|
|
|
218
226
|
{ position: "left" as const, type: "burger" as const },
|
|
219
227
|
];
|
|
220
228
|
|
|
229
|
+
// Forward container to appBarProps if not already set
|
|
230
|
+
const appBarProps = { ...props.appBarProps };
|
|
231
|
+
appBarProps.container ??= props.container;
|
|
232
|
+
|
|
221
233
|
const hasSidebar = showSidebar && props.sidebarProps !== undefined;
|
|
222
234
|
const hasAppBar = hasSidebar || props.appBarProps || props.header;
|
|
223
235
|
|
|
@@ -229,14 +241,13 @@ const AdminShell = (props: AdminShellProps) => {
|
|
|
229
241
|
const isExpandedByHover = collapsed && isHovering;
|
|
230
242
|
const effectiveCollapsed = collapsed && !isHovering;
|
|
231
243
|
const hoverWidth = Math.max(defaultWidth, collapsedWidth);
|
|
244
|
+
// When hovering, keep main content at collapsed width (sidebar overlays)
|
|
232
245
|
const sidebarWidth = hasSidebar
|
|
233
|
-
? effectiveCollapsed
|
|
246
|
+
? effectiveCollapsed || isExpandedByHover
|
|
234
247
|
? collapsedWidth
|
|
235
|
-
:
|
|
236
|
-
? hoverWidth
|
|
237
|
-
: expandedWidth
|
|
248
|
+
: expandedWidth
|
|
238
249
|
: 0;
|
|
239
|
-
const canResize = props.
|
|
250
|
+
const canResize = props.sidebarResizable && !collapsed;
|
|
240
251
|
|
|
241
252
|
return (
|
|
242
253
|
<AppShell
|
|
@@ -247,10 +258,10 @@ const AdminShell = (props: AdminShellProps) => {
|
|
|
247
258
|
navbar={
|
|
248
259
|
hasSidebar
|
|
249
260
|
? {
|
|
250
|
-
width
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
? { base:
|
|
261
|
+
// When hovering, keep collapsed width to avoid pushing content
|
|
262
|
+
width:
|
|
263
|
+
effectiveCollapsed || isExpandedByHover
|
|
264
|
+
? { base: collapsedWidth }
|
|
254
265
|
: { base: expandedWidth },
|
|
255
266
|
breakpoint: "sm",
|
|
256
267
|
collapsed: { mobile: !opened },
|
|
@@ -261,9 +272,7 @@ const AdminShell = (props: AdminShellProps) => {
|
|
|
261
272
|
{...props.appShellProps}
|
|
262
273
|
>
|
|
263
274
|
<AppShell.Header bg={ui.colors.surface} {...props.appShellHeaderProps}>
|
|
264
|
-
{props.header ??
|
|
265
|
-
<AppBar items={defaultAppBarItems} {...props.appBarProps} />
|
|
266
|
-
)}
|
|
275
|
+
{props.header ?? <AppBar items={defaultAppBarItems} {...appBarProps} />}
|
|
267
276
|
</AppShell.Header>
|
|
268
277
|
|
|
269
278
|
{hasSidebar && (
|
|
@@ -271,6 +280,7 @@ const AdminShell = (props: AdminShellProps) => {
|
|
|
271
280
|
bg={ui.colors.surface}
|
|
272
281
|
className="alepha-sidebar-navbar"
|
|
273
282
|
data-resizing={isResizing}
|
|
283
|
+
data-hover-expanded={isExpandedByHover}
|
|
274
284
|
onMouseEnter={handleNavbarMouseEnter}
|
|
275
285
|
onMouseLeave={handleNavbarMouseLeave}
|
|
276
286
|
style={{
|
|
@@ -278,6 +288,12 @@ const AdminShell = (props: AdminShellProps) => {
|
|
|
278
288
|
? `translateX(${collapseEffect.offset}px)`
|
|
279
289
|
: undefined,
|
|
280
290
|
opacity: collapseEffect.opacity,
|
|
291
|
+
// When hovering, expand width visually as overlay
|
|
292
|
+
...(isExpandedByHover && {
|
|
293
|
+
width: hoverWidth,
|
|
294
|
+
zIndex: 200,
|
|
295
|
+
boxShadow: "var(--mantine-shadow-xl)",
|
|
296
|
+
}),
|
|
281
297
|
}}
|
|
282
298
|
{...props.appShellNavbarProps}
|
|
283
299
|
>
|
|
@@ -288,10 +304,10 @@ const AdminShell = (props: AdminShellProps) => {
|
|
|
288
304
|
{(canResize || isExpandedByHover) && (
|
|
289
305
|
<Flex
|
|
290
306
|
pos="absolute"
|
|
291
|
-
right={-
|
|
307
|
+
right={-6}
|
|
292
308
|
top={0}
|
|
293
309
|
bottom={0}
|
|
294
|
-
w={
|
|
310
|
+
w={12}
|
|
295
311
|
style={{
|
|
296
312
|
cursor: "col-resize",
|
|
297
313
|
userSelect: "none",
|
|
@@ -314,7 +330,19 @@ const AdminShell = (props: AdminShellProps) => {
|
|
|
314
330
|
data-resizing={isResizing}
|
|
315
331
|
{...props.appShellMainProps}
|
|
316
332
|
>
|
|
317
|
-
{props.
|
|
333
|
+
{props.container ? (
|
|
334
|
+
<Container
|
|
335
|
+
w={"100%"}
|
|
336
|
+
flex={1}
|
|
337
|
+
display="flex"
|
|
338
|
+
style={{ flexDirection: "column" }}
|
|
339
|
+
{...(typeof props.container === "boolean" ? {} : props.container)}
|
|
340
|
+
>
|
|
341
|
+
{props.children ?? <NestedView />}
|
|
342
|
+
</Container>
|
|
343
|
+
) : (
|
|
344
|
+
(props.children ?? <NestedView />)
|
|
345
|
+
)}
|
|
318
346
|
</AppShell.Main>
|
|
319
347
|
|
|
320
348
|
{props.footer && (
|
|
@@ -1,5 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
Anchor,
|
|
3
|
+
Container,
|
|
4
|
+
type ContainerProps,
|
|
5
|
+
Divider,
|
|
6
|
+
Flex,
|
|
7
|
+
type FlexProps,
|
|
8
|
+
Image,
|
|
9
|
+
Text,
|
|
10
|
+
} from "@mantine/core";
|
|
11
|
+
import { IconArrowLeft } from "@tabler/icons-react";
|
|
12
|
+
import { Link, useRouter } from "alepha/react/router";
|
|
13
|
+
import type { ComponentType, ReactNode } from "react";
|
|
14
|
+
import ActionButton from "../buttons/ActionButton.tsx";
|
|
3
15
|
import BurgerButton from "../buttons/BurgerButton.tsx";
|
|
4
16
|
import DarkModeButton, {
|
|
5
17
|
type DarkModeButtonProps,
|
|
@@ -18,55 +30,146 @@ export type AppBarItem =
|
|
|
18
30
|
| AppBarSearch
|
|
19
31
|
| AppBarLang
|
|
20
32
|
| AppBarSpacer
|
|
21
|
-
| AppBarDivider
|
|
33
|
+
| AppBarDivider
|
|
34
|
+
| AppBarLogo
|
|
35
|
+
| AppBarBack;
|
|
22
36
|
|
|
23
|
-
export interface
|
|
37
|
+
export interface AppBarAbstractItem {
|
|
24
38
|
position: "left" | "center" | "right";
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Visibility control: return true to show, false to hide.
|
|
42
|
+
*/
|
|
43
|
+
can?: () => boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface AppBarElement extends AppBarAbstractItem {
|
|
25
47
|
element: ReactNode;
|
|
26
48
|
}
|
|
27
49
|
|
|
28
|
-
export interface AppBarBurger {
|
|
29
|
-
position: "left" | "center" | "right";
|
|
50
|
+
export interface AppBarBurger extends AppBarAbstractItem {
|
|
30
51
|
type: "burger";
|
|
31
52
|
}
|
|
32
53
|
|
|
33
|
-
export interface AppBarDark {
|
|
34
|
-
position: "left" | "center" | "right";
|
|
54
|
+
export interface AppBarDark extends AppBarAbstractItem {
|
|
35
55
|
type: "dark";
|
|
36
56
|
props?: DarkModeButtonProps;
|
|
37
57
|
}
|
|
38
58
|
|
|
39
|
-
export interface AppBarSearch {
|
|
40
|
-
position: "left" | "center" | "right";
|
|
59
|
+
export interface AppBarSearch extends AppBarAbstractItem {
|
|
41
60
|
type: "search";
|
|
42
61
|
props?: OmnibarButtonProps;
|
|
43
62
|
}
|
|
44
63
|
|
|
45
|
-
export interface AppBarLang {
|
|
46
|
-
position: "left" | "center" | "right";
|
|
64
|
+
export interface AppBarLang extends AppBarAbstractItem {
|
|
47
65
|
type: "lang";
|
|
48
66
|
props?: LanguageButtonProps;
|
|
49
67
|
}
|
|
50
68
|
|
|
51
|
-
export interface AppBarSpacer {
|
|
52
|
-
position: "left" | "center" | "right";
|
|
69
|
+
export interface AppBarSpacer extends AppBarAbstractItem {
|
|
53
70
|
type: "spacer";
|
|
54
71
|
}
|
|
55
72
|
|
|
56
|
-
export interface AppBarDivider {
|
|
57
|
-
position: "left" | "center" | "right";
|
|
73
|
+
export interface AppBarDivider extends AppBarAbstractItem {
|
|
58
74
|
type: "divider";
|
|
59
75
|
}
|
|
60
76
|
|
|
77
|
+
export interface AppBarLogo extends AppBarAbstractItem {
|
|
78
|
+
type: "logo";
|
|
79
|
+
props?: {
|
|
80
|
+
/**
|
|
81
|
+
* Logo image source URL.
|
|
82
|
+
*/
|
|
83
|
+
src?: string;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Logo text (used if no src provided).
|
|
87
|
+
*/
|
|
88
|
+
text?: string;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Icon component (used if no src or text provided).
|
|
92
|
+
*/
|
|
93
|
+
icon?: ReactNode | ComponentType;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Link href when logo is clicked.
|
|
97
|
+
*/
|
|
98
|
+
href?: string;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Logo image height in pixels.
|
|
102
|
+
* @default 32
|
|
103
|
+
*/
|
|
104
|
+
height?: number;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Logo image width in pixels.
|
|
108
|
+
*/
|
|
109
|
+
width?: number;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Font weight for text logo.
|
|
113
|
+
* @default 700
|
|
114
|
+
*/
|
|
115
|
+
fontWeight?: number;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Font size for text logo.
|
|
119
|
+
* @default "lg"
|
|
120
|
+
*/
|
|
121
|
+
fontSize?: string;
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export interface AppBarBack extends AppBarAbstractItem {
|
|
126
|
+
type: "back";
|
|
127
|
+
props?: {
|
|
128
|
+
/**
|
|
129
|
+
* Custom label for back button.
|
|
130
|
+
* @default "Back"
|
|
131
|
+
*/
|
|
132
|
+
label?: string;
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Show only icon without label.
|
|
136
|
+
* @default true
|
|
137
|
+
*/
|
|
138
|
+
iconOnly?: boolean;
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Custom href to navigate to instead of history back.
|
|
142
|
+
*/
|
|
143
|
+
href?: string;
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Custom icon component.
|
|
147
|
+
*/
|
|
148
|
+
icon?: ReactNode | ComponentType;
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
61
152
|
export interface AppBarProps {
|
|
62
153
|
flexProps?: FlexProps;
|
|
63
154
|
items?: AppBarItem[];
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Wrap the AppBar content in a Mantine Container.
|
|
158
|
+
* Pass `true` for default Container, or ContainerProps to customize.
|
|
159
|
+
*/
|
|
160
|
+
container?: boolean | ContainerProps;
|
|
64
161
|
}
|
|
65
162
|
|
|
66
163
|
const AppBar = (props: AppBarProps) => {
|
|
67
164
|
const { items = [] } = props;
|
|
165
|
+
const router = useRouter();
|
|
68
166
|
|
|
69
167
|
const renderItem = (item: AppBarItem, index: number) => {
|
|
168
|
+
// Check visibility control
|
|
169
|
+
if (item.can && !item.can()) {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
|
|
70
173
|
if ("type" in item) {
|
|
71
174
|
if (item.type === "burger") {
|
|
72
175
|
return <BurgerButton key={index} />;
|
|
@@ -86,6 +189,12 @@ const AppBar = (props: AppBarProps) => {
|
|
|
86
189
|
if (item.type === "divider") {
|
|
87
190
|
return <Divider key={index} orientation="vertical" />;
|
|
88
191
|
}
|
|
192
|
+
if (item.type === "logo") {
|
|
193
|
+
return renderLogo(item, index);
|
|
194
|
+
}
|
|
195
|
+
if (item.type === "back") {
|
|
196
|
+
return renderBack(item, index);
|
|
197
|
+
}
|
|
89
198
|
}
|
|
90
199
|
if ("element" in item) {
|
|
91
200
|
return item.element;
|
|
@@ -93,15 +202,111 @@ const AppBar = (props: AppBarProps) => {
|
|
|
93
202
|
return null;
|
|
94
203
|
};
|
|
95
204
|
|
|
205
|
+
const renderLogo = (item: AppBarLogo, index: number) => {
|
|
206
|
+
const {
|
|
207
|
+
src,
|
|
208
|
+
text,
|
|
209
|
+
icon,
|
|
210
|
+
href,
|
|
211
|
+
height = 32,
|
|
212
|
+
width,
|
|
213
|
+
fontWeight = 700,
|
|
214
|
+
fontSize = "lg",
|
|
215
|
+
} = item.props ?? {};
|
|
216
|
+
|
|
217
|
+
const logoContent = src ? (
|
|
218
|
+
<Image src={src} h={height} w={width} fit="contain" />
|
|
219
|
+
) : icon ? (
|
|
220
|
+
typeof icon === "function" ? (
|
|
221
|
+
(() => {
|
|
222
|
+
const IconComponent = icon;
|
|
223
|
+
return <IconComponent />;
|
|
224
|
+
})()
|
|
225
|
+
) : (
|
|
226
|
+
icon
|
|
227
|
+
)
|
|
228
|
+
) : text ? (
|
|
229
|
+
<Text fw={fontWeight} size={fontSize}>
|
|
230
|
+
{text}
|
|
231
|
+
</Text>
|
|
232
|
+
) : null;
|
|
233
|
+
|
|
234
|
+
if (href) {
|
|
235
|
+
return (
|
|
236
|
+
<Anchor
|
|
237
|
+
component={Link}
|
|
238
|
+
key={index}
|
|
239
|
+
href={href}
|
|
240
|
+
underline="never"
|
|
241
|
+
c="inherit"
|
|
242
|
+
>
|
|
243
|
+
{logoContent}
|
|
244
|
+
</Anchor>
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return <Flex key={index}>{logoContent}</Flex>;
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
const renderBack = (item: AppBarBack, index: number) => {
|
|
252
|
+
const { label = "Back", iconOnly = true, href, icon } = item.props ?? {};
|
|
253
|
+
|
|
254
|
+
const renderIcon = () => {
|
|
255
|
+
if (!icon) {
|
|
256
|
+
return <IconArrowLeft size={18} />;
|
|
257
|
+
}
|
|
258
|
+
if (typeof icon === "function") {
|
|
259
|
+
const IconComponent = icon as ComponentType<{ size?: number }>;
|
|
260
|
+
return <IconComponent size={18} />;
|
|
261
|
+
}
|
|
262
|
+
return icon;
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const iconElement = renderIcon();
|
|
266
|
+
|
|
267
|
+
const handleClick = () => {
|
|
268
|
+
if (href) {
|
|
269
|
+
router.push(href);
|
|
270
|
+
} else {
|
|
271
|
+
router.back();
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
if (iconOnly) {
|
|
276
|
+
return (
|
|
277
|
+
<ActionButton
|
|
278
|
+
key={index}
|
|
279
|
+
icon={iconElement}
|
|
280
|
+
variant="subtle"
|
|
281
|
+
color="gray"
|
|
282
|
+
onClick={handleClick}
|
|
283
|
+
tooltip={{ label, position: "bottom" }}
|
|
284
|
+
/>
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return (
|
|
289
|
+
<ActionButton
|
|
290
|
+
key={index}
|
|
291
|
+
leftSection={iconElement}
|
|
292
|
+
variant="subtle"
|
|
293
|
+
color="gray"
|
|
294
|
+
onClick={handleClick}
|
|
295
|
+
>
|
|
296
|
+
{label}
|
|
297
|
+
</ActionButton>
|
|
298
|
+
);
|
|
299
|
+
};
|
|
300
|
+
|
|
96
301
|
const leftItems = items.filter((item) => item.position === "left");
|
|
97
302
|
const centerItems = items.filter((item) => item.position === "center");
|
|
98
303
|
const rightItems = items.filter((item) => item.position === "right");
|
|
99
304
|
|
|
100
|
-
|
|
305
|
+
const content = (
|
|
101
306
|
<Flex
|
|
102
307
|
h="100%"
|
|
103
308
|
align="center"
|
|
104
|
-
px="md"
|
|
309
|
+
px={props.container ? 0 : "md"}
|
|
105
310
|
justify="space-between"
|
|
106
311
|
{...props.flexProps}
|
|
107
312
|
>
|
|
@@ -128,6 +333,18 @@ const AppBar = (props: AppBarProps) => {
|
|
|
128
333
|
</Flex>
|
|
129
334
|
</Flex>
|
|
130
335
|
);
|
|
336
|
+
|
|
337
|
+
if (props.container) {
|
|
338
|
+
const containerProps =
|
|
339
|
+
typeof props.container === "boolean" ? {} : props.container;
|
|
340
|
+
return (
|
|
341
|
+
<Container h="100%" {...containerProps}>
|
|
342
|
+
{content}
|
|
343
|
+
</Container>
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return content;
|
|
131
348
|
};
|
|
132
349
|
|
|
133
350
|
export default AppBar;
|
|
@@ -35,9 +35,9 @@ const Omnibar = (props: OmnibarProps) => {
|
|
|
35
35
|
description: page.description,
|
|
36
36
|
onClick: () => {
|
|
37
37
|
if (page.staticName) {
|
|
38
|
-
return router.
|
|
38
|
+
return router.push(page.staticName, { params: page.params });
|
|
39
39
|
}
|
|
40
|
-
return router.
|
|
40
|
+
return router.push(page.name);
|
|
41
41
|
},
|
|
42
42
|
leftSection: renderIcon(page.icon),
|
|
43
43
|
})),
|
|
@@ -265,6 +265,7 @@ export const SidebarItem = (props: SidebarItemProps) => {
|
|
|
265
265
|
props.theme.button?.size ??
|
|
266
266
|
(level === 0 ? "sm" : "xs")
|
|
267
267
|
}
|
|
268
|
+
tooltip={item.description}
|
|
268
269
|
c={"var(--mantine-color-text)"}
|
|
269
270
|
color={"gray"}
|
|
270
271
|
variant={"subtle"}
|
|
@@ -276,11 +277,6 @@ export const SidebarItem = (props: SidebarItemProps) => {
|
|
|
276
277
|
{renderIcon(item.icon)}
|
|
277
278
|
<Flex direction={"column"}>
|
|
278
279
|
<Flex>{item.label}</Flex>
|
|
279
|
-
{item.description && (
|
|
280
|
-
<Text size={"xs"} c={"dimmed"}>
|
|
281
|
-
{item.description}
|
|
282
|
-
</Text>
|
|
283
|
-
)}
|
|
284
280
|
</Flex>
|
|
285
281
|
</Flex>
|
|
286
282
|
}
|
package/src/core/index.ts
CHANGED
|
@@ -5,11 +5,15 @@ import { AlephaReactI18n } from "alepha/react/i18n";
|
|
|
5
5
|
import type { ComponentType, ReactNode } from "react";
|
|
6
6
|
import { alephaSidebarAtom } from "./atoms/alephaSidebarAtom.ts";
|
|
7
7
|
import { alephaThemeAtom } from "./atoms/alephaThemeAtom.ts";
|
|
8
|
+
import {
|
|
9
|
+
type AlephaThemeListAtom,
|
|
10
|
+
alephaThemeListAtom,
|
|
11
|
+
} from "./atoms/alephaThemeListAtom.ts";
|
|
8
12
|
import type { ControlProps } from "./components/form/Control.tsx";
|
|
9
13
|
import { ThemeProvider } from "./providers/ThemeProvider.ts";
|
|
10
|
-
import { RootRouter } from "./RootRouter.ts";
|
|
11
14
|
import { DialogService } from "./services/DialogService.tsx";
|
|
12
15
|
import { ToastService } from "./services/ToastService.tsx";
|
|
16
|
+
import { UiRouter } from "./UiRouter.ts";
|
|
13
17
|
|
|
14
18
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
15
19
|
|
|
@@ -98,7 +102,6 @@ export * from "./constants/ui.ts";
|
|
|
98
102
|
export { useDialog } from "./hooks/useDialog.ts";
|
|
99
103
|
export { useToast } from "./hooks/useToast.ts";
|
|
100
104
|
export * from "./providers/ThemeProvider.ts";
|
|
101
|
-
export * from "./RootRouter.ts";
|
|
102
105
|
export type {
|
|
103
106
|
AlertDialogOptions,
|
|
104
107
|
AlertDialogProps,
|
|
@@ -110,6 +113,7 @@ export type {
|
|
|
110
113
|
} from "./services/DialogService.tsx";
|
|
111
114
|
export { DialogService } from "./services/DialogService.tsx";
|
|
112
115
|
export { ToastService } from "./services/ToastService.tsx";
|
|
116
|
+
export * from "./UiRouter.ts";
|
|
113
117
|
export * from "./utils/extractSchemaFields.ts";
|
|
114
118
|
export * from "./utils/icons.tsx";
|
|
115
119
|
export * from "./utils/string.ts";
|
|
@@ -180,7 +184,7 @@ declare module "alepha/react/router" {
|
|
|
180
184
|
*/
|
|
181
185
|
export const AlephaUI = $module({
|
|
182
186
|
name: "alepha.ui",
|
|
183
|
-
services: [DialogService, ToastService, ThemeProvider,
|
|
187
|
+
services: [DialogService, ToastService, ThemeProvider, UiRouter],
|
|
184
188
|
register: (alepha) => {
|
|
185
189
|
alepha.with(AlephaReactI18n);
|
|
186
190
|
alepha.with(AlephaReactHead);
|
|
@@ -192,30 +196,12 @@ export const AlephaUI = $module({
|
|
|
192
196
|
});
|
|
193
197
|
|
|
194
198
|
/**
|
|
195
|
-
*
|
|
199
|
+
* Convenience function to configure and inject the UiRouter.
|
|
196
200
|
*/
|
|
197
|
-
export const $ui = (
|
|
198
|
-
opts: {
|
|
199
|
-
// TODO:
|
|
200
|
-
// theme?: ThemeOptions;
|
|
201
|
-
// root?: string = "/";
|
|
202
|
-
} = {},
|
|
203
|
-
) => {
|
|
201
|
+
export const $ui = (options: { themes?: AlephaThemeListAtom } = {}) => {
|
|
204
202
|
const { alepha } = $context();
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
// auth = $ui({ root: "/auth", theme: authTheme });
|
|
210
|
-
// etc...
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* If multi ui, should we have N themes ? or one $atom theme but with change based on current ui app ?
|
|
214
|
-
*
|
|
215
|
-
* App (theme=T1) -> Admin (theme=T2) ?
|
|
216
|
-
*
|
|
217
|
-
* > It can be done with onLeave()/onEnter() of the RootRouter to set the theme atom.
|
|
218
|
-
*/
|
|
219
|
-
|
|
220
|
-
return alepha.inject(RootRouter); // Inject as singleton ?
|
|
203
|
+
if (options.themes) {
|
|
204
|
+
alepha.store.set(alephaThemeListAtom, options.themes);
|
|
205
|
+
}
|
|
206
|
+
return alepha.inject(UiRouter); // Inject as singleton ?
|
|
221
207
|
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"AdminAudits-C0DPYw0W.js","names":[],"sources":["../../src/admin/components/audits/AdminAudits.tsx"],"sourcesContent":["import { DataTable, Flex, Text } from \"@alepha/ui\";\nimport { Badge, Group, Stack, Tooltip } from \"@mantine/core\";\nimport {\n IconAlertTriangle,\n IconCheck,\n IconInfoCircle,\n IconUser,\n IconX,\n} from \"@tabler/icons-react\";\nimport { type Page, t } from \"alepha\";\nimport type { AdminAuditController, AuditEntity } from \"alepha/api/audits\";\nimport { useClient } from \"alepha/react\";\nimport { useI18n } from \"alepha/react/i18n\";\nimport { useRouter } from \"alepha/react/router\";\nimport type { AdminRouter } from \"../../AdminRouter.ts\";\n\nexport interface AdminAuditsProps {\n userRealmName?: string;\n}\n\nconst getSeverityColor = (severity: string) => {\n switch (severity) {\n case \"critical\":\n return \"red\";\n case \"warning\":\n return \"yellow\";\n default:\n return \"blue\";\n }\n};\n\nconst getSeverityIcon = (severity: string) => {\n switch (severity) {\n case \"critical\":\n return <IconAlertTriangle size={12} />;\n case \"warning\":\n return <IconAlertTriangle size={12} />;\n default:\n return <IconInfoCircle size={12} />;\n }\n};\n\nconst getTypeColor = (type: string) => {\n switch (type) {\n case \"auth\":\n return \"blue\";\n case \"user\":\n return \"grape\";\n case \"security\":\n return \"red\";\n case \"system\":\n return \"orange\";\n case \"access\":\n return \"cyan\";\n case \"payment\":\n return \"green\";\n case \"order\":\n return \"teal\";\n default:\n return \"gray\";\n }\n};\n\nconst AdminAudits = (props: AdminAuditsProps) => {\n const client = useClient<AdminAuditController>();\n const router = useRouter<AdminRouter>();\n const { l } = useI18n();\n\n const filters = t.object({\n type: t.optional(t.text()),\n action: t.optional(t.text()),\n severity: t.optional(t.enum([\"info\", \"warning\", \"critical\"])),\n success: t.optional(t.boolean()),\n resourceType: t.optional(t.text()),\n search: t.optional(t.text()),\n from: t.optional(t.datetime()),\n to: t.optional(t.datetime()),\n });\n\n return (\n <Flex flex={1} direction=\"column\">\n <DataTable<AuditEntity, typeof filters>\n submitOnInit\n defaultSize={20}\n typeFormProps={{\n skipSubmitButton: true,\n columns: 4,\n }}\n tableProps={{\n horizontalSpacing: \"xs\",\n verticalSpacing: \"xs\",\n striped: false,\n highlightOnHover: true,\n }}\n filters={filters}\n tableTrProps={(item) => ({\n style: {\n cursor: item.userId ? \"pointer\" : \"default\",\n opacity: item.success ? 1 : 0.85,\n },\n onClick: () => {\n if (item.userId) {\n router.go(\"adminUserDetails\", {\n params: { userId: item.userId },\n });\n }\n },\n })}\n items={async (query) => {\n const response = await client.findAudits({\n query: {\n ...query,\n userRealm: props.userRealmName,\n },\n });\n return response as Page<AuditEntity>;\n }}\n columns={{\n type: {\n label: \"Type\",\n fit: true,\n value: (item) => (\n <Badge size=\"sm\" variant=\"light\" color={getTypeColor(item.type)}>\n {item.type}\n </Badge>\n ),\n },\n action: {\n label: \"Action\",\n fit: true,\n value: (item) => (\n <Badge size=\"sm\" variant=\"outline\">\n {item.action}\n </Badge>\n ),\n },\n severity: {\n label: \"Severity\",\n fit: true,\n value: (item) => (\n <Badge\n size=\"xs\"\n variant=\"light\"\n color={getSeverityColor(item.severity)}\n leftSection={getSeverityIcon(item.severity)}\n >\n {item.severity}\n </Badge>\n ),\n },\n user: {\n label: \"User\",\n fit: true,\n value: (item) =>\n item.userId ? (\n <Tooltip\n label={\n <Stack gap={2}>\n <Text size=\"xs\">{item.userEmail || \"No email\"}</Text>\n <Text size=\"xs\" c=\"dimmed\">\n {item.userRealm || \"default\"}\n </Text>\n </Stack>\n }\n >\n <Group gap={4}>\n <IconUser size={12} />\n <Text size=\"xs\" lineClamp={1} maw={100}>\n {item.userEmail?.split(\"@\")[0] || item.userId.slice(0, 8)}\n </Text>\n </Group>\n </Tooltip>\n ) : (\n <Text size=\"xs\" c=\"dimmed\">\n System\n </Text>\n ),\n },\n description: {\n label: \"Description\",\n value: (item) => (\n <Text size=\"sm\" lineClamp={1}>\n {item.description || \"-\"}\n </Text>\n ),\n },\n resource: {\n label: \"Resource\",\n fit: true,\n value: (item) =>\n item.resourceType ? (\n <Tooltip label={`${item.resourceType}: ${item.resourceId}`}>\n <Badge size=\"xs\" variant=\"dot\" color=\"gray\">\n {item.resourceType}\n </Badge>\n </Tooltip>\n ) : (\n <Text size=\"xs\" c=\"dimmed\">\n -\n </Text>\n ),\n },\n success: {\n label: \"Status\",\n fit: true,\n value: (item) =>\n item.success ? (\n <IconCheck size={14} color=\"var(--mantine-color-green-6)\" />\n ) : (\n <Tooltip label={item.errorMessage || \"Failed\"}>\n <IconX size={14} color=\"var(--mantine-color-red-6)\" />\n </Tooltip>\n ),\n },\n ipAddress: {\n label: \"IP\",\n fit: true,\n value: (item) => (\n <Text size=\"xs\" c=\"dimmed\" ff=\"monospace\">\n {item.ipAddress || \"-\"}\n </Text>\n ),\n },\n createdAt: {\n label: \"Time\",\n fit: true,\n value: (item) => (\n <Tooltip label={l(item.createdAt, { date: \"medium\" })}>\n <Text size=\"xs\" c=\"dimmed\">\n {l(item.createdAt, { date: \"fromNow\" })}\n </Text>\n </Tooltip>\n ),\n },\n }}\n />\n </Flex>\n );\n};\n\nexport default AdminAudits;\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"AdminUserCreate-DiXi1EWB.js","names":["Text"],"sources":["../../src/admin/components/users/AdminUserCreate.tsx"],"sourcesContent":["import { ActionButton, Control, Flex } from \"@alepha/ui\";\nimport { Card, Stack, Text } from \"@mantine/core\";\nimport { t } from \"alepha\";\nimport type { AdminUserController } from \"alepha/api/users\";\nimport { useClient } from \"alepha/react\";\nimport { useForm } from \"alepha/react/form\";\nimport { useRouter } from \"alepha/react/router\";\nimport type { AdminRouter } from \"../../AdminRouter.ts\";\n\nexport interface AdminUserCreateProps {\n userRealmName?: string;\n}\n\nconst AdminUserCreate = (props: AdminUserCreateProps) => {\n const client = useClient<AdminUserController>();\n const router = useRouter<AdminRouter>();\n\n const form = useForm({\n schema: t.object({\n username: t.optional(\n t.shortText({\n minLength: 3,\n maxLength: 50,\n pattern: \"^[a-zA-Z0-9._-]+$\",\n }),\n ),\n email: t.optional(t.email()),\n phoneNumber: t.optional(t.e164()),\n firstName: t.optional(t.string()),\n lastName: t.optional(t.string()),\n roles: t.optional(t.array(t.string())),\n enabled: t.optional(t.boolean()),\n password: t.optional(t.string({ minLength: 8 })),\n }),\n handler: async (data) => {\n const user = await client.createUser({\n query: {\n userRealmName: props.userRealmName,\n },\n body: {\n ...data,\n enabled: data.enabled ?? true,\n },\n });\n\n await router.go(\"adminUserDetails\", {\n params: { userId: user.id },\n });\n },\n });\n\n return (\n <Flex flex={1} p=\"md\">\n <Card withBorder p=\"lg\" maw={600} w=\"100%\">\n <form {...form.props}>\n <Stack gap=\"md\">\n <Text size=\"lg\" fw={500}>\n Create New User\n </Text>\n\n <Control title=\"Username\" input={form.input.username} />\n\n <Control title=\"Email\" input={form.input.email} />\n\n <Control title=\"Phone Number\" input={form.input.phoneNumber} />\n\n <Control title=\"First Name\" input={form.input.firstName} />\n\n <Control title=\"Last Name\" input={form.input.lastName} />\n\n <Control title=\"Password\" input={form.input.password} password />\n\n <Control title=\"Roles\" input={form.input.roles} />\n\n <Control title=\"Enabled\" input={form.input.enabled} />\n\n <ActionButton form={form}>Create User</ActionButton>\n </Stack>\n </form>\n </Card>\n </Flex>\n );\n};\n\nexport default AdminUserCreate;\n"],"mappings":";;;;;;;;;AAaA,MAAM,mBAAmB,UAAgC;CACvD,MAAM,SAAS,WAAgC;CAC/C,MAAM,SAAS,WAAwB;CAEvC,MAAM,OAAO,QAAQ;EACnB,QAAQ,EAAE,OAAO;GACf,UAAU,EAAE,SACV,EAAE,UAAU;IACV,WAAW;IACX,WAAW;IACX,SAAS;IACV,CAAC,CACH;GACD,OAAO,EAAE,SAAS,EAAE,OAAO,CAAC;GAC5B,aAAa,EAAE,SAAS,EAAE,MAAM,CAAC;GACjC,WAAW,EAAE,SAAS,EAAE,QAAQ,CAAC;GACjC,UAAU,EAAE,SAAS,EAAE,QAAQ,CAAC;GAChC,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;GACtC,SAAS,EAAE,SAAS,EAAE,SAAS,CAAC;GAChC,UAAU,EAAE,SAAS,EAAE,OAAO,EAAE,WAAW,GAAG,CAAC,CAAC;GACjD,CAAC;EACF,SAAS,OAAO,SAAS;GACvB,MAAM,OAAO,MAAM,OAAO,WAAW;IACnC,OAAO,EACL,eAAe,MAAM,eACtB;IACD,MAAM;KACJ,GAAG;KACH,SAAS,KAAK,WAAW;KAC1B;IACF,CAAC;AAEF,SAAM,OAAO,GAAG,oBAAoB,EAClC,QAAQ,EAAE,QAAQ,KAAK,IAAI,EAC5B,CAAC;;EAEL,CAAC;AAEF,QACE,oBAAC;EAAK,MAAM;EAAG,GAAE;YACf,oBAAC;GAAK;GAAW,GAAE;GAAK,KAAK;GAAK,GAAE;aAClC,oBAAC;IAAK,GAAI,KAAK;cACb,qBAAC;KAAM,KAAI;;MACT,oBAACA;OAAK,MAAK;OAAK,IAAI;iBAAK;QAElB;MAEP,oBAAC;OAAQ,OAAM;OAAW,OAAO,KAAK,MAAM;QAAY;MAExD,oBAAC;OAAQ,OAAM;OAAQ,OAAO,KAAK,MAAM;QAAS;MAElD,oBAAC;OAAQ,OAAM;OAAe,OAAO,KAAK,MAAM;QAAe;MAE/D,oBAAC;OAAQ,OAAM;OAAa,OAAO,KAAK,MAAM;QAAa;MAE3D,oBAAC;OAAQ,OAAM;OAAY,OAAO,KAAK,MAAM;QAAY;MAEzD,oBAAC;OAAQ,OAAM;OAAW,OAAO,KAAK,MAAM;OAAU;QAAW;MAEjE,oBAAC;OAAQ,OAAM;OAAQ,OAAO,KAAK,MAAM;QAAS;MAElD,oBAAC;OAAQ,OAAM;OAAU,OAAO,KAAK,MAAM;QAAW;MAEtD,oBAAC;OAAmB;iBAAM;QAA0B;;MAC9C;KACH;IACF;GACF;;AAIX,8BAAe"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"AdminUserSettings-BnzRAcqV.js","names":[],"sources":["../../src/admin/components/users/AdminUserSettings.tsx"],"sourcesContent":["import { ActionButton, Flex, Text } from \"@alepha/ui\";\nimport { Alert, Card, Group, Loader, Stack } from \"@mantine/core\";\nimport {\n IconAlertCircle,\n IconCheck,\n IconMail,\n IconTrash,\n} from \"@tabler/icons-react\";\nimport type {\n AdminUserController,\n UserController,\n UserEntity,\n} from \"alepha/api/users\";\nimport { useClient } from \"alepha/react\";\nimport { useRouter, useRouterState } from \"alepha/react/router\";\nimport { useEffect, useState } from \"react\";\nimport type { AdminRouter } from \"../../AdminRouter.ts\";\n\nexport interface AdminUserSettingsProps {\n userRealmName?: string;\n}\n\nconst AdminUserSettings = (props: AdminUserSettingsProps) => {\n const router = useRouter<AdminRouter>();\n const state = useRouterState();\n const adminClient = useClient<AdminUserController>();\n const userClient = useClient<UserController>();\n const userId = state.params.userId as string;\n\n const [user, setUser] = useState<UserEntity | null>(null);\n const [loading, setLoading] = useState(true);\n const [deleteLoading, setDeleteLoading] = useState(false);\n const [verifyLoading, setVerifyLoading] = useState(false);\n const [verifySuccess, setVerifySuccess] = useState(false);\n\n useEffect(() => {\n const loadUser = async () => {\n try {\n const data = await adminClient.getUser({\n params: { id: userId },\n query: { userRealmName: props.userRealmName },\n });\n setUser(data);\n } finally {\n setLoading(false);\n }\n };\n\n loadUser();\n }, [userId]);\n\n const handleDelete = async () => {\n if (!confirm(\"Are you sure you want to delete this user?\")) {\n return;\n }\n\n setDeleteLoading(true);\n try {\n await adminClient.deleteUser({\n params: { id: userId },\n query: { userRealmName: props.userRealmName },\n });\n await router.go(\"adminUsers\");\n } finally {\n setDeleteLoading(false);\n }\n };\n\n const handleTriggerEmailVerification = async () => {\n if (!user?.email) return;\n\n setVerifyLoading(true);\n setVerifySuccess(false);\n try {\n await userClient.requestEmailVerification({\n query: {\n userRealmName: props.userRealmName,\n method: \"link\",\n verifyUrl: `${window.location.origin}/auth/verify-email`,\n },\n body: { email: user.email },\n });\n setVerifySuccess(true);\n } finally {\n setVerifyLoading(false);\n }\n };\n\n if (loading) {\n return (\n <Flex flex={1} justify=\"center\" align=\"center\">\n <Loader />\n </Flex>\n );\n }\n\n if (!user) {\n return (\n <Flex flex={1} justify=\"center\" align=\"center\">\n <Text c=\"dimmed\">User not found</Text>\n </Flex>\n );\n }\n\n return (\n <Flex flex={1} direction=\"column\" gap=\"md\">\n {user.email && !user.emailVerified && (\n <Card withBorder p=\"lg\">\n <Stack gap=\"md\">\n <Text size=\"lg\" fw={500}>\n Email Verification\n </Text>\n\n <Alert variant=\"light\" color=\"yellow\" icon={<IconMail />}>\n <Text size=\"sm\">\n This user's email ({user.email}) is not verified. You can send a\n verification link to the user.\n </Text>\n </Alert>\n\n {verifySuccess && (\n <Alert variant=\"light\" color=\"green\" icon={<IconCheck />}>\n <Text size=\"sm\">\n Verification link sent successfully to {user.email}.\n </Text>\n </Alert>\n )}\n\n <Group>\n <ActionButton\n leftSection={<IconMail size={16} />}\n loading={verifyLoading}\n onClick={handleTriggerEmailVerification}\n >\n Send Verification Link\n </ActionButton>\n </Group>\n </Stack>\n </Card>\n )}\n\n <Card withBorder p=\"lg\">\n <Stack gap=\"md\">\n <Text size=\"lg\" fw={500} c=\"red\">\n Danger Zone\n </Text>\n\n <Alert variant=\"light\" color=\"red\" icon={<IconAlertCircle />}>\n <Text size=\"sm\">\n Deleting this user will permanently remove their account and all\n associated data. This action cannot be undone.\n </Text>\n </Alert>\n\n <Group>\n <ActionButton\n color=\"red\"\n leftSection={<IconTrash size={16} />}\n loading={deleteLoading}\n onClick={handleDelete}\n >\n Delete User\n </ActionButton>\n </Group>\n </Stack>\n </Card>\n </Flex>\n );\n};\n\nexport default AdminUserSettings;\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"AdminUsers-CYkcUWCg.js","names":["Flex"],"sources":["../../src/admin/components/users/AdminUsers.tsx"],"sourcesContent":["import { DataTable, Text } from \"@alepha/ui\";\nimport { Badge, Flex, Group } from \"@mantine/core\";\nimport { IconCheck, IconUsersPlus, IconX } from \"@tabler/icons-react\";\nimport { type Page, t } from \"alepha\";\nimport {\n type AdminUserController,\n type UserEntity,\n users,\n} from \"alepha/api/users\";\nimport { useClient } from \"alepha/react\";\nimport { useI18n } from \"alepha/react/i18n\";\nimport { useRouter } from \"alepha/react/router\";\nimport type { AdminRouter } from \"../../AdminRouter.ts\";\n\nexport interface AdminUsersProps {\n userRealmName?: string;\n}\n\nconst AdminUsers = (props: AdminUsersProps) => {\n const client = useClient<AdminUserController>();\n const router = useRouter<AdminRouter>();\n const { l } = useI18n();\n\n const filters = t.object({\n query: t.optional(\n t.string({\n $control: {\n query: t.omit(users.schema, [\"id\", \"version\"]),\n },\n }),\n ),\n });\n\n return (\n <Flex flex={1} direction=\"column\">\n <DataTable<UserEntity, typeof filters>\n submitOnInit\n actions={[\n {\n icon: IconUsersPlus,\n href: router.path(\"adminUserCreate\"),\n label: \"Create User\",\n },\n ]}\n defaultSize={10}\n typeFormProps={{\n skipSubmitButton: true,\n columns: 3,\n }}\n tableProps={{\n horizontalSpacing: \"xs\",\n verticalSpacing: \"xs\",\n striped: false,\n highlightOnHover: true,\n }}\n onFilterChange={(key, value, form) => {\n if (key === \"query\") {\n return form.submit();\n }\n }}\n filters={filters}\n tableTrProps={(item) => {\n const baseProps: Record<string, any> = {\n style: { cursor: \"pointer\" },\n onClick: () =>\n router.go(\"adminUserDetails\", {\n params: { userId: item.id },\n }),\n };\n\n if (!item.enabled) {\n baseProps.opacity = 0.5;\n }\n\n return baseProps;\n }}\n items={async (filters) => {\n const response = await client.findUsers({\n query: {\n ...filters,\n userRealmName: props.userRealmName,\n },\n });\n\n return response as Page<UserEntity>;\n }}\n columns={{\n username: {\n label: \"Username\",\n value: (item) => (\n <Text size=\"sm\" fw={500}>\n {item.username || \"-\"}\n </Text>\n ),\n },\n email: {\n label: \"Email\",\n value: (item) => (\n <Group gap=\"xs\">\n <Text size=\"sm\">{item.email || \"-\"}</Text>\n {item.email && (\n <Badge\n size=\"xs\"\n variant=\"light\"\n color={item.emailVerified ? \"green\" : \"gray\"}\n leftSection={\n item.emailVerified ? (\n <IconCheck size={10} />\n ) : (\n <IconX size={10} />\n )\n }\n >\n {item.emailVerified ? \"Verified\" : \"Unverified\"}\n </Badge>\n )}\n </Group>\n ),\n },\n roles: {\n label: \"Roles\",\n value: (item) => (\n <Group gap={4}>\n {item.roles.map((role: string) => (\n <Badge key={role} size=\"xs\" variant=\"outline\">\n {role}\n </Badge>\n ))}\n </Group>\n ),\n },\n enabled: {\n label: \"Status\",\n fit: true,\n value: (item) => (\n <Badge\n size=\"sm\"\n variant=\"light\"\n color={item.enabled ? \"green\" : \"red\"}\n >\n {item.enabled ? \"Active\" : \"Disabled\"}\n </Badge>\n ),\n },\n createdAt: {\n label: \"Created\",\n fit: true,\n value: (item) => (\n <Text size=\"xs\" c=\"dimmed\">\n {l(item.createdAt, { date: \"fromNow\" })}\n </Text>\n ),\n },\n }}\n />\n </Flex>\n );\n};\n\nexport default AdminUsers;\n"],"mappings":";;;;;;;;;;;AAkBA,MAAM,cAAc,UAA2B;CAC7C,MAAM,SAAS,WAAgC;CAC/C,MAAM,SAAS,WAAwB;CACvC,MAAM,EAAE,MAAM,SAAS;CAEvB,MAAM,UAAU,EAAE,OAAO,EACvB,OAAO,EAAE,SACP,EAAE,OAAO,EACP,UAAU,EACR,OAAO,EAAE,KAAK,MAAM,QAAQ,CAAC,MAAM,UAAU,CAAC,EAC/C,EACF,CAAC,CACH,EACF,CAAC;AAEF,QACE,oBAACA;EAAK,MAAM;EAAG,WAAU;YACvB,oBAAC;GACC;GACA,SAAS,CACP;IACE,MAAM;IACN,MAAM,OAAO,KAAK,kBAAkB;IACpC,OAAO;IACR,CACF;GACD,aAAa;GACb,eAAe;IACb,kBAAkB;IAClB,SAAS;IACV;GACD,YAAY;IACV,mBAAmB;IACnB,iBAAiB;IACjB,SAAS;IACT,kBAAkB;IACnB;GACD,iBAAiB,KAAK,OAAO,SAAS;AACpC,QAAI,QAAQ,QACV,QAAO,KAAK,QAAQ;;GAGf;GACT,eAAe,SAAS;IACtB,MAAM,YAAiC;KACrC,OAAO,EAAE,QAAQ,WAAW;KAC5B,eACE,OAAO,GAAG,oBAAoB,EAC5B,QAAQ,EAAE,QAAQ,KAAK,IAAI,EAC5B,CAAC;KACL;AAED,QAAI,CAAC,KAAK,QACR,WAAU,UAAU;AAGtB,WAAO;;GAET,OAAO,OAAO,YAAY;AAQxB,WAPiB,MAAM,OAAO,UAAU,EACtC,OAAO;KACL,GAAG;KACH,eAAe,MAAM;KACtB,EACF,CAAC;;GAIJ,SAAS;IACP,UAAU;KACR,OAAO;KACP,QAAQ,SACN,oBAAC;MAAK,MAAK;MAAK,IAAI;gBACjB,KAAK,YAAY;OACb;KAEV;IACD,OAAO;KACL,OAAO;KACP,QAAQ,SACN,qBAAC;MAAM,KAAI;iBACT,oBAAC;OAAK,MAAK;iBAAM,KAAK,SAAS;QAAW,EACzC,KAAK,SACJ,oBAAC;OACC,MAAK;OACL,SAAQ;OACR,OAAO,KAAK,gBAAgB,UAAU;OACtC,aACE,KAAK,gBACH,oBAAC,aAAU,MAAM,KAAM,GAEvB,oBAAC,SAAM,MAAM,KAAM;iBAItB,KAAK,gBAAgB,aAAa;QAC7B;OAEJ;KAEX;IACD,OAAO;KACL,OAAO;KACP,QAAQ,SACN,oBAAC;MAAM,KAAK;gBACT,KAAK,MAAM,KAAK,SACf,oBAAC;OAAiB,MAAK;OAAK,SAAQ;iBACjC;SADS,KAEJ,CACR;OACI;KAEX;IACD,SAAS;KACP,OAAO;KACP,KAAK;KACL,QAAQ,SACN,oBAAC;MACC,MAAK;MACL,SAAQ;MACR,OAAO,KAAK,UAAU,UAAU;gBAE/B,KAAK,UAAU,WAAW;OACrB;KAEX;IACD,WAAW;KACT,OAAO;KACP,KAAK;KACL,QAAQ,SACN,oBAAC;MAAK,MAAK;MAAK,GAAE;gBACf,EAAE,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;OAClC;KAEV;IACF;IACD;GACG;;AAIX,yBAAe"}
|