devextreme-cli 1.10.1 → 1.11.0-beta.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 +7 -5
- package/src/application.js +40 -20
- package/src/applications/application.angular.js +42 -9
- package/src/applications/application.nextjs.js +231 -0
- package/src/applications/application.react.js +41 -25
- package/src/applications/application.vue.js +23 -6
- 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/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/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 +0 -1
- package/src/templates/react/application/src/components/change-password-form/ChangePasswordForm.tsx +2 -2
- package/src/templates/react/application/src/components/create-account-form/CreateAccountForm.scss +0 -2
- package/src/templates/react/application/src/components/create-account-form/CreateAccountForm.tsx +2 -2
- package/src/templates/react/application/src/components/footer/Footer.tsx +0 -1
- package/src/templates/react/application/src/components/header/Header.scss +1 -1
- package/src/templates/react/application/src/components/header/Header.tsx +0 -1
- package/src/templates/react/application/src/components/login-form/LoginForm.scss +0 -2
- package/src/templates/react/application/src/components/login-form/LoginForm.tsx +2 -2
- package/src/templates/react/application/src/components/reset-password-form/ResetPasswordForm.scss +0 -2
- package/src/templates/react/application/src/components/reset-password-form/ResetPasswordForm.tsx +2 -2
- package/src/templates/react/application/src/components/side-navigation-menu/SideNavigationMenu.scss +3 -3
- package/src/templates/react/application/src/components/side-navigation-menu/SideNavigationMenu.tsx +1 -1
- package/src/templates/react/application/src/components/theme-switcher/ThemeSwitcher.tsx +1 -1
- package/src/templates/react/application/src/components/user-panel/UserPanel.tsx +1 -1
- package/src/templates/react/application/src/dx-styles.scss +5 -3
- package/src/templates/react/application/src/layouts/side-nav-inner-toolbar/side-nav-inner-toolbar.tsx +11 -5
- package/src/templates/react/application/src/layouts/side-nav-outer-toolbar/side-nav-outer-toolbar.tsx +11 -5
- package/src/templates/react/application/src/layouts/single-card/single-card.scss +0 -2
- package/src/templates/react/application/src/layouts/single-card/single-card.tsx +0 -1
- package/src/templates/react/application/src/types.tsx +1 -2
- package/src/templates/react/application/src/utils/media-query.tsx +3 -1
- package/src/templates/react/application/src/utils/patches.scss +4 -4
- package/src/templates/react/sample-pages/home/home.scss +0 -2
- package/src/templates/react/sample-pages/home/home.tsx +1 -1
- package/src/templates/react/sample-pages/tasks/tasks.tsx +4 -4
- package/src/templates/vue-v3/application/src/components/header-toolbar.vue +1 -1
- package/src/templates/vue-v3/application/src/dx-styles.scss +4 -3
- package/src/templates/vue-v3/sample-pages/tasks-page.vue +1 -1
- package/src/utility/extract-deps-version-tag.js +13 -0
- package/src/utility/latest-versions.js +6 -3
- package/src/utility/module.js +11 -3
- package/src/utility/prompts/react-app-type.js +17 -0
- package/src/utility/prompts/transpiler.js +17 -0
- package/src/utility/run-command.js +10 -2
- package/src/utility/template-creator.js +8 -4
- package/src/utility/typescript-extension.js +1 -1
- package/src/templates/cra-template/LICENSE +0 -21
- package/src/templates/cra-template/README.md +0 -10
- package/src/templates/cra-template/package.json +0 -26
- package/src/templates/cra-template/template/README.md +0 -70
- package/src/templates/cra-template/template/gitignore +0 -23
- package/src/templates/cra-template/template/public/favicon.ico +0 -0
- package/src/templates/cra-template/template/public/index.html +0 -43
- package/src/templates/cra-template/template/src/App.css +0 -38
- package/src/templates/cra-template/template/src/App.js +0 -25
- package/src/templates/cra-template/template/src/App.test.js +0 -9
- package/src/templates/cra-template/template/src/index.js +0 -17
- package/src/templates/cra-template/template/src/logo.svg +0 -1
- package/src/templates/cra-template/template/src/reportWebVitals.js +0 -13
- package/src/templates/cra-template/template/src/setupTests.js +0 -5
- package/src/templates/cra-template/template.json +0 -14
- package/src/templates/cra-template-typescript/LICENSE +0 -21
- package/src/templates/cra-template-typescript/README.md +0 -20
- package/src/templates/cra-template-typescript/package.json +0 -27
- package/src/templates/cra-template-typescript/template/README.md +0 -46
- package/src/templates/cra-template-typescript/template/gitignore +0 -23
- package/src/templates/cra-template-typescript/template/public/index.html +0 -43
- package/src/templates/cra-template-typescript/template/src/App.css +0 -38
- package/src/templates/cra-template-typescript/template/src/App.test.tsx +0 -10
- package/src/templates/cra-template-typescript/template/src/App.tsx +0 -26
- package/src/templates/cra-template-typescript/template/src/index.tsx +0 -19
- package/src/templates/cra-template-typescript/template/src/logo.svg +0 -1
- package/src/templates/cra-template-typescript/template/src/reportWebVitals.ts +0 -13
- package/src/templates/cra-template-typescript/template/src/setupTests.ts +0 -5
- package/src/templates/cra-template-typescript/template.json +0 -19
- package/src/templates/react/application/src/App.test.tsx +0 -13
- package/src/templates/react/application/src/matchMediaMock.tsx +0 -14
- package/src/templates/react/application/src/polyfills.tsx +0 -2
- package/src/utility/extract-tooling-version.js +0 -13
- /package/src/templates/{cra-template-typescript/template → nextjs/application}/public/logo192.png +0 -0
- /package/src/templates/{cra-template-typescript/template → nextjs/application}/public/logo512.png +0 -0
- /package/src/templates/{cra-template-typescript/template → nextjs/application}/public/manifest.json +0 -0
- /package/src/templates/{cra-template-typescript/template → nextjs/application}/public/robots.txt +0 -0
- /package/src/templates/{cra-template-typescript/template → nextjs/application}/src/index.css +0 -0
- /package/src/templates/{cra-template-typescript/template → react/application}/public/favicon.ico +0 -0
- /package/src/templates/{cra-template/template → react/application}/public/logo192.png +0 -0
- /package/src/templates/{cra-template/template → react/application}/public/logo512.png +0 -0
- /package/src/templates/{cra-template/template → react/application}/public/manifest.json +0 -0
- /package/src/templates/{cra-template/template → react/application}/public/robots.txt +0 -0
- /package/src/templates/{cra-template/template → react/application}/src/index.css +0 -0
|
@@ -9,7 +9,7 @@ const insertItemToArray = require('../utility/file-content').insertItemToArray;
|
|
|
9
9
|
const moduleUtils = require('../utility/module');
|
|
10
10
|
const stringUtils = require('../utility/string');
|
|
11
11
|
const latestVersions = require('../utility/latest-versions');
|
|
12
|
-
const {
|
|
12
|
+
const { depsVersionTagOptionName, extractDepsVersionTag } = require('../utility/extract-deps-version-tag');
|
|
13
13
|
const defaultStyles = [
|
|
14
14
|
'devextreme/dist/css/dx.light.css'
|
|
15
15
|
];
|
|
@@ -37,25 +37,42 @@ const preparePackageJsonForTemplate = (appPath, appName) => {
|
|
|
37
37
|
packageJsonUtils.updateName(appPath, appName);
|
|
38
38
|
};
|
|
39
39
|
|
|
40
|
-
async function createVueApp(name,
|
|
41
|
-
const
|
|
42
|
-
const argList = ['-p', `@vue/cli${toolingVersion}`, 'vue', 'create', name, '--registry', 'https://registry.npmjs.org/', '-p "Default (Vue 3)"'];
|
|
40
|
+
async function createVueApp(name, depsVersionTag) {
|
|
41
|
+
const argList = ['-p', `@vue/cli@${depsVersionTag}`, 'vue', 'create', name, '--registry', 'https://registry.npmjs.org/', '-p "Default (Vue 3)"'];
|
|
43
42
|
|
|
44
43
|
return runCommand('npx', argList);
|
|
45
44
|
}
|
|
46
45
|
|
|
46
|
+
const bumpVue = (appPath, versionTag) => {
|
|
47
|
+
const dependencies = [
|
|
48
|
+
{ name: 'vue', version: versionTag },
|
|
49
|
+
{ name: 'vue-router', version: versionTag },
|
|
50
|
+
{ name: '@vue/cli-plugin-babel', version: versionTag, dev: true },
|
|
51
|
+
{ name: '@vue/cli-plugin-eslint', version: versionTag, dev: true },
|
|
52
|
+
{ name: '@vue/cli-service', version: versionTag, dev: true },
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
packageJsonUtils.addDependencies(appPath, dependencies);
|
|
56
|
+
};
|
|
57
|
+
|
|
47
58
|
const create = async(appName, options) => {
|
|
48
59
|
const layout = await getLayoutInfo(options.layout);
|
|
60
|
+
const depsVersionTag = extractDepsVersionTag(options);
|
|
49
61
|
|
|
50
62
|
const templateOptions = {
|
|
51
63
|
project: stringUtils.humanize(appName),
|
|
52
64
|
layout: layout,
|
|
53
|
-
[
|
|
65
|
+
[depsVersionTagOptionName]: options[depsVersionTagOptionName]
|
|
54
66
|
};
|
|
55
67
|
|
|
56
|
-
await createVueApp(appName,
|
|
68
|
+
await createVueApp(appName, depsVersionTag);
|
|
57
69
|
|
|
58
70
|
const appPath = path.join(process.cwd(), appName);
|
|
71
|
+
|
|
72
|
+
if(depsVersionTag) {
|
|
73
|
+
bumpVue(appPath, depsVersionTag);
|
|
74
|
+
}
|
|
75
|
+
|
|
59
76
|
modifyIndexHtml(appPath, templateOptions.project);
|
|
60
77
|
addTemplate(appPath, appName, templateOptions);
|
|
61
78
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
SESSION_SECRET=<your_secret_key_goes_here>
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"applicationEngine": "nextjs",
|
|
3
|
+
"build": {
|
|
4
|
+
"commands": [
|
|
5
|
+
{
|
|
6
|
+
"command": "build-theme",
|
|
7
|
+
"options": {
|
|
8
|
+
"inputFile": "src/themes/metadata.base.json",
|
|
9
|
+
"outputFile": "src/themes/generated/theme.base.css"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"command": "build-theme",
|
|
14
|
+
"options": {
|
|
15
|
+
"inputFile": "src/themes/metadata.base.dark.json",
|
|
16
|
+
"outputFile": "src/themes/generated/theme.base.dark.css"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"command": "build-theme",
|
|
21
|
+
"options": {
|
|
22
|
+
"inputFile": "src/themes/metadata.additional.json",
|
|
23
|
+
"outputFile": "src/themes/generated/theme.additional.css"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"command": "build-theme",
|
|
28
|
+
"options": {
|
|
29
|
+
"inputFile": "src/themes/metadata.additional.dark.json",
|
|
30
|
+
"outputFile": "src/themes/generated/theme.additional.dark.css"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"command": "export-theme-vars",
|
|
35
|
+
"options": {
|
|
36
|
+
"inputFile": "src/themes/metadata.base.json",
|
|
37
|
+
"outputFile": "src/themes/generated/variables.base.scss"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"command": "export-theme-vars",
|
|
42
|
+
"options": {
|
|
43
|
+
"inputFile": "src/themes/metadata.base.dark.json",
|
|
44
|
+
"outputFile": "src/themes/generated/variables.base.dark.scss"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"command": "export-theme-vars",
|
|
49
|
+
"options": {
|
|
50
|
+
"inputFile": "src/themes/metadata.additional.json",
|
|
51
|
+
"outputFile": "src/themes/generated/variables.additional.scss"
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"command": "export-theme-vars",
|
|
56
|
+
"options": {
|
|
57
|
+
"inputFile": "src/themes/metadata.additional.dark.json",
|
|
58
|
+
"outputFile": "src/themes/generated/variables.additional.dark.scss"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
]
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** @type {import('next').NextConfig} */
|
|
2
|
+
const nextConfig = {
|
|
3
|
+
async redirects() {
|
|
4
|
+
return [
|
|
5
|
+
{
|
|
6
|
+
source: '/',
|
|
7
|
+
destination: '/pages/home',
|
|
8
|
+
permanent: true,
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
source: '/login',
|
|
12
|
+
destination: '/auth/login',
|
|
13
|
+
permanent: true,
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
source: '/reset-password',
|
|
17
|
+
destination: '/auth/reset-password',
|
|
18
|
+
permanent: true,
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
source: '/create-account',
|
|
22
|
+
destination: '/auth/create-account',
|
|
23
|
+
permanent: true,
|
|
24
|
+
},
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
images: {
|
|
28
|
+
remotePatterns: [new URL('https://js.devexpress.com/**')]
|
|
29
|
+
},
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export default nextConfig;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
'use server'
|
|
2
|
+
import { redirect } from 'next/navigation'
|
|
3
|
+
import defaultUser from '@/utils/default-user';
|
|
4
|
+
import { createSession, deleteSession } from '@/app/lib/session'
|
|
5
|
+
|
|
6
|
+
export async function signUp(email<%=#isTypeScript%>: string<%=/isTypeScript%>, password<%=#isTypeScript%>: string<%=/isTypeScript%>) {
|
|
7
|
+
try {
|
|
8
|
+
// 1. Check if the user exists in the database and return isOk: false if so;
|
|
9
|
+
// 2. Otherwise, add the user to the database.
|
|
10
|
+
console.log(email, password);
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
isOk: true,
|
|
14
|
+
}
|
|
15
|
+
} catch {
|
|
16
|
+
return {
|
|
17
|
+
isOk: false,
|
|
18
|
+
message: 'Unable to create an account',
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function signIn(email<%=#isTypeScript%>: string<%=/isTypeScript%>, password<%=#isTypeScript%>: string<%=/isTypeScript%>) {
|
|
24
|
+
try {
|
|
25
|
+
// Verify that a user exists
|
|
26
|
+
console.log(email, password);
|
|
27
|
+
|
|
28
|
+
await createSession(defaultUser.id);
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
isOk: true,
|
|
32
|
+
}
|
|
33
|
+
} catch {
|
|
34
|
+
return {
|
|
35
|
+
isOk: false,
|
|
36
|
+
message: 'Unable to sign in',
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function signOut() {
|
|
42
|
+
await deleteSession();
|
|
43
|
+
redirect('/login');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function changePassword(email<%=#isTypeScript%>: string<%=/isTypeScript%>, recoveryCode<%=#isTypeScript%>?: string<%=/isTypeScript%>) {
|
|
47
|
+
try {
|
|
48
|
+
// Verify the recovery code
|
|
49
|
+
console.log(email, recoveryCode);
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
isOk: true,
|
|
53
|
+
}
|
|
54
|
+
} catch {
|
|
55
|
+
return {
|
|
56
|
+
isOk: false,
|
|
57
|
+
message: 'Unable to change the password',
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export async function resetPassword(email<%=#isTypeScript%>: string<%=/isTypeScript%>) {
|
|
63
|
+
try {
|
|
64
|
+
// Reset password
|
|
65
|
+
console.log(email);
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
isOk: true,
|
|
69
|
+
}
|
|
70
|
+
} catch {
|
|
71
|
+
return {
|
|
72
|
+
isOk: false,
|
|
73
|
+
message: 'Unable to reset password',
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import { use } from 'react';
|
|
3
|
+
import { notFound } from 'next/navigation';
|
|
4
|
+
import { SingleCard } from '@/layouts';
|
|
5
|
+
import {
|
|
6
|
+
LoginForm,
|
|
7
|
+
CreateAccountForm,
|
|
8
|
+
ResetPasswordForm,
|
|
9
|
+
ChangePasswordForm,
|
|
10
|
+
} from '@/components';
|
|
11
|
+
|
|
12
|
+
const formText<%=#isTypeScript%>: Record<string, Record<string, string>><%=/isTypeScript%> = {
|
|
13
|
+
'login': {
|
|
14
|
+
title: 'Sign In'
|
|
15
|
+
},
|
|
16
|
+
'create-account': {
|
|
17
|
+
title: 'Sign Up'
|
|
18
|
+
},
|
|
19
|
+
'reset-password': {
|
|
20
|
+
title: 'Reset Password',
|
|
21
|
+
description: 'Please enter the email address that you used to register, and we will send you a link to reset your password via Email.'
|
|
22
|
+
},
|
|
23
|
+
'change-password': {
|
|
24
|
+
title: 'Change Password',
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function AuthForm({name}<%=#isTypeScript%>: {name: string}<%=/isTypeScript%>) {
|
|
29
|
+
switch (name) {
|
|
30
|
+
case 'login': return <LoginForm />;
|
|
31
|
+
case 'create-account': return <CreateAccountForm />;
|
|
32
|
+
case 'reset-password': return <ResetPasswordForm />;
|
|
33
|
+
case 'change-password': return <ChangePasswordForm />;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export default function AuthPage({ params }<%=#isTypeScript%>: {params: Promise<{type: string}>}<%=/isTypeScript%>) {
|
|
38
|
+
const { type } = use(params)
|
|
39
|
+
|
|
40
|
+
if (!formText[type]) {
|
|
41
|
+
notFound();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const { title, description } = formText[type];
|
|
45
|
+
|
|
46
|
+
return <SingleCard title={title} description={description}>
|
|
47
|
+
<AuthForm name={type}/>
|
|
48
|
+
</SingleCard>
|
|
49
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<%=#isTypeScript%>import type { PropsWithChildren } from 'react';
|
|
2
|
+
<%=/isTypeScript%>import { ThemeProvider } from "@/theme";
|
|
3
|
+
|
|
4
|
+
export default function RootLayout({ children }<%=#isTypeScript%>: PropsWithChildren<object><%=/isTypeScript%>) {
|
|
5
|
+
return (
|
|
6
|
+
<html lang="en">
|
|
7
|
+
<title>NextJs Dx App</title>
|
|
8
|
+
<body className="dx-viewport">
|
|
9
|
+
<ThemeProvider>
|
|
10
|
+
<div className='app'>
|
|
11
|
+
{children}
|
|
12
|
+
</div>
|
|
13
|
+
</ThemeProvider>
|
|
14
|
+
</body>
|
|
15
|
+
</html>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import 'server-only';
|
|
2
|
+
import { SignJWT, jwtVerify } from 'jose';
|
|
3
|
+
import { cookies } from 'next/headers';
|
|
4
|
+
<%=#isTypeScript%>import type { SessionPayload } from '@/types';
|
|
5
|
+
<%=/isTypeScript%>
|
|
6
|
+
const secretKey = process.env.SESSION_SECRET;
|
|
7
|
+
const encoder = new TextEncoder();
|
|
8
|
+
const encodedKey = encoder.encode(secretKey);
|
|
9
|
+
|
|
10
|
+
export async function encrypt(payload<%=#isTypeScript%>: SessionPayload<%=/isTypeScript%>) {
|
|
11
|
+
return new SignJWT(payload)
|
|
12
|
+
.setProtectedHeader({ alg: 'HS256' })
|
|
13
|
+
.setIssuedAt()
|
|
14
|
+
.setExpirationTime('7d')
|
|
15
|
+
.sign(encodedKey);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function decrypt(session<%=#isTypeScript%>: string | undefined = ''<%=/isTypeScript%>) {
|
|
19
|
+
try {
|
|
20
|
+
const { payload } = await jwtVerify(session, encodedKey, {
|
|
21
|
+
algorithms: ['HS256'],
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
return payload;
|
|
25
|
+
} catch {
|
|
26
|
+
console.log('Failed to verify session');
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function createSession(userId<%=#isTypeScript%>: string<%=/isTypeScript%>) {
|
|
31
|
+
const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);
|
|
32
|
+
const session = await encrypt({ userId, expiresAt });
|
|
33
|
+
const cookieStore = await cookies();
|
|
34
|
+
|
|
35
|
+
cookieStore.set('session', session, {
|
|
36
|
+
httpOnly: true,
|
|
37
|
+
secure: true,
|
|
38
|
+
expires: expiresAt,
|
|
39
|
+
sameSite: 'lax',
|
|
40
|
+
path: '/',
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function deleteSession() {
|
|
45
|
+
const cookieStore = await cookies();
|
|
46
|
+
cookieStore.delete('session');
|
|
47
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<%=#isTypeScript%>import type { PropsWithChildren } from 'react';
|
|
2
|
+
<%=/isTypeScript%>import appInfo from '@/app-info';
|
|
3
|
+
import { Footer } from '@/components';
|
|
4
|
+
import { <%=layout%> as SideNavBarLayout } from '@/layouts';
|
|
5
|
+
|
|
6
|
+
export default function Content({children}<%=#isTypeScript%>: PropsWithChildren<object><%=/isTypeScript%>) {
|
|
7
|
+
return (
|
|
8
|
+
<SideNavBarLayout title={appInfo.title}>
|
|
9
|
+
{children}
|
|
10
|
+
<Footer>
|
|
11
|
+
Copyright © 2011-{new Date().getFullYear()} {appInfo.title} Inc.
|
|
12
|
+
<br />
|
|
13
|
+
All trademarks or registered trademarks are property of their
|
|
14
|
+
respective owners.
|
|
15
|
+
</Footer>
|
|
16
|
+
</SideNavBarLayout>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const navigation = [<%=^empty%>
|
|
2
|
+
{
|
|
3
|
+
text: 'Home',
|
|
4
|
+
path: '/pages/home',
|
|
5
|
+
icon: 'home'
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
text: 'Examples',
|
|
9
|
+
icon: 'folder',
|
|
10
|
+
items: [
|
|
11
|
+
{
|
|
12
|
+
text: 'Profile',
|
|
13
|
+
path: '/pages/profile'
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
text: 'Tasks',
|
|
17
|
+
path: '/pages/tasks'
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|
|
21
|
+
<%=/empty%>];
|
package/src/templates/nextjs/application/src/components/change-password-form/ChangePasswordForm.tsx
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import <%=#isTypeScript%>React, <%=/isTypeScript%>{ useState, useRef, useCallback } from 'react';
|
|
3
|
+
import { useRouter } from 'next/navigation';
|
|
4
|
+
import Form, {
|
|
5
|
+
Item,
|
|
6
|
+
Label,
|
|
7
|
+
ButtonItem,
|
|
8
|
+
ButtonOptions,
|
|
9
|
+
RequiredRule,
|
|
10
|
+
CustomRule,
|
|
11
|
+
} from 'devextreme-react/form';
|
|
12
|
+
import LoadIndicator from 'devextreme-react/load-indicator';
|
|
13
|
+
import notify from 'devextreme/ui/notify';
|
|
14
|
+
<%=#isTypeScript%>import { ValidationCallbackData } from 'devextreme-react/common';<%=/isTypeScript%>
|
|
15
|
+
import { changePassword } from '@/app/actions/auth';
|
|
16
|
+
|
|
17
|
+
export default function ChangePasswordForm() {
|
|
18
|
+
const router = useRouter();
|
|
19
|
+
const [loading, setLoading] = useState(false);
|
|
20
|
+
const formData = useRef({ password: '' });
|
|
21
|
+
|
|
22
|
+
const onSubmit = useCallback(async (e<%=#isTypeScript%>: React.FormEvent<HTMLFormElement><%=/isTypeScript%>) => {
|
|
23
|
+
e.preventDefault();
|
|
24
|
+
const { password } = formData.current;
|
|
25
|
+
setLoading(true);
|
|
26
|
+
|
|
27
|
+
const result = await changePassword(password);
|
|
28
|
+
setLoading(false);
|
|
29
|
+
|
|
30
|
+
if (result.isOk) {
|
|
31
|
+
router.push('/login');
|
|
32
|
+
} else {
|
|
33
|
+
notify(result.message, 'error', 2000);
|
|
34
|
+
}
|
|
35
|
+
}, [router]);
|
|
36
|
+
|
|
37
|
+
const confirmPassword = useCallback(
|
|
38
|
+
({ value }<%=#isTypeScript%>: ValidationCallbackData<%=/isTypeScript%>) => value === formData.current.password,
|
|
39
|
+
[]
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<form onSubmit={onSubmit}>
|
|
44
|
+
<Form formData={formData.current} disabled={loading}>
|
|
45
|
+
<Item
|
|
46
|
+
dataField={'password'}
|
|
47
|
+
editorType={'dxTextBox'}
|
|
48
|
+
editorOptions={passwordEditorOptions}
|
|
49
|
+
>
|
|
50
|
+
<RequiredRule message="Password is required" />
|
|
51
|
+
<Label visible={false} />
|
|
52
|
+
</Item>
|
|
53
|
+
<Item
|
|
54
|
+
dataField={'confirmedPassword'}
|
|
55
|
+
editorType={'dxTextBox'}
|
|
56
|
+
editorOptions={confirmedPasswordEditorOptions}
|
|
57
|
+
>
|
|
58
|
+
<RequiredRule message="Password is required" />
|
|
59
|
+
<CustomRule
|
|
60
|
+
message={'Passwords do not match'}
|
|
61
|
+
validationCallback={confirmPassword}
|
|
62
|
+
/>
|
|
63
|
+
<Label visible={false} />
|
|
64
|
+
</Item>
|
|
65
|
+
<ButtonItem>
|
|
66
|
+
<ButtonOptions
|
|
67
|
+
width={'100%'}
|
|
68
|
+
type={'default'}
|
|
69
|
+
useSubmitBehavior={true}
|
|
70
|
+
>
|
|
71
|
+
<span className="dx-button-text">
|
|
72
|
+
{
|
|
73
|
+
loading
|
|
74
|
+
? <LoadIndicator width={'24px'} height={'24px'} visible={true} />
|
|
75
|
+
: 'Continue'
|
|
76
|
+
}
|
|
77
|
+
</span>
|
|
78
|
+
</ButtonOptions>
|
|
79
|
+
</ButtonItem>
|
|
80
|
+
</Form>
|
|
81
|
+
</form>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const passwordEditorOptions = { stylingMode: 'filled', placeholder: 'Password', mode: 'password' };
|
|
86
|
+
const confirmedPasswordEditorOptions = { stylingMode: 'filled', placeholder: 'Confirm Password', mode: 'password' };
|
package/src/templates/nextjs/application/src/components/create-account-form/CreateAccountForm.scss
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
.create-account-form {
|
|
2
|
+
.policy-info {
|
|
3
|
+
color: var(--base-text-color-alpha-7);
|
|
4
|
+
font-size: 12px;
|
|
5
|
+
font-style: normal;
|
|
6
|
+
|
|
7
|
+
a {
|
|
8
|
+
color: var(--base-text-color-alpha-7);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.login-link {
|
|
13
|
+
color: var(--base-accent);
|
|
14
|
+
font-size: 12px;
|
|
15
|
+
text-align: center;
|
|
16
|
+
padding: 6px 0 32px 0;
|
|
17
|
+
border-bottom: 1px solid var(--border-color);
|
|
18
|
+
}
|
|
19
|
+
}
|
package/src/templates/nextjs/application/src/components/create-account-form/CreateAccountForm.tsx
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
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
|
+
CustomRule,
|
|
12
|
+
EmailRule
|
|
13
|
+
} from 'devextreme-react/form';
|
|
14
|
+
import notify from 'devextreme/ui/notify';
|
|
15
|
+
import LoadIndicator from 'devextreme-react/load-indicator';
|
|
16
|
+
import { signUp } from '@/app/actions/auth';
|
|
17
|
+
<%=#isTypeScript%>import { ValidationCallbackData } from 'devextreme-react/common';<%=/isTypeScript%>
|
|
18
|
+
import './CreateAccountForm.scss';
|
|
19
|
+
|
|
20
|
+
export default function CreateAccountForm() {
|
|
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 signUp(email, password);
|
|
31
|
+
setLoading(false);
|
|
32
|
+
|
|
33
|
+
if (result.isOk) {
|
|
34
|
+
router.push('/login');
|
|
35
|
+
} else {
|
|
36
|
+
notify(result.message, 'error', 2000);
|
|
37
|
+
}
|
|
38
|
+
}, [router]);
|
|
39
|
+
|
|
40
|
+
const confirmPassword = useCallback(
|
|
41
|
+
({ value }<%=#isTypeScript%>: ValidationCallbackData<%=/isTypeScript%>) => value === formData.current.password,
|
|
42
|
+
[]
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<form className={'create-account-form'} onSubmit={onSubmit}>
|
|
47
|
+
<Form formData={formData.current} disabled={loading}>
|
|
48
|
+
<Item
|
|
49
|
+
dataField={'email'}
|
|
50
|
+
editorType={'dxTextBox'}
|
|
51
|
+
editorOptions={emailEditorOptions}
|
|
52
|
+
>
|
|
53
|
+
<RequiredRule message="Email is required" />
|
|
54
|
+
<EmailRule message="Email is invalid" />
|
|
55
|
+
<Label visible={false} />
|
|
56
|
+
</Item>
|
|
57
|
+
<Item
|
|
58
|
+
dataField={'password'}
|
|
59
|
+
editorType={'dxTextBox'}
|
|
60
|
+
editorOptions={passwordEditorOptions}
|
|
61
|
+
>
|
|
62
|
+
<RequiredRule message="Password is required" />
|
|
63
|
+
<Label visible={false} />
|
|
64
|
+
</Item>
|
|
65
|
+
<Item
|
|
66
|
+
dataField={'confirmedPassword'}
|
|
67
|
+
editorType={'dxTextBox'}
|
|
68
|
+
editorOptions={confirmedPasswordEditorOptions}
|
|
69
|
+
>
|
|
70
|
+
<RequiredRule message="Password is required" />
|
|
71
|
+
<CustomRule
|
|
72
|
+
message={'Passwords do not match'}
|
|
73
|
+
validationCallback={confirmPassword}
|
|
74
|
+
/>
|
|
75
|
+
<Label visible={false} />
|
|
76
|
+
</Item>
|
|
77
|
+
<Item>
|
|
78
|
+
<div className='policy-info'>
|
|
79
|
+
By creating an account, you agree to the <Link href="#">Terms of Service</Link> and <Link href="#">Privacy Policy</Link>
|
|
80
|
+
</div>
|
|
81
|
+
</Item>
|
|
82
|
+
<ButtonItem>
|
|
83
|
+
<ButtonOptions
|
|
84
|
+
width={'100%'}
|
|
85
|
+
type={'default'}
|
|
86
|
+
useSubmitBehavior={true}
|
|
87
|
+
>
|
|
88
|
+
<span className="dx-button-text">
|
|
89
|
+
{
|
|
90
|
+
loading
|
|
91
|
+
? <LoadIndicator width={'24px'} height={'24px'} visible={true} />
|
|
92
|
+
: 'Create a new account'
|
|
93
|
+
}
|
|
94
|
+
</span>
|
|
95
|
+
</ButtonOptions>
|
|
96
|
+
</ButtonItem>
|
|
97
|
+
</Form>
|
|
98
|
+
<div className={'login-link'}>
|
|
99
|
+
Have an account? <Link href={'/login'}>Sign In</Link>
|
|
100
|
+
</div>
|
|
101
|
+
</form>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const emailEditorOptions = { stylingMode: 'filled', placeholder: 'Email', mode: 'email' };
|
|
106
|
+
const passwordEditorOptions = { stylingMode: 'filled', placeholder: 'Password', mode: 'password' };
|
|
107
|
+
const confirmedPasswordEditorOptions = { stylingMode: 'filled', placeholder: 'Confirm Password', mode: 'password' };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
@use "../../dx-styles.scss" as *;
|
|
2
|
+
|
|
3
|
+
header {
|
|
4
|
+
background-color: var(--base-bg);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.header-component {
|
|
8
|
+
flex: 0 0 auto;
|
|
9
|
+
z-index: 1;
|
|
10
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.dx-toolbar.header-toolbar .dx-toolbar-items-container .dx-toolbar-after {
|
|
14
|
+
padding: 0 40px;
|
|
15
|
+
|
|
16
|
+
@media (max-width: 599.99px) {
|
|
17
|
+
padding: 0 20px;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.dx-toolbar .dx-toolbar-item.dx-toolbar-button.menu-button {
|
|
22
|
+
width: $side-panel-min-width;
|
|
23
|
+
text-align: center;
|
|
24
|
+
padding: 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.header-title .dx-item-content {
|
|
28
|
+
padding: 0;
|
|
29
|
+
margin: 0;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.dx-theme-generic {
|
|
33
|
+
.header-toolbar {
|
|
34
|
+
padding: 10px 0;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.user-button>.dx-button-content {
|
|
38
|
+
padding: 3px;
|
|
39
|
+
}
|
|
40
|
+
}
|