devextreme-cli 1.11.0-alpha.0 → 1.11.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/package.json +10 -9
- package/src/application.js +40 -20
- package/src/applications/application.angular.js +11 -3
- package/src/applications/application.nextjs.js +231 -0
- package/src/applications/application.react.js +16 -6
- package/src/applications/application.vue.js +6 -11
- package/src/templates/nextjs/application/.env +1 -0
- package/src/templates/nextjs/application/devextreme.json +63 -0
- package/src/templates/nextjs/application/next.config.mjs +32 -0
- package/src/templates/nextjs/application/public/logo192.png +0 -0
- package/src/templates/nextjs/application/public/logo512.png +0 -0
- package/src/templates/nextjs/application/public/manifest.json +25 -0
- package/src/templates/nextjs/application/public/robots.txt +3 -0
- package/src/templates/nextjs/application/src/app/actions/auth.ts +76 -0
- package/src/templates/nextjs/application/src/app/auth/[type]/page.tsx +49 -0
- package/src/templates/nextjs/application/src/app/layout.tsx +17 -0
- package/src/templates/nextjs/application/src/app/lib/session.ts +47 -0
- package/src/templates/nextjs/application/src/app/pages/layout.tsx +18 -0
- package/src/templates/nextjs/application/src/app-info.tsx +5 -0
- package/src/templates/nextjs/application/src/app-navigation.tsx +21 -0
- package/src/templates/nextjs/application/src/components/change-password-form/ChangePasswordForm.tsx +86 -0
- package/src/templates/nextjs/application/src/components/create-account-form/CreateAccountForm.scss +19 -0
- package/src/templates/nextjs/application/src/components/create-account-form/CreateAccountForm.tsx +107 -0
- package/src/templates/nextjs/application/src/components/footer/Footer.scss +12 -0
- package/src/templates/nextjs/application/src/components/footer/Footer.tsx +5 -0
- package/src/templates/nextjs/application/src/components/header/Header.scss +40 -0
- package/src/templates/nextjs/application/src/components/header/Header.tsx +38 -0
- package/src/templates/nextjs/application/src/components/index.tsx +7 -0
- package/src/templates/nextjs/application/src/components/login-form/LoginForm.scss +12 -0
- package/src/templates/nextjs/application/src/components/login-form/LoginForm.tsx +101 -0
- package/src/templates/nextjs/application/src/components/reset-password-form/ResetPasswordForm.scss +12 -0
- package/src/templates/nextjs/application/src/components/reset-password-form/ResetPasswordForm.tsx +78 -0
- package/src/templates/nextjs/application/src/components/side-navigation-menu/SideNavigationMenu.scss +71 -0
- package/src/templates/nextjs/application/src/components/side-navigation-menu/SideNavigationMenu.tsx +88 -0
- package/src/templates/nextjs/application/src/components/theme-switcher/ThemeSwitcher.tsx +21 -0
- package/src/templates/nextjs/application/src/components/user-panel/UserPanel.scss +51 -0
- package/src/templates/nextjs/application/src/components/user-panel/UserPanel.tsx +55 -0
- package/src/templates/nextjs/application/src/dx-styles.scss +106 -0
- package/src/templates/nextjs/application/src/index.css +12 -0
- package/src/templates/nextjs/application/src/layouts/index.tsx +3 -0
- package/src/templates/nextjs/application/src/layouts/side-nav-inner-toolbar/side-nav-inner-toolbar.scss +17 -0
- package/src/templates/nextjs/application/src/layouts/side-nav-inner-toolbar/side-nav-inner-toolbar.tsx +133 -0
- package/src/templates/nextjs/application/src/layouts/side-nav-outer-toolbar/side-nav-outer-toolbar.scss +10 -0
- package/src/templates/nextjs/application/src/layouts/side-nav-outer-toolbar/side-nav-outer-toolbar.tsx +119 -0
- package/src/templates/nextjs/application/src/layouts/single-card/single-card.scss +42 -0
- package/src/templates/nextjs/application/src/layouts/single-card/single-card.tsx +16 -0
- package/src/templates/nextjs/application/src/middleware.ts +46 -0
- package/src/templates/nextjs/application/src/theme.tsx +66 -0
- package/src/templates/nextjs/application/src/themes/metadata.additional.dark.json +11 -0
- package/src/templates/nextjs/application/src/themes/metadata.additional.json +11 -0
- package/src/templates/nextjs/application/src/themes/metadata.base.dark.json +8 -0
- package/src/templates/nextjs/application/src/themes/metadata.base.json +7 -0
- package/src/templates/nextjs/application/src/types.tsx +60 -0
- package/src/templates/nextjs/application/src/utils/default-user.tsx +7 -0
- package/src/templates/nextjs/application/src/utils/media-query.tsx +56 -0
- package/src/templates/nextjs/application/src/variables.scss +53 -0
- package/src/templates/nextjs/page/page.scss +0 -0
- package/src/templates/nextjs/page/page.tsx +13 -0
- package/src/templates/nextjs/sample-pages/home/home.scss +37 -0
- package/src/templates/nextjs/sample-pages/home/page.tsx +101 -0
- package/src/templates/nextjs/sample-pages/profile/page.tsx +61 -0
- package/src/templates/nextjs/sample-pages/profile/profile.scss +19 -0
- package/src/templates/nextjs/sample-pages/tasks/page.tsx +112 -0
- package/src/templates/nextjs/sample-pages/tasks/tasks.scss +3 -0
- package/src/templates/react/application/src/App.tsx +2 -1
- package/src/templates/react/application/src/Content.tsx +1 -1
- package/src/templates/react/application/src/app-routes.tsx +3 -3
- package/src/templates/react/application/src/components/change-password-form/ChangePasswordForm.tsx +1 -1
- package/src/templates/react/application/src/components/create-account-form/CreateAccountForm.tsx +1 -1
- package/src/templates/react/application/src/components/header/Header.scss +1 -1
- package/src/templates/react/application/src/components/login-form/LoginForm.tsx +1 -1
- package/src/templates/react/application/src/components/side-navigation-menu/SideNavigationMenu.tsx +2 -2
- package/src/templates/react/application/src/components/user-panel/UserPanel.tsx +1 -1
- package/src/templates/react/application/src/contexts/auth-hooks.ts +8 -0
- package/src/templates/react/application/src/contexts/auth.tsx +7 -5
- package/src/templates/react/application/src/contexts/navigation-hooks.ts +22 -0
- package/src/templates/react/application/src/contexts/navigation.tsx +3 -17
- package/src/templates/react/application/src/dx-styles.scss +3 -3
- package/src/templates/react/application/src/layouts/side-nav-inner-toolbar/side-nav-inner-toolbar.tsx +3 -3
- package/src/templates/react/application/src/layouts/side-nav-outer-toolbar/side-nav-outer-toolbar.tsx +3 -3
- package/src/templates/react/application/src/types.tsx +2 -2
- package/src/templates/react/page/page.tsx +1 -1
- package/src/templates/react/sample-pages/home/home.tsx +1 -1
- package/src/templates/react/sample-pages/index.tsx +3 -3
- package/src/templates/react/sample-pages/profile/profile.tsx +1 -1
- package/src/templates/react/sample-pages/tasks/tasks.tsx +1 -1
- package/src/templates/vue-v3/application/eslint.config.js +32 -0
- package/src/templates/vue-v3/application/src/App.vue +1 -1
- package/src/templates/vue-v3/application/src/components/header-toolbar.vue +2 -2
- package/src/templates/vue-v3/application/src/dx-styles.scss +4 -3
- package/src/templates/vue-v3/application/src/layouts/side-nav-inner-toolbar.vue +2 -2
- package/src/templates/vue-v3/application/src/layouts/side-nav-outer-toolbar.vue +2 -2
- package/src/templates/vue-v3/application/src/main.js +1 -1
- package/src/templates/vue-v3/application/src/router.js +5 -5
- package/src/utility/latest-versions.js +7 -4
- package/src/utility/module.js +13 -5
- package/src/utility/prompts/react-app-type.js +17 -0
- package/src/utility/run-command.js +10 -2
- package/src/utility/template-creator.js +8 -4
- package/src/templates/vue-v3/application/vue.config.js +0 -1
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import <%=#isTypeScript%>React, <%=/isTypeScript%>{ useState, useRef, useCallback } from 'react';
|
|
3
|
+
import { useRouter } from 'next/navigation';
|
|
4
|
+
import Link from 'next/link';
|
|
5
|
+
import Form, {
|
|
6
|
+
Item,
|
|
7
|
+
Label,
|
|
8
|
+
ButtonItem,
|
|
9
|
+
ButtonOptions,
|
|
10
|
+
RequiredRule,
|
|
11
|
+
EmailRule
|
|
12
|
+
} from 'devextreme-react/form';
|
|
13
|
+
import LoadIndicator from 'devextreme-react/load-indicator';
|
|
14
|
+
import Button from 'devextreme-react/button';
|
|
15
|
+
import notify from 'devextreme/ui/notify';
|
|
16
|
+
import { signIn } from '@/app/actions/auth';
|
|
17
|
+
|
|
18
|
+
import './LoginForm.scss';
|
|
19
|
+
|
|
20
|
+
export default function LoginForm() {
|
|
21
|
+
const router = useRouter();
|
|
22
|
+
const [loading, setLoading] = useState(false);
|
|
23
|
+
const formData = useRef({ email: '', password: '' });
|
|
24
|
+
|
|
25
|
+
const onSubmit = useCallback(async (e<%=#isTypeScript%>: React.FormEvent<HTMLFormElement><%=/isTypeScript%>) => {
|
|
26
|
+
e.preventDefault();
|
|
27
|
+
const { email, password } = formData.current;
|
|
28
|
+
setLoading(true);
|
|
29
|
+
|
|
30
|
+
const result = await signIn(email, password);
|
|
31
|
+
if (!result.isOk) {
|
|
32
|
+
setLoading(false);
|
|
33
|
+
notify(result.message, 'error', 2000);
|
|
34
|
+
} else {
|
|
35
|
+
router.push('/');
|
|
36
|
+
}
|
|
37
|
+
}, [router]);
|
|
38
|
+
|
|
39
|
+
const onCreateAccountClick = useCallback(() => {
|
|
40
|
+
router.push('/auth/create-account');
|
|
41
|
+
}, [router]);
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<form className={'login-form'} onSubmit={onSubmit}>
|
|
45
|
+
<Form formData={formData.current} disabled={loading}>
|
|
46
|
+
<Item
|
|
47
|
+
dataField={'email'}
|
|
48
|
+
editorType={'dxTextBox'}
|
|
49
|
+
editorOptions={emailEditorOptions}
|
|
50
|
+
>
|
|
51
|
+
<RequiredRule message="Email is required" />
|
|
52
|
+
<EmailRule message="Email is invalid" />
|
|
53
|
+
<Label visible={false} />
|
|
54
|
+
</Item>
|
|
55
|
+
<Item
|
|
56
|
+
dataField={'password'}
|
|
57
|
+
editorType={'dxTextBox'}
|
|
58
|
+
editorOptions={passwordEditorOptions}
|
|
59
|
+
>
|
|
60
|
+
<RequiredRule message="Password is required" />
|
|
61
|
+
<Label visible={false} />
|
|
62
|
+
</Item>
|
|
63
|
+
<Item
|
|
64
|
+
dataField={'rememberMe'}
|
|
65
|
+
editorType={'dxCheckBox'}
|
|
66
|
+
editorOptions={rememberMeEditorOptions}
|
|
67
|
+
>
|
|
68
|
+
<Label visible={false} />
|
|
69
|
+
</Item>
|
|
70
|
+
<ButtonItem>
|
|
71
|
+
<ButtonOptions
|
|
72
|
+
width={'100%'}
|
|
73
|
+
type={'default'}
|
|
74
|
+
useSubmitBehavior={true}
|
|
75
|
+
>
|
|
76
|
+
<span className="dx-button-text">
|
|
77
|
+
{
|
|
78
|
+
loading
|
|
79
|
+
? <LoadIndicator width={'24px'} height={'24px'} visible={true} />
|
|
80
|
+
: 'Sign In'
|
|
81
|
+
}
|
|
82
|
+
</span>
|
|
83
|
+
</ButtonOptions>
|
|
84
|
+
</ButtonItem>
|
|
85
|
+
</Form>
|
|
86
|
+
<div className={'link'}>
|
|
87
|
+
<Link href={'/auth/reset-password'}>Forgot password?</Link>
|
|
88
|
+
</div>
|
|
89
|
+
<Button
|
|
90
|
+
text={'Create an account'}
|
|
91
|
+
stylingMode={ 'outlined' }
|
|
92
|
+
width={'100%'}
|
|
93
|
+
onClick={onCreateAccountClick}
|
|
94
|
+
/>
|
|
95
|
+
</form>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const emailEditorOptions = { stylingMode: 'filled', placeholder: 'Email', mode: 'email' };
|
|
100
|
+
const passwordEditorOptions = { stylingMode: 'filled', placeholder: 'Password', mode: 'password' };
|
|
101
|
+
const rememberMeEditorOptions = { text: 'Remember me', elementAttr: { class: 'form-text' } };
|
package/src/templates/nextjs/application/src/components/reset-password-form/ResetPasswordForm.tsx
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import <%=#isTypeScript%>React, <%=/isTypeScript%>{ useState, useRef, useCallback } from 'react';
|
|
3
|
+
import { useRouter } from 'next/navigation';
|
|
4
|
+
import Link from 'next/link';
|
|
5
|
+
import Form, {
|
|
6
|
+
Item,
|
|
7
|
+
Label,
|
|
8
|
+
ButtonItem,
|
|
9
|
+
ButtonOptions,
|
|
10
|
+
RequiredRule,
|
|
11
|
+
EmailRule
|
|
12
|
+
} from 'devextreme-react/form';
|
|
13
|
+
import LoadIndicator from 'devextreme-react/load-indicator';
|
|
14
|
+
import notify from 'devextreme/ui/notify';
|
|
15
|
+
import { resetPassword } from '@/app/actions/auth';
|
|
16
|
+
import './ResetPasswordForm.scss';
|
|
17
|
+
|
|
18
|
+
const notificationText = 'We\'ve sent a link to reset your password. Check your inbox.';
|
|
19
|
+
|
|
20
|
+
export default function ResetPasswordForm() {
|
|
21
|
+
const router = useRouter();
|
|
22
|
+
const [loading, setLoading] = useState(false);
|
|
23
|
+
const formData = useRef({ email: '', password: '' });
|
|
24
|
+
|
|
25
|
+
const onSubmit = useCallback(async (e<%=#isTypeScript%>: React.FormEvent<HTMLFormElement><%=/isTypeScript%>) => {
|
|
26
|
+
e.preventDefault();
|
|
27
|
+
const { email } = formData.current;
|
|
28
|
+
setLoading(true);
|
|
29
|
+
|
|
30
|
+
const result = await resetPassword(email);
|
|
31
|
+
setLoading(false);
|
|
32
|
+
|
|
33
|
+
if (result.isOk) {
|
|
34
|
+
router.push('/login');
|
|
35
|
+
notify(notificationText, 'success', 2500);
|
|
36
|
+
} else {
|
|
37
|
+
notify(result.message, 'error', 2000);
|
|
38
|
+
}
|
|
39
|
+
}, [router]);
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<form className={'reset-password-form'} onSubmit={onSubmit}>
|
|
43
|
+
<Form formData={formData.current} disabled={loading}>
|
|
44
|
+
<Item
|
|
45
|
+
dataField={'email'}
|
|
46
|
+
editorType={'dxTextBox'}
|
|
47
|
+
editorOptions={emailEditorOptions}
|
|
48
|
+
>
|
|
49
|
+
<RequiredRule message="Email is required" />
|
|
50
|
+
<EmailRule message="Email is invalid" />
|
|
51
|
+
<Label visible={false} />
|
|
52
|
+
</Item>
|
|
53
|
+
<ButtonItem>
|
|
54
|
+
<ButtonOptions
|
|
55
|
+
elementAttr={submitButtonAttributes}
|
|
56
|
+
width={'100%'}
|
|
57
|
+
type={'default'}
|
|
58
|
+
useSubmitBehavior={true}
|
|
59
|
+
>
|
|
60
|
+
<span className="dx-button-text">
|
|
61
|
+
{
|
|
62
|
+
loading
|
|
63
|
+
? <LoadIndicator width={'24px'} height={'24px'} visible={true} />
|
|
64
|
+
: 'Reset my password'
|
|
65
|
+
}
|
|
66
|
+
</span>
|
|
67
|
+
</ButtonOptions>
|
|
68
|
+
</ButtonItem>
|
|
69
|
+
</Form>
|
|
70
|
+
<div className={'login-link'}>
|
|
71
|
+
Return to <Link href={'/login'}>Sign In</Link>
|
|
72
|
+
</div>
|
|
73
|
+
</form>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const emailEditorOptions = { stylingMode: 'filled', placeholder: 'Email', mode: 'email' };
|
|
78
|
+
const submitButtonAttributes = { class: 'submit-button' };
|
package/src/templates/nextjs/application/src/components/side-navigation-menu/SideNavigationMenu.scss
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
@use "../../dx-styles" as *;
|
|
2
|
+
|
|
3
|
+
.dx-swatch-additional, .dx-swatch-additional-dark {
|
|
4
|
+
&.side-navigation-menu {
|
|
5
|
+
display: flex;
|
|
6
|
+
flex-direction: column;
|
|
7
|
+
min-height: 100%;
|
|
8
|
+
height: 100%;
|
|
9
|
+
width: 250px !important;
|
|
10
|
+
background-color: var(--base-bg);
|
|
11
|
+
|
|
12
|
+
.menu-container {
|
|
13
|
+
min-height: 100%;
|
|
14
|
+
display: flex;
|
|
15
|
+
flex: 1;
|
|
16
|
+
|
|
17
|
+
.dx-treeview {
|
|
18
|
+
// ## Long text positioning
|
|
19
|
+
white-space: nowrap;
|
|
20
|
+
// ##
|
|
21
|
+
|
|
22
|
+
.dx-treeview-node-container:empty {
|
|
23
|
+
display: none;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ## Icon width customization
|
|
27
|
+
.dx-treeview-item {
|
|
28
|
+
padding-left: 0;
|
|
29
|
+
border-radius: 0;
|
|
30
|
+
flex-direction: row-reverse;
|
|
31
|
+
|
|
32
|
+
.dx-icon {
|
|
33
|
+
width: $side-panel-min-width !important;
|
|
34
|
+
margin: 0 !important;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ##
|
|
39
|
+
|
|
40
|
+
// ## Arrow customization
|
|
41
|
+
.dx-treeview-node {
|
|
42
|
+
padding: 0 0 !important;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.dx-treeview-toggle-item-visibility {
|
|
46
|
+
right: 10px;
|
|
47
|
+
left: auto;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.dx-rtl .dx-treeview-toggle-item-visibility {
|
|
51
|
+
left: 10px;
|
|
52
|
+
right: auto;
|
|
53
|
+
}
|
|
54
|
+
// ##
|
|
55
|
+
|
|
56
|
+
// ## Item levels customization
|
|
57
|
+
.dx-treeview-node {
|
|
58
|
+
&[aria-level="1"] {
|
|
59
|
+
font-weight: bold;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
&[aria-level="2"] .dx-treeview-item-content {
|
|
63
|
+
font-weight: normal;
|
|
64
|
+
padding: 0 $side-panel-min-width;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// ##
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
package/src/templates/nextjs/application/src/components/side-navigation-menu/SideNavigationMenu.tsx
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import React, { useEffect, useRef, useCallback, useMemo, useContext } from 'react';
|
|
3
|
+
import { TreeView<%=#isTypeScript%>, TreeViewRef<%=/isTypeScript%> } from 'devextreme-react/tree-view';
|
|
4
|
+
import * as events from 'devextreme-react/common/core/events';
|
|
5
|
+
import { navigation } from '@/app-navigation';
|
|
6
|
+
import { usePathname } from 'next/navigation';
|
|
7
|
+
import { useScreenSize } from '@/utils/media-query';
|
|
8
|
+
import './SideNavigationMenu.scss';
|
|
9
|
+
<%=#isTypeScript%>import type { SideNavigationMenuProps } from '@/types';
|
|
10
|
+
<%=/isTypeScript%>import { ThemeContext } from '@/theme';
|
|
11
|
+
|
|
12
|
+
export default function SideNavigationMenu(props<%=#isTypeScript%>: React.PropsWithChildren<SideNavigationMenuProps><%=/isTypeScript%>) {
|
|
13
|
+
const {
|
|
14
|
+
children,
|
|
15
|
+
selectedItemChanged,
|
|
16
|
+
openMenu,
|
|
17
|
+
compactMode,
|
|
18
|
+
onMenuReady
|
|
19
|
+
} = props;
|
|
20
|
+
|
|
21
|
+
const theme = useContext(ThemeContext);
|
|
22
|
+
const { isLarge } = useScreenSize();
|
|
23
|
+
function normalizePath () {
|
|
24
|
+
return navigation.map((item) => (
|
|
25
|
+
{ ...item, expanded: isLarge, path: item.path && !(/^\//.test(item.path)) ? `/${item.path}` : item.path }
|
|
26
|
+
))
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const items = useMemo(
|
|
30
|
+
normalizePath,
|
|
31
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
32
|
+
[]
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const pathname = usePathname();
|
|
36
|
+
|
|
37
|
+
const treeViewRef = useRef<%=#isTypeScript%><TreeViewRef><%=/isTypeScript%>(null);
|
|
38
|
+
const wrapperRef = useRef<%=#isTypeScript%><HTMLDivElement><%=/isTypeScript%>(null);
|
|
39
|
+
const getWrapperRef = useCallback((element<%=#isTypeScript%>: HTMLDivElement<%=/isTypeScript%>) => {
|
|
40
|
+
const prevElement = wrapperRef.current;
|
|
41
|
+
if (prevElement) {
|
|
42
|
+
events.off(prevElement, 'dxclick');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
wrapperRef.current = element;
|
|
46
|
+
events.on(element, 'dxclick', (e<%=#isTypeScript%>: React.PointerEvent<%=/isTypeScript%>) => {
|
|
47
|
+
openMenu(e);
|
|
48
|
+
});
|
|
49
|
+
}, [openMenu]);
|
|
50
|
+
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
const treeView = treeViewRef.current && treeViewRef.current.instance();
|
|
53
|
+
if (!treeView) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (pathname !== undefined) {
|
|
58
|
+
treeView.selectItem(pathname);
|
|
59
|
+
treeView.expandItem(pathname);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (compactMode) {
|
|
63
|
+
treeView.collapseAll();
|
|
64
|
+
}
|
|
65
|
+
}, [pathname, compactMode]);
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<div
|
|
69
|
+
className={`dx-swatch-additional${theme?.isDark() ? '-dark' : ''} side-navigation-menu`}
|
|
70
|
+
ref={getWrapperRef}
|
|
71
|
+
>
|
|
72
|
+
{children}
|
|
73
|
+
<div className={'menu-container'}>
|
|
74
|
+
<TreeView
|
|
75
|
+
ref={treeViewRef}
|
|
76
|
+
items={items}
|
|
77
|
+
keyExpr={'path'}
|
|
78
|
+
selectionMode={'single'}
|
|
79
|
+
focusStateEnabled={false}
|
|
80
|
+
expandEvent={'click'}
|
|
81
|
+
onItemClick={selectedItemChanged}
|
|
82
|
+
onContentReady={onMenuReady}
|
|
83
|
+
width={'100%'}
|
|
84
|
+
/>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import { useCallback, useContext } from 'react';
|
|
3
|
+
import Button from 'devextreme-react/button';
|
|
4
|
+
import { ThemeContext } from '@/theme';
|
|
5
|
+
|
|
6
|
+
export const ThemeSwitcher = () => {
|
|
7
|
+
const themeContext = useContext(ThemeContext);
|
|
8
|
+
|
|
9
|
+
const onButtonClick = useCallback(() => {
|
|
10
|
+
themeContext?.switchTheme();
|
|
11
|
+
}, [themeContext]);
|
|
12
|
+
|
|
13
|
+
return <div>
|
|
14
|
+
<Button
|
|
15
|
+
className='theme-button'
|
|
16
|
+
stylingMode='text'
|
|
17
|
+
icon={`${themeContext?.theme === 'dark' ? 'sun' : 'moon'}`}
|
|
18
|
+
onClick={onButtonClick}
|
|
19
|
+
/>
|
|
20
|
+
</div>;
|
|
21
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
.app .header-toolbar .user-panel .user-button.dx-dropdownbutton img.dx-icon {
|
|
2
|
+
height: 100%;
|
|
3
|
+
width: auto;
|
|
4
|
+
|
|
5
|
+
.dx-theme-generic & {
|
|
6
|
+
max-height: 32px;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.user-panel {
|
|
11
|
+
display: flex;
|
|
12
|
+
flex-direction: column;
|
|
13
|
+
|
|
14
|
+
.user-button.dx-dropdownbutton {
|
|
15
|
+
margin-left: 5px;
|
|
16
|
+
|
|
17
|
+
img.dx-icon {
|
|
18
|
+
border-radius: 50%;
|
|
19
|
+
margin: 0;
|
|
20
|
+
width: auto;
|
|
21
|
+
aspect-ratio: 1 / 1;
|
|
22
|
+
box-sizing: border-box;
|
|
23
|
+
border: 1px solid var(--dx-color-border);
|
|
24
|
+
object-fit: cover;
|
|
25
|
+
object-position: top;
|
|
26
|
+
background: rgb(255, 255, 255);
|
|
27
|
+
background-clip: padding-box;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
.dx-buttongroup {
|
|
33
|
+
vertical-align: middle;
|
|
34
|
+
|
|
35
|
+
.dx-button.dx-button-has-icon:not(.dx-button-has-text) {
|
|
36
|
+
.dx-button-content {
|
|
37
|
+
padding: 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
&.dx-state-hover,
|
|
41
|
+
&.dx-state-focused {
|
|
42
|
+
background-color: transparent;
|
|
43
|
+
|
|
44
|
+
img.dx-icon {
|
|
45
|
+
border-color: var(--dx-color-primary);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import { useMemo, useCallback } from 'react';
|
|
3
|
+
import { useRouter } from 'next/navigation';
|
|
4
|
+
import DropDownButton from 'devextreme-react/drop-down-button';
|
|
5
|
+
import List from 'devextreme-react/list';
|
|
6
|
+
import { signOut } from '@/app/actions/auth';
|
|
7
|
+
import './UserPanel.scss';
|
|
8
|
+
<%=#isTypeScript%>import type { UserPanelProps } from '@/types';<%=/isTypeScript%>
|
|
9
|
+
|
|
10
|
+
export default function UserPanel({ menuMode }<%=#isTypeScript%>: UserPanelProps<%=/isTypeScript%>) {
|
|
11
|
+
const router = useRouter();
|
|
12
|
+
|
|
13
|
+
const navigateToProfile = useCallback(() => {
|
|
14
|
+
router.push("/pages/profile");
|
|
15
|
+
}, [router]);
|
|
16
|
+
|
|
17
|
+
const menuItems = useMemo(() => ([
|
|
18
|
+
{
|
|
19
|
+
text: 'Profile',
|
|
20
|
+
icon: 'user',
|
|
21
|
+
onClick: navigateToProfile
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
text: 'Logout',
|
|
25
|
+
icon: 'runner',
|
|
26
|
+
onClick: signOut
|
|
27
|
+
}
|
|
28
|
+
]), [navigateToProfile]);
|
|
29
|
+
|
|
30
|
+
const dropDownButtonAttributes = {
|
|
31
|
+
class: 'user-button'
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const buttonDropDownOptions = {
|
|
35
|
+
width: '150px'
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div className='user-panel'>
|
|
40
|
+
{menuMode === 'context' && (
|
|
41
|
+
<DropDownButton
|
|
42
|
+
stylingMode='text'
|
|
43
|
+
icon='https://js.devexpress.com/Demos/WidgetsGallery/JSDemos/images/employees/06.png'
|
|
44
|
+
showArrowIcon={false}
|
|
45
|
+
elementAttr={dropDownButtonAttributes}
|
|
46
|
+
dropDownOptions={buttonDropDownOptions}
|
|
47
|
+
items={menuItems}>
|
|
48
|
+
</DropDownButton>
|
|
49
|
+
)}
|
|
50
|
+
{menuMode === 'list' && (
|
|
51
|
+
<List items={menuItems} />
|
|
52
|
+
)}
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
@use "variables.scss" as *;
|
|
2
|
+
|
|
3
|
+
$side-panel-min-width: 60px;
|
|
4
|
+
|
|
5
|
+
html,
|
|
6
|
+
body {
|
|
7
|
+
margin: 0;
|
|
8
|
+
min-height: 100%;
|
|
9
|
+
height: 100%;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.dx-viewport {
|
|
13
|
+
.dx-popup-wrapper {
|
|
14
|
+
z-index: 1510 !important;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
#root {
|
|
18
|
+
height: 100%;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
* {
|
|
22
|
+
box-sizing: border-box;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.app {
|
|
26
|
+
background-color: var(--base-bg-darken-5);
|
|
27
|
+
display: flex;
|
|
28
|
+
height: 100%;
|
|
29
|
+
width: 100%;
|
|
30
|
+
min-width: 320px;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.content {
|
|
34
|
+
line-height: 1.5;
|
|
35
|
+
flex-grow: 1;
|
|
36
|
+
padding: 20px 40px;
|
|
37
|
+
|
|
38
|
+
h2 {
|
|
39
|
+
font-size: 32px;
|
|
40
|
+
margin: 0;
|
|
41
|
+
line-height: 40px;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@media (max-width: 599.99px) {
|
|
46
|
+
:not(.dx-card).content {
|
|
47
|
+
padding: 20px;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.container {
|
|
52
|
+
height: 100%;
|
|
53
|
+
flex-direction: column;
|
|
54
|
+
display: flex;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.layout-body {
|
|
58
|
+
flex: 1;
|
|
59
|
+
min-height: 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.side-nav-outer-toolbar .dx-drawer {
|
|
63
|
+
height: calc(100% - 56px)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.content-block {
|
|
67
|
+
margin-top: 20px;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.responsive-paddings {
|
|
71
|
+
padding: 20px;
|
|
72
|
+
|
|
73
|
+
@media (min-width: 1280px) {
|
|
74
|
+
padding: 40px;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.dx-card.wide-card {
|
|
79
|
+
border-radius: 0;
|
|
80
|
+
margin-left: 0;
|
|
81
|
+
margin-right: 0;
|
|
82
|
+
border-right: 0;
|
|
83
|
+
border-left: 0;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.with-footer > .dx-scrollable-wrapper >
|
|
87
|
+
.dx-scrollable-container > .dx-scrollable-content {
|
|
88
|
+
height: 100%;
|
|
89
|
+
|
|
90
|
+
& > .dx-scrollview-content {
|
|
91
|
+
display: flex;
|
|
92
|
+
flex-direction: column;
|
|
93
|
+
min-height: 100%;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.dx-theme-fluent {
|
|
100
|
+
.dx-drawer-wrapper {
|
|
101
|
+
.dx-drawer-panel-content,
|
|
102
|
+
.dx-overlay-content {
|
|
103
|
+
box-shadow: 0 4px 4px 0 var(--shadow-color-first), 0 1px 2px 0 var(--shadow-color-second);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
body {
|
|
2
|
+
margin: 0;
|
|
3
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
|
4
|
+
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
|
5
|
+
sans-serif;
|
|
6
|
+
-moz-osx-font-smoothing: grayscale;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
code {
|
|
10
|
+
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
|
11
|
+
monospace;
|
|
12
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
.side-nav-inner-toolbar {
|
|
2
|
+
width: 100%;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
#navigation-header {
|
|
6
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
|
|
7
|
+
background-color: var(--base-bg);
|
|
8
|
+
|
|
9
|
+
@media (max-width: 599.99px) {
|
|
10
|
+
padding-left: 20px;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.dx-theme-generic & {
|
|
14
|
+
padding-top: 10px;
|
|
15
|
+
padding-bottom: 10px;
|
|
16
|
+
}
|
|
17
|
+
}
|