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,133 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import Button from 'devextreme-react/button';
|
|
3
|
+
import Drawer from 'devextreme-react/drawer';
|
|
4
|
+
import { ScrollView<%=#isTypeScript%>, ScrollViewRef<%=/isTypeScript%> } from 'devextreme-react/scroll-view';
|
|
5
|
+
import Toolbar, { Item } from 'devextreme-react/toolbar';
|
|
6
|
+
import React, { useState, useCallback, useRef } from 'react';
|
|
7
|
+
import { useRouter } from 'next/navigation';
|
|
8
|
+
import { Header, SideNavigationMenu } from '@/components';
|
|
9
|
+
import './side-nav-inner-toolbar.scss';
|
|
10
|
+
import { useScreenSize } from '@/utils/media-query';
|
|
11
|
+
import { Template } from 'devextreme-react/core/template';
|
|
12
|
+
<%=#isTypeScript%>import type { TreeViewTypes } from 'devextreme-react/tree-view';
|
|
13
|
+
import type { SideNavToolbarProps } from '@/types';
|
|
14
|
+
import type { ButtonTypes } from 'devextreme-react/button';
|
|
15
|
+
<%=/isTypeScript%>
|
|
16
|
+
export default function SideNavInnerToolbar({ title, children }<%=#isTypeScript%>: React.PropsWithChildren<SideNavToolbarProps><%=/isTypeScript%>) {
|
|
17
|
+
const scrollViewRef = useRef<%=#isTypeScript%><ScrollViewRef><%=/isTypeScript%>(null);
|
|
18
|
+
const router = useRouter();
|
|
19
|
+
const { isXSmall, isLarge } = useScreenSize();
|
|
20
|
+
const [menuStatus, setMenuStatus] = useState(
|
|
21
|
+
isLarge ? MenuStatus.Opened : MenuStatus.Closed
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const toggleMenu = useCallback(({ event }<%=#isTypeScript%>: ButtonTypes.ClickEvent<%=/isTypeScript%>) => {
|
|
25
|
+
setMenuStatus(
|
|
26
|
+
prevMenuStatus => prevMenuStatus === MenuStatus.Closed
|
|
27
|
+
? MenuStatus.Opened
|
|
28
|
+
: MenuStatus.Closed
|
|
29
|
+
);
|
|
30
|
+
event<%=#isTypeScript%>?<%=/isTypeScript%>.stopPropagation();
|
|
31
|
+
}, []);
|
|
32
|
+
|
|
33
|
+
const temporaryOpenMenu = useCallback(() => {
|
|
34
|
+
setMenuStatus(
|
|
35
|
+
prevMenuStatus => prevMenuStatus === MenuStatus.Closed
|
|
36
|
+
? MenuStatus.TemporaryOpened
|
|
37
|
+
: prevMenuStatus
|
|
38
|
+
);
|
|
39
|
+
}, []);
|
|
40
|
+
|
|
41
|
+
const onOutsideClick = useCallback(() => {
|
|
42
|
+
setMenuStatus(
|
|
43
|
+
prevMenuStatus => prevMenuStatus !== MenuStatus.Closed && !isLarge
|
|
44
|
+
? MenuStatus.Closed
|
|
45
|
+
: prevMenuStatus
|
|
46
|
+
);
|
|
47
|
+
return menuStatus === MenuStatus.Closed ? true : false;
|
|
48
|
+
}, [isLarge, menuStatus]);
|
|
49
|
+
|
|
50
|
+
const onNavigationChanged = useCallback(({ itemData, event, node }<%=#isTypeScript%>: TreeViewTypes.ItemClickEvent<%=/isTypeScript%>) => {
|
|
51
|
+
if (menuStatus === MenuStatus.Closed || !itemData<%=#isTypeScript%>?<%=/isTypeScript%>.path || node<%=#isTypeScript%>?<%=/isTypeScript%>.selected) {
|
|
52
|
+
event<%=#isTypeScript%>?<%=/isTypeScript%>.preventDefault();
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
router.push(itemData.path);
|
|
57
|
+
scrollViewRef.current<%=#isTypeScript%>?<%=/isTypeScript%>.instance().scrollTo(0);
|
|
58
|
+
|
|
59
|
+
if (!isLarge || menuStatus === MenuStatus.TemporaryOpened) {
|
|
60
|
+
setMenuStatus(MenuStatus.Closed);
|
|
61
|
+
event<%=#isTypeScript%>?<%=/isTypeScript%>.stopPropagation();
|
|
62
|
+
}
|
|
63
|
+
}, [router, menuStatus, isLarge]);
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<div className={'side-nav-inner-toolbar'}>
|
|
67
|
+
<Drawer
|
|
68
|
+
className={'drawer'}
|
|
69
|
+
position={'before'}
|
|
70
|
+
closeOnOutsideClick={onOutsideClick}
|
|
71
|
+
openedStateMode={isLarge ? 'shrink' : 'overlap'}
|
|
72
|
+
revealMode={isXSmall ? 'slide' : 'expand'}
|
|
73
|
+
minSize={isXSmall ? 0 : 60}
|
|
74
|
+
maxSize={250}
|
|
75
|
+
shading={isLarge ? false : true}
|
|
76
|
+
opened={menuStatus === MenuStatus.Closed ? false : true}
|
|
77
|
+
template={'menu'}
|
|
78
|
+
>
|
|
79
|
+
<div className={'container'}>
|
|
80
|
+
<Header
|
|
81
|
+
menuToggleEnabled={isXSmall}
|
|
82
|
+
toggleMenu={toggleMenu}
|
|
83
|
+
/>
|
|
84
|
+
<ScrollView ref={scrollViewRef} className={'layout-body with-footer'}>
|
|
85
|
+
<div className={'content'}>
|
|
86
|
+
{React.Children.map(children, (item) => {
|
|
87
|
+
if (<%=#isTypeScript%>React.isValidElement(item) && <%=/isTypeScript%>item.type !== "footer") {
|
|
88
|
+
return item;
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
})}
|
|
92
|
+
</div>
|
|
93
|
+
<div className={'content-block'}>
|
|
94
|
+
{React.Children.map(children, (item) => {
|
|
95
|
+
if (<%=#isTypeScript%>React.isValidElement(item) && <%=/isTypeScript%>item.type === "footer") {
|
|
96
|
+
return item;
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
})}
|
|
100
|
+
</div>
|
|
101
|
+
</ScrollView>
|
|
102
|
+
</div>
|
|
103
|
+
<Template name={'menu'}>
|
|
104
|
+
<SideNavigationMenu
|
|
105
|
+
compactMode={menuStatus === MenuStatus.Closed}
|
|
106
|
+
selectedItemChanged={onNavigationChanged}
|
|
107
|
+
openMenu={temporaryOpenMenu}
|
|
108
|
+
>
|
|
109
|
+
<Toolbar id={'navigation-header'}>
|
|
110
|
+
{
|
|
111
|
+
!isXSmall &&
|
|
112
|
+
<Item
|
|
113
|
+
location={'before'}
|
|
114
|
+
cssClass={'menu-button'}
|
|
115
|
+
>
|
|
116
|
+
<Button icon="menu" stylingMode="text" onClick={toggleMenu} />
|
|
117
|
+
</Item>
|
|
118
|
+
}
|
|
119
|
+
<Item location={'before'} cssClass={'header-title'} text={title} />
|
|
120
|
+
</Toolbar>
|
|
121
|
+
</SideNavigationMenu>
|
|
122
|
+
</Template>
|
|
123
|
+
</Drawer>
|
|
124
|
+
</div>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const MenuStatus = {
|
|
129
|
+
Closed: 1,
|
|
130
|
+
Opened: 2,
|
|
131
|
+
TemporaryOpened: 3
|
|
132
|
+
};
|
|
133
|
+
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import Drawer from 'devextreme-react/drawer';
|
|
3
|
+
import { ScrollView<%=#isTypeScript%>, ScrollViewRef<%=/isTypeScript%> } from 'devextreme-react/scroll-view';
|
|
4
|
+
import React, { useState, useCallback, useRef } from 'react';
|
|
5
|
+
import { useRouter } from 'next/navigation';
|
|
6
|
+
import { Header, SideNavigationMenu } from '@/components';
|
|
7
|
+
import './side-nav-outer-toolbar.scss';
|
|
8
|
+
import { useScreenSize } from '@/utils/media-query';
|
|
9
|
+
import { Template } from 'devextreme-react/core/template';
|
|
10
|
+
<%=#isTypeScript%>import type { ButtonTypes } from 'devextreme-react/button';
|
|
11
|
+
import type { TreeViewTypes } from 'devextreme-react/tree-view';
|
|
12
|
+
import type { SideNavToolbarProps } from '@/types';
|
|
13
|
+
<%=/isTypeScript%>
|
|
14
|
+
export default function SideNavOuterToolbar({ title, children }<%=#isTypeScript%>: React.PropsWithChildren<SideNavToolbarProps><%=/isTypeScript%>) {
|
|
15
|
+
const scrollViewRef = useRef<%=#isTypeScript%><ScrollViewRef><%=/isTypeScript%>(null);
|
|
16
|
+
const router = useRouter();
|
|
17
|
+
const { isXSmall, isLarge } = useScreenSize();
|
|
18
|
+
const [menuStatus, setMenuStatus] = useState(
|
|
19
|
+
isLarge ? MenuStatus.Opened : MenuStatus.Closed
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const toggleMenu = useCallback(({ event }<%=#isTypeScript%>: ButtonTypes.ClickEvent<%=/isTypeScript%>) => {
|
|
23
|
+
setMenuStatus(
|
|
24
|
+
prevMenuStatus => prevMenuStatus === MenuStatus.Closed
|
|
25
|
+
? MenuStatus.Opened
|
|
26
|
+
: MenuStatus.Closed
|
|
27
|
+
);
|
|
28
|
+
event<%=#isTypeScript%>?<%=/isTypeScript%>.stopPropagation();
|
|
29
|
+
}, []);
|
|
30
|
+
|
|
31
|
+
const temporaryOpenMenu = useCallback(() => {
|
|
32
|
+
setMenuStatus(
|
|
33
|
+
prevMenuStatus => prevMenuStatus === MenuStatus.Closed
|
|
34
|
+
? MenuStatus.TemporaryOpened
|
|
35
|
+
: prevMenuStatus
|
|
36
|
+
);
|
|
37
|
+
}, []);
|
|
38
|
+
|
|
39
|
+
const onOutsideClick = useCallback(() => {
|
|
40
|
+
setMenuStatus(
|
|
41
|
+
prevMenuStatus => prevMenuStatus !== MenuStatus.Closed && !isLarge
|
|
42
|
+
? MenuStatus.Closed
|
|
43
|
+
: prevMenuStatus
|
|
44
|
+
);
|
|
45
|
+
return menuStatus === MenuStatus.Closed ? true : false;
|
|
46
|
+
}, [isLarge, menuStatus]);
|
|
47
|
+
|
|
48
|
+
const onNavigationChanged = useCallback(({ itemData, event, node }<%=#isTypeScript%>: TreeViewTypes.ItemClickEvent<%=/isTypeScript%>) => {
|
|
49
|
+
if (menuStatus === MenuStatus.Closed || !itemData<%=#isTypeScript%>?<%=/isTypeScript%>.path || node<%=#isTypeScript%>?<%=/isTypeScript%>.selected) {
|
|
50
|
+
event<%=#isTypeScript%>?<%=/isTypeScript%>.preventDefault();
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
router.push(itemData.path);
|
|
55
|
+
scrollViewRef.current<%=#isTypeScript%>?<%=/isTypeScript%>.instance().scrollTo(0);
|
|
56
|
+
|
|
57
|
+
if (!isLarge || menuStatus === MenuStatus.TemporaryOpened) {
|
|
58
|
+
setMenuStatus(MenuStatus.Closed);
|
|
59
|
+
event<%=#isTypeScript%>?<%=/isTypeScript%>.stopPropagation();
|
|
60
|
+
}
|
|
61
|
+
}, [router, menuStatus, isLarge]);
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<div className={'side-nav-outer-toolbar'}>
|
|
65
|
+
<Header
|
|
66
|
+
menuToggleEnabled
|
|
67
|
+
toggleMenu={toggleMenu}
|
|
68
|
+
title={title}
|
|
69
|
+
/>
|
|
70
|
+
<Drawer
|
|
71
|
+
className={'drawer layout-body'}
|
|
72
|
+
position={'before'}
|
|
73
|
+
closeOnOutsideClick={onOutsideClick}
|
|
74
|
+
openedStateMode={isLarge ? 'shrink' : 'overlap'}
|
|
75
|
+
revealMode={isXSmall ? 'slide' : 'expand'}
|
|
76
|
+
minSize={isXSmall ? 0 : 60}
|
|
77
|
+
maxSize={250}
|
|
78
|
+
shading={isLarge ? false : true}
|
|
79
|
+
opened={menuStatus === MenuStatus.Closed ? false : true}
|
|
80
|
+
template={'menu'}
|
|
81
|
+
>
|
|
82
|
+
<div className={'container'}>
|
|
83
|
+
<ScrollView ref={scrollViewRef} className={'with-footer'}>
|
|
84
|
+
<div className={'content'}>
|
|
85
|
+
{React.Children.map(children, (item) => {
|
|
86
|
+
if (<%=#isTypeScript%>React.isValidElement(item) && <%=/isTypeScript%>item.type !== "footer") {
|
|
87
|
+
return item;
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
})}
|
|
91
|
+
</div>
|
|
92
|
+
<div className={'content-block'}>
|
|
93
|
+
{React.Children.map(children, (item) => {
|
|
94
|
+
if (<%=#isTypeScript%>React.isValidElement(item) && <%=/isTypeScript%>item.type === "footer") {
|
|
95
|
+
return item;
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
})}
|
|
99
|
+
</div>
|
|
100
|
+
</ScrollView>
|
|
101
|
+
</div>
|
|
102
|
+
<Template name={'menu'}>
|
|
103
|
+
<SideNavigationMenu
|
|
104
|
+
compactMode={menuStatus === MenuStatus.Closed}
|
|
105
|
+
selectedItemChanged={onNavigationChanged}
|
|
106
|
+
openMenu={temporaryOpenMenu}
|
|
107
|
+
>
|
|
108
|
+
</SideNavigationMenu>
|
|
109
|
+
</Template>
|
|
110
|
+
</Drawer>
|
|
111
|
+
</div>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const MenuStatus = {
|
|
116
|
+
Closed: 1,
|
|
117
|
+
Opened: 2,
|
|
118
|
+
TemporaryOpened: 3
|
|
119
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
.single-card {
|
|
2
|
+
width: 100%;
|
|
3
|
+
height: 100%;
|
|
4
|
+
|
|
5
|
+
.dx-card {
|
|
6
|
+
width: 360px;
|
|
7
|
+
margin: auto auto;
|
|
8
|
+
padding: 24px;
|
|
9
|
+
flex-grow: 0;
|
|
10
|
+
border-radius: 8px;
|
|
11
|
+
|
|
12
|
+
@media (max-width: 599.99px) {
|
|
13
|
+
width: 100%;
|
|
14
|
+
height: 100%;
|
|
15
|
+
border-radius: 0;
|
|
16
|
+
box-shadow: none;
|
|
17
|
+
margin: 0;
|
|
18
|
+
border: 0;
|
|
19
|
+
flex-grow: 1;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.header {
|
|
23
|
+
margin: 24px 0;
|
|
24
|
+
|
|
25
|
+
.title {
|
|
26
|
+
color: var(--base-text-color);
|
|
27
|
+
font-weight: 500;
|
|
28
|
+
font-size: 24px;
|
|
29
|
+
text-align: center;
|
|
30
|
+
line-height: 24px;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.description {
|
|
34
|
+
color: var(--base-text-color-alpha-7);
|
|
35
|
+
line-height: 16px;
|
|
36
|
+
font-size: 12px;
|
|
37
|
+
margin-top: 32px;
|
|
38
|
+
text-align: center;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import ScrollView from 'devextreme-react/scroll-view';
|
|
2
|
+
import './single-card.scss';
|
|
3
|
+
<%=#isTypeScript%>import type { SingleCardProps } from '@/types';<%=/isTypeScript%>
|
|
4
|
+
|
|
5
|
+
export default function SingleCard({ title, description, children }<%=#isTypeScript%>: React.PropsWithChildren<SingleCardProps><%=/isTypeScript%>) {
|
|
6
|
+
return (
|
|
7
|
+
<ScrollView height={'100%'} width={'100%'} className={'with-footer single-card'}>
|
|
8
|
+
<div className={'dx-card content'}>
|
|
9
|
+
<div className={'header'}>
|
|
10
|
+
<div className={'title'}>{title}</div>
|
|
11
|
+
<div className={'description'}>{description}</div>
|
|
12
|
+
</div>
|
|
13
|
+
{children}
|
|
14
|
+
</div>
|
|
15
|
+
</ScrollView>
|
|
16
|
+
)}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { NextResponse<%=#isTypeScript%>, type NextRequest<%=/isTypeScript%> } from 'next/server';
|
|
2
|
+
import { cookies } from 'next/headers';
|
|
3
|
+
import { decrypt, createSession } from '@/app/lib/session';
|
|
4
|
+
import defaultUser from '@/utils/default-user';
|
|
5
|
+
|
|
6
|
+
const isProtectedRoute = (path<%=#isTypeScript%>: string<%=/isTypeScript%>) => path.startsWith('/pages');
|
|
7
|
+
|
|
8
|
+
async function _DEMO_logIn() {
|
|
9
|
+
await createSession(defaultUser.id);
|
|
10
|
+
|
|
11
|
+
return NextResponse.next();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
<%=#isTypeScript%>// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
15
|
+
<%=/isTypeScript%>async function redirectUnauthorized(req<%=#isTypeScript%>: NextRequest<%=/isTypeScript%>) {
|
|
16
|
+
return await _DEMO_logIn();
|
|
17
|
+
|
|
18
|
+
// In production, you will need to redirect unauthorized users
|
|
19
|
+
// return NextResponse.redirect(new URL('/auth/login', req.nextUrl))
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export default async function middleware(req<%=#isTypeScript%>: NextRequest<%=/isTypeScript%>) {
|
|
23
|
+
const path = req.nextUrl.pathname;
|
|
24
|
+
|
|
25
|
+
if (!isProtectedRoute(path)) {
|
|
26
|
+
return NextResponse.next();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const cookie = (await cookies()).get('session')?.value;
|
|
30
|
+
|
|
31
|
+
if (!cookie) {
|
|
32
|
+
return await redirectUnauthorized(req);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const session = await decrypt(cookie);
|
|
36
|
+
|
|
37
|
+
if (!session?.userId) {
|
|
38
|
+
return await redirectUnauthorized(req);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return NextResponse.next();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const config = {
|
|
45
|
+
matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'],
|
|
46
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import React, { useCallback, useMemo, useState, useLayoutEffect } from 'react';
|
|
3
|
+
|
|
4
|
+
const themes = ['light', 'dark'];
|
|
5
|
+
const themeClassNamePrefix = 'dx-swatch-';
|
|
6
|
+
let currentTheme = getNextTheme();
|
|
7
|
+
|
|
8
|
+
function getNextTheme(theme = '') {
|
|
9
|
+
return themes[themes.indexOf(theme) + 1] || themes[0];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function getCurrentTheme() {
|
|
13
|
+
return currentTheme;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function toggleTheme(prevTheme<%=#isTypeScript%>: string<%=/isTypeScript%>) {
|
|
17
|
+
const isCurrentThemeDark = prevTheme === 'dark';
|
|
18
|
+
const newTheme = getNextTheme(prevTheme);
|
|
19
|
+
|
|
20
|
+
if (typeof window !== 'undefined') {
|
|
21
|
+
document.body.classList.replace(
|
|
22
|
+
themeClassNamePrefix + prevTheme,
|
|
23
|
+
themeClassNamePrefix + newTheme
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
const additionalClassNamePrefix = themeClassNamePrefix + 'additional';
|
|
27
|
+
const additionalClassNamePostfix = isCurrentThemeDark ? '-' + prevTheme : '';
|
|
28
|
+
const additionalClassName = `${additionalClassNamePrefix}${additionalClassNamePostfix}`
|
|
29
|
+
|
|
30
|
+
document.body
|
|
31
|
+
.querySelector(`.${additionalClassName}`)?.classList
|
|
32
|
+
.replace(additionalClassName, additionalClassNamePrefix + (isCurrentThemeDark ? '' : '-dark'));
|
|
33
|
+
|
|
34
|
+
currentTheme = newTheme;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return newTheme;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function useThemeContext() {
|
|
41
|
+
const [theme, setTheme] = useState(getCurrentTheme());
|
|
42
|
+
const switchTheme = useCallback(() => setTheme((currentTheme) => toggleTheme(currentTheme)), []);
|
|
43
|
+
const isDark = useCallback(()<%=#isTypeScript%>: boolean<%=/isTypeScript%> => {
|
|
44
|
+
return currentTheme === 'dark';
|
|
45
|
+
}, []);
|
|
46
|
+
|
|
47
|
+
useLayoutEffect(() => {
|
|
48
|
+
if (typeof window !== 'undefined' && !document.body.className.includes(themeClassNamePrefix)) {
|
|
49
|
+
document.body.classList.add(themeClassNamePrefix + theme);
|
|
50
|
+
}
|
|
51
|
+
}, [theme]);
|
|
52
|
+
|
|
53
|
+
return useMemo(()=> ({ theme, switchTheme, isDark }), [theme, switchTheme, isDark]);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const ThemeContext = React.createContext<%=#isTypeScript%><ReturnType<typeof useThemeContext> | null><%=/isTypeScript%>(null);
|
|
57
|
+
|
|
58
|
+
export const ThemeProvider = ({ children }<%=#isTypeScript%>: React.PropsWithChildren<%=/isTypeScript%>) => {
|
|
59
|
+
const themeContext = useThemeContext();
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<ThemeContext.Provider value={themeContext}>
|
|
63
|
+
{ children }
|
|
64
|
+
</ThemeContext.Provider>
|
|
65
|
+
);
|
|
66
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { TreeViewTypes } from 'devextreme-react/tree-view';
|
|
2
|
+
import { ButtonTypes } from 'devextreme-react/button';
|
|
3
|
+
|
|
4
|
+
export interface HeaderProps {
|
|
5
|
+
menuToggleEnabled: boolean;
|
|
6
|
+
title?: string;
|
|
7
|
+
toggleMenu: (e: ButtonTypes.ClickEvent) => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface SideNavigationMenuProps {
|
|
11
|
+
selectedItemChanged: (e: TreeViewTypes.ItemClickEvent) => void;
|
|
12
|
+
openMenu: (e: React.PointerEvent) => void;
|
|
13
|
+
compactMode: boolean;
|
|
14
|
+
onMenuReady?: (e: TreeViewTypes.ContentReadyEvent) => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface UserPanelProps {
|
|
18
|
+
menuMode: 'context' | 'list';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type SessionPayload = {
|
|
22
|
+
userId: string;
|
|
23
|
+
expiresAt: Date;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface User {
|
|
27
|
+
email: string;
|
|
28
|
+
avatarUrl: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type AuthContextType = {
|
|
32
|
+
user?: User;
|
|
33
|
+
signIn: (email: string, password: string) => Promise<{isOk: boolean, data?: User, message?: string}>;
|
|
34
|
+
signOut: () => void;
|
|
35
|
+
loading: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface SideNavToolbarProps {
|
|
39
|
+
title: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface SingleCardProps {
|
|
43
|
+
title?: string;
|
|
44
|
+
description?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export type Handle = () => void;
|
|
48
|
+
|
|
49
|
+
interface NavigationData {
|
|
50
|
+
currentPath: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export type NavigationContextType = {
|
|
54
|
+
setNavigationData?: ({ currentPath }: NavigationData) => void;
|
|
55
|
+
navigationData: NavigationData;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export type ValidationType = {
|
|
59
|
+
value: string;
|
|
60
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import { useState, useCallback, useEffect } from 'react';
|
|
3
|
+
<%=#isTypeScript%>import type { Handle } from '@/types';
|
|
4
|
+
<%=/isTypeScript%>
|
|
5
|
+
export const useScreenSize = () => {
|
|
6
|
+
const [screenSize, setScreenSize] = useState(getScreenSize());
|
|
7
|
+
const onSizeChanged = useCallback(() => {
|
|
8
|
+
setScreenSize(getScreenSize());
|
|
9
|
+
}, []);
|
|
10
|
+
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
subscribe(onSizeChanged);
|
|
13
|
+
|
|
14
|
+
return () => {
|
|
15
|
+
unsubscribe(onSizeChanged);
|
|
16
|
+
};
|
|
17
|
+
}, [onSizeChanged]);
|
|
18
|
+
|
|
19
|
+
return screenSize;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
let handlers<%=#isTypeScript%>: Handle[]<%=/isTypeScript%> = [];
|
|
23
|
+
let xSmallMedia<%=#isTypeScript%>: MediaQueryList<%=/isTypeScript%>;
|
|
24
|
+
let smallMedia<%=#isTypeScript%>: MediaQueryList<%=/isTypeScript%>;
|
|
25
|
+
let mediumMedia<%=#isTypeScript%>: MediaQueryList<%=/isTypeScript%>;
|
|
26
|
+
let largeMedia<%=#isTypeScript%>: MediaQueryList<%=/isTypeScript%>;
|
|
27
|
+
|
|
28
|
+
if (typeof window !== 'undefined') {
|
|
29
|
+
xSmallMedia = window.matchMedia('(max-width: 599.99px)');
|
|
30
|
+
smallMedia = window.matchMedia('(min-width: 600px) and (max-width: 959.99px)');
|
|
31
|
+
mediumMedia = window.matchMedia('(min-width: 960px) and (max-width: 1279.99px)');
|
|
32
|
+
largeMedia = window.matchMedia('(min-width: 1280px)');
|
|
33
|
+
|
|
34
|
+
[xSmallMedia, smallMedia, mediumMedia, largeMedia].forEach(media => {
|
|
35
|
+
media.addListener((e) => {
|
|
36
|
+
if(e.matches) {
|
|
37
|
+
handlers.forEach(handler => handler())
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const subscribe = (handler<%=#isTypeScript%>: Handle<%=/isTypeScript%>) => handlers.push(handler);
|
|
44
|
+
|
|
45
|
+
const unsubscribe = (handler<%=#isTypeScript%>: Handle<%=/isTypeScript%>) => {
|
|
46
|
+
handlers = handlers.filter(item => item !== handler);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
function getScreenSize() {
|
|
50
|
+
return {
|
|
51
|
+
isXSmall: xSmallMedia?.matches,
|
|
52
|
+
isSmall: smallMedia?.matches,
|
|
53
|
+
isMedium: mediumMedia?.matches,
|
|
54
|
+
isLarge: largeMedia?.matches
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
@use 'sass:meta';
|
|
2
|
+
@use 'sass:color';
|
|
3
|
+
@use 'sass:map';
|
|
4
|
+
@use "./themes/generated/variables.base.scss" as variablesBase;
|
|
5
|
+
@use "./themes/generated/variables.base.dark.scss" as variablesBaseDark;
|
|
6
|
+
@use "./themes/generated/variables.additional.scss" as variablesAdditional;
|
|
7
|
+
@use "./themes/generated/variables.additional.dark.scss" as variablesAdditionalDark;
|
|
8
|
+
|
|
9
|
+
@mixin theme-variables($theme-name) {
|
|
10
|
+
$theme: meta.module-variables($theme-name);
|
|
11
|
+
$base-text-color: map.get($theme, 'base-text-color');
|
|
12
|
+
$base-bg: map.get($theme, 'base-bg');
|
|
13
|
+
|
|
14
|
+
--base-text-color: #{$base-text-color};
|
|
15
|
+
--base-bg: #{$base-bg};
|
|
16
|
+
--base-bg-darken-5: #{color.adjust($base-bg, $lightness: -5%)};
|
|
17
|
+
--base-accent: #{map.get($theme, 'base-accent')};
|
|
18
|
+
--base-text-color-alpha-7: #{rgba($base-text-color, color.alpha($base-text-color) * 0.7)};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
:root {
|
|
22
|
+
body {
|
|
23
|
+
@include theme-variables('variablesBase');
|
|
24
|
+
|
|
25
|
+
--footer-border-color: rgba(224, 224, 224, 1);
|
|
26
|
+
--plus-icon-color: #242424;
|
|
27
|
+
--devextreme-logo-color: #596C7D;
|
|
28
|
+
--vue-logo-text-color: #35495E;
|
|
29
|
+
|
|
30
|
+
--shadow-color-first: rgba(0, 0, 0, 0.06);
|
|
31
|
+
--shadow-color-second: rgba(0, 0, 0, 0.12);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.dx-swatch-additional {
|
|
35
|
+
@include theme-variables('variablesAdditional');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.dx-swatch-dark {
|
|
39
|
+
@include theme-variables('variablesBaseDark');;
|
|
40
|
+
|
|
41
|
+
--plus-icon-color: #fff;
|
|
42
|
+
--devextreme-logo-color: #fff;
|
|
43
|
+
--vue-logo-text-color: #fff;
|
|
44
|
+
|
|
45
|
+
--shadow-color-first: rgba(0, 0, 0, 0.12);
|
|
46
|
+
--shadow-color-second: rgba(0, 0, 0, 0.24);
|
|
47
|
+
--footer-border-color: rgba(97, 97, 97, 1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.dx-swatch-additional-dark {
|
|
51
|
+
@include theme-variables('variablesAdditionalDark');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import './<%=pageName%>.scss';
|
|
3
|
+
|
|
4
|
+
export default function Page() {
|
|
5
|
+
return <React.Fragment>
|
|
6
|
+
<h2><%=title%></h2>
|
|
7
|
+
<div className={'content-block'}>
|
|
8
|
+
<div className={'dx-card responsive-paddings'}>
|
|
9
|
+
Put your content here
|
|
10
|
+
</div>
|
|
11
|
+
</div>
|
|
12
|
+
</React.Fragment>
|
|
13
|
+
};
|