diff --git a/.github/workflows/applications.yml b/.github/workflows/applications.yml
index a80546e63..aab90cad8 100644
--- a/.github/workflows/applications.yml
+++ b/.github/workflows/applications.yml
@@ -5,9 +5,9 @@ on:
pull_request:
workflow_call:
inputs:
- external-tools-version:
- description: 'Version of external tools'
- default: latest
+ deps-version-tag:
+ description: 'Version tag of scaffolder and framework'
+ default: ''
required: false
type: string
@@ -21,16 +21,19 @@ jobs:
- vue-v3
- react
- react-ts
+ - react-swc
+ - react-swc-ts
+ - nextjs
+ - nextjs-ts
NODE:
- 18
OS:
- ubuntu-latest
- - windows-latest
runs-on: ${{ matrix.OS }}
env:
VUE_CLI_CONFIG_PATH: $GITHUB_WORKSPACE/.vuerc
- TOOLING_VERSION_PARAM: ${{ inputs.external-tools-version != '' && format('--tooling-version {0}', inputs.external-tools-version) || '' }}
+ DEPS_VERSION_TAG_PARAM: ${{ inputs.deps-version-tag != '' && format('--deps-version-tag {0}', inputs.deps-version-tag) || '' }}
name: ${{ matrix.APPROACH }}, node ${{ matrix.NODE }}, ${{ matrix.OS }}
@@ -52,7 +55,7 @@ jobs:
run: npm config set legacy-peer-deps true
- name: Create ${{ matrix.APPROACH }} application
- run: npm run create-template -- -- -e ${{ matrix.APPROACH }} ${{ env.TOOLING_VERSION_PARAM }}
+ run: npm run create-template -- -- -e ${{ matrix.APPROACH }} ${{ env.DEPS_VERSION_TAG_PARAM }}
timeout-minutes: 30
- name: Install Internal Packages
@@ -62,14 +65,14 @@ jobs:
- name: Lint created application
if: ${{ matrix.OS != 'windows-latest' }}
- run: npm run lint-template -- -- -e ${{ matrix.APPROACH }} ${{ env.TOOLING_VERSION_PARAM }}
+ run: npm run lint-template -- -- -e ${{ matrix.APPROACH }} ${{ env.DEPS_VERSION_TAG_PARAM }}
- name: Run template tests
if: ${{ matrix.OS != 'windows-latest' }}
env:
LAUNCH_BROWSER: true
CHROME_PATH: ${{ steps.setup-chrome.outputs.chrome-path }}
- run: npm run test-template -- -- -e ${{ matrix.APPROACH }} ${{ env.TOOLING_VERSION_PARAM }}
+ run: npm run test-template -- -- -e ${{ matrix.APPROACH }} ${{ env.DEPS_VERSION_TAG_PARAM }}
timeout-minutes: 40
- name: Archive artifacts
diff --git a/.github/workflows/check-nextjs.yml b/.github/workflows/check-nextjs.yml
new file mode 100644
index 000000000..f3c5071ee
--- /dev/null
+++ b/.github/workflows/check-nextjs.yml
@@ -0,0 +1,94 @@
+name: Check "add devextreme-react" for NextJS app
+
+on:
+ push:
+ pull_request:
+ workflow_dispatch:
+
+jobs:
+ nextjs-devextreme-test:
+ strategy:
+ fail-fast: false
+ matrix:
+ TYPESCRIPT: [true, false]
+ SRC_DIR: [true, false]
+ APP_ROUTER: [true, false]
+ NODE:
+ - 18
+ OS:
+ - ubuntu-latest
+
+ runs-on: ${{ matrix.OS }}
+ name: Next.js + DevExtreme (TS:${{ matrix.TYPESCRIPT }}, src:${{ matrix.SRC_DIR }}, app-router:${{ matrix.APP_ROUTER }}), node ${{ matrix.NODE }}, ${{ matrix.OS }}
+
+ steps:
+ - name: Get sources
+ uses: actions/checkout@v4
+
+ - name: Set up Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: ${{ matrix.NODE }}
+ cache: 'npm'
+
+ - name: Extract create-next-app version
+ run: |
+ NEXT_APP_VERSION=$(node -e "const versions = require('./packages/devextreme-cli/src/utility/latest-versions.js'); console.log(versions['create-next-app'])")
+ echo "Using create-next-app version: $NEXT_APP_VERSION"
+ echo "NEXT_APP_VERSION=$NEXT_APP_VERSION" >> $GITHUB_ENV
+ shell: bash
+
+ - name: Create Next.js application
+ run: |
+ npx create-next-app@${{ env.NEXT_APP_VERSION }} test-nextjs-app \
+ --typescript=${{ matrix.TYPESCRIPT }} \
+ --src-dir=${{ matrix.SRC_DIR }} \
+ --app=${{ matrix.APP_ROUTER }} \
+ --eslint \
+ --no-tailwind \
+ --import-alias="@/*" \
+ --no-git \
+ --use-npm
+ shell: bash
+
+ - name: Add actual devExtreme-cli
+ run: |
+ cd test-nextjs-app
+ npm add devextreme-cli
+ rm -r ./node_modules/devextreme-cli/src/
+ cp -r ../packages/devextreme-cli/src/ ./node_modules/devextreme-cli/
+ ls ./node_modules/devextreme-cli
+ ls ./node_modules/devextreme-cli/src
+ shell: bash
+ timeout-minutes: 15
+
+ - name: Add DevExtreme to Next.js application
+ run: |
+ cd test-nextjs-app
+ npx devextreme-cli add devextreme-react
+ shell: bash
+ timeout-minutes: 15
+
+ - name: Verify DevExtreme dependencies in package.json
+ run: |
+ cd test-nextjs-app
+
+ if ! grep -q '"devextreme":' package.json; then
+ echo "Error: devextreme dependency not found in package.json"
+ exit 1
+ fi
+
+ if ! grep -q '"devextreme-react":' package.json; then
+ echo "Error: devextreme-react dependency not found in package.json"
+ exit 1
+ fi
+
+ echo "DevExtreme dependencies successfully installed"
+ shell: bash
+
+ - name: Build Next.js application
+ run: |
+ cd test-nextjs-app
+ npm run build
+ shell: bash
+ timeout-minutes: 15
diff --git a/.github/workflows/schedule-cli-tests-latest-version.yml b/.github/workflows/schedule-cli-tests-latest-version.yml
index 45e3ce573..7fad5e04e 100644
--- a/.github/workflows/schedule-cli-tests-latest-version.yml
+++ b/.github/workflows/schedule-cli-tests-latest-version.yml
@@ -10,7 +10,7 @@ jobs:
name: Run CLI tests
uses: ./.github/workflows/applications.yml
with:
- external-tools-version: latest
+ deps-version-tag: latest
teams_notification_on_failure:
name: Send Teams notification
diff --git a/.github/workflows/schedule-cli-tests-next-version.yml b/.github/workflows/schedule-cli-tests-next-version.yml
index 8b2cf28f8..e8ba0d7d0 100644
--- a/.github/workflows/schedule-cli-tests-next-version.yml
+++ b/.github/workflows/schedule-cli-tests-next-version.yml
@@ -2,15 +2,15 @@ name: Schedule tests for next versions of external CLIs
on:
workflow_dispatch:
- schedule:
- - cron: "0 1 * * 1"
+ # schedule:
+ # - cron: "0 1 * * 1"
jobs:
call_tests_workflow:
name: Run CLI tests
uses: ./.github/workflows/applications.yml
with:
- external-tools-version: next
+ deps-version-tag: next
teams_notification_on_failure:
name: Send Teams notification
diff --git a/package-lock.json b/package-lock.json
index 172d932c7..02118673f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -15,62 +15,6 @@
"puppeteer": "^21.11.0"
}
},
- "node_modules/@angular-devkit/core": {
- "version": "16.1.2",
- "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-16.1.2.tgz",
- "integrity": "sha512-RGSkcu03Zybg7drenKxBHvQ/xF8LLZR7o09S4vtLVDwMTGW/ZHnZ+YSC0OuRddbtBlBRhjmsQN6HPvXXfwGLkQ==",
- "dev": true,
- "peer": true,
- "dependencies": {
- "ajv": "8.12.0",
- "ajv-formats": "2.1.1",
- "jsonc-parser": "3.2.0",
- "rxjs": "7.8.1",
- "source-map": "0.7.4"
- },
- "engines": {
- "node": "^16.14.0 || >=18.10.0",
- "npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
- "yarn": ">= 1.13.0"
- },
- "peerDependencies": {
- "chokidar": "^3.5.2"
- },
- "peerDependenciesMeta": {
- "chokidar": {
- "optional": true
- }
- }
- },
- "node_modules/@angular-devkit/core/node_modules/source-map": {
- "version": "0.7.4",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
- "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
- "dev": true,
- "peer": true,
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/@angular-devkit/schematics": {
- "version": "16.1.2",
- "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-16.1.2.tgz",
- "integrity": "sha512-5CRHrurFwJh7IPpo56DbIjcqzI1usraYwjHKZFeXoW4sQTRP5yeSuJoBM9zuBX5ZFQVCRt24j5DQTlsGk7Ky8Q==",
- "dev": true,
- "peer": true,
- "dependencies": {
- "@angular-devkit/core": "16.1.2",
- "jsonc-parser": "3.2.0",
- "magic-string": "0.30.0",
- "ora": "5.4.1",
- "rxjs": "7.8.1"
- },
- "engines": {
- "node": "^16.14.0 || >=18.10.0",
- "npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
- "yarn": ">= 1.13.0"
- }
- },
"node_modules/@angular-eslint/schematics": {
"version": "0.2.0-beta.1",
"resolved": "https://registry.npmjs.org/@angular-eslint/schematics/-/schematics-0.2.0-beta.1.tgz",
@@ -201,13 +145,6 @@
"integrity": "sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==",
"dev": true
},
- "node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.4.15",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
- "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
- "dev": true,
- "peer": true
- },
"node_modules/@lerna/add": {
"version": "5.6.2",
"resolved": "https://registry.npmjs.org/@lerna/add/-/add-5.6.2.tgz",
@@ -1955,41 +1892,6 @@
"node": ">=8"
}
},
- "node_modules/ajv": {
- "version": "8.12.0",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
- "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
- "dev": true,
- "peer": true,
- "dependencies": {
- "fast-deep-equal": "^3.1.1",
- "json-schema-traverse": "^1.0.0",
- "require-from-string": "^2.0.2",
- "uri-js": "^4.2.2"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/epoberezkin"
- }
- },
- "node_modules/ajv-formats": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
- "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
- "dev": true,
- "peer": true,
- "dependencies": {
- "ajv": "^8.0.0"
- },
- "peerDependencies": {
- "ajv": "^8.0.0"
- },
- "peerDependenciesMeta": {
- "ajv": {
- "optional": true
- }
- }
- },
"node_modules/ansi-colors": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
@@ -3348,13 +3250,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/fast-deep-equal": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
- "dev": true,
- "peer": true
- },
"node_modules/fast-fifo": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
@@ -4539,13 +4434,6 @@
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
"dev": true
},
- "node_modules/json-schema-traverse": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
- "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
- "dev": true,
- "peer": true
- },
"node_modules/json-stringify-nice": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/json-stringify-nice/-/json-stringify-nice-1.1.4.tgz",
@@ -4853,19 +4741,6 @@
"node": ">=12"
}
},
- "node_modules/magic-string": {
- "version": "0.30.0",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.0.tgz",
- "integrity": "sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==",
- "dev": true,
- "peer": true,
- "dependencies": {
- "@jridgewell/sourcemap-codec": "^1.4.13"
- },
- "engines": {
- "node": ">=12"
- }
- },
"node_modules/make-dir": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
@@ -6708,16 +6583,6 @@
"once": "^1.3.1"
}
},
- "node_modules/punycode": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
- "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==",
- "dev": true,
- "peer": true,
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/puppeteer": {
"version": "21.11.0",
"resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-21.11.0.tgz",
@@ -6779,21 +6644,6 @@
}
}
},
- "node_modules/puppeteer/node_modules/typescript": {
- "version": "5.3.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
- "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==",
- "dev": true,
- "optional": true,
- "peer": true,
- "bin": {
- "tsc": "bin/tsc",
- "tsserver": "bin/tsserver"
- },
- "engines": {
- "node": ">=14.17"
- }
- },
"node_modules/q": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
@@ -7163,16 +7013,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/require-from-string": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
- "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
- "dev": true,
- "peer": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/resolve": {
"version": "1.22.1",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
@@ -8034,16 +7874,6 @@
"yarn": "*"
}
},
- "node_modules/uri-js": {
- "version": "4.4.1",
- "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
- "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
- "dev": true,
- "peer": true,
- "dependencies": {
- "punycode": "^2.1.0"
- }
- },
"node_modules/urlpattern-polyfill": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz",
@@ -8427,43 +8257,6 @@
}
},
"dependencies": {
- "@angular-devkit/core": {
- "version": "16.1.2",
- "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-16.1.2.tgz",
- "integrity": "sha512-RGSkcu03Zybg7drenKxBHvQ/xF8LLZR7o09S4vtLVDwMTGW/ZHnZ+YSC0OuRddbtBlBRhjmsQN6HPvXXfwGLkQ==",
- "dev": true,
- "peer": true,
- "requires": {
- "ajv": "8.12.0",
- "ajv-formats": "2.1.1",
- "jsonc-parser": "3.2.0",
- "rxjs": "7.8.1",
- "source-map": "0.7.4"
- },
- "dependencies": {
- "source-map": {
- "version": "0.7.4",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
- "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
- "dev": true,
- "peer": true
- }
- }
- },
- "@angular-devkit/schematics": {
- "version": "16.1.2",
- "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-16.1.2.tgz",
- "integrity": "sha512-5CRHrurFwJh7IPpo56DbIjcqzI1usraYwjHKZFeXoW4sQTRP5yeSuJoBM9zuBX5ZFQVCRt24j5DQTlsGk7Ky8Q==",
- "dev": true,
- "peer": true,
- "requires": {
- "@angular-devkit/core": "16.1.2",
- "jsonc-parser": "3.2.0",
- "magic-string": "0.30.0",
- "ora": "5.4.1",
- "rxjs": "7.8.1"
- }
- },
"@angular-eslint/schematics": {
"version": "0.2.0-beta.1",
"resolved": "https://registry.npmjs.org/@angular-eslint/schematics/-/schematics-0.2.0-beta.1.tgz",
@@ -8569,13 +8362,6 @@
"integrity": "sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==",
"dev": true
},
- "@jridgewell/sourcemap-codec": {
- "version": "1.4.15",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
- "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
- "dev": true,
- "peer": true
- },
"@lerna/add": {
"version": "5.6.2",
"resolved": "https://registry.npmjs.org/@lerna/add/-/add-5.6.2.tgz",
@@ -9725,8 +9511,7 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz",
"integrity": "sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==",
- "dev": true,
- "requires": {}
+ "dev": true
},
"@octokit/plugin-rest-endpoint-methods": {
"version": "6.7.0",
@@ -10002,29 +9787,6 @@
"indent-string": "^4.0.0"
}
},
- "ajv": {
- "version": "8.12.0",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
- "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
- "dev": true,
- "peer": true,
- "requires": {
- "fast-deep-equal": "^3.1.1",
- "json-schema-traverse": "^1.0.0",
- "require-from-string": "^2.0.2",
- "uri-js": "^4.2.2"
- }
- },
- "ajv-formats": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
- "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
- "dev": true,
- "peer": true,
- "requires": {
- "ajv": "^8.0.0"
- }
- },
"ansi-colors": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
@@ -11056,13 +10818,6 @@
}
}
},
- "fast-deep-equal": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
- "dev": true,
- "peer": true
- },
"fast-fifo": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
@@ -11978,13 +11733,6 @@
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
"dev": true
},
- "json-schema-traverse": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
- "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
- "dev": true,
- "peer": true
- },
"json-stringify-nice": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/json-stringify-nice/-/json-stringify-nice-1.1.4.tgz",
@@ -12233,16 +11981,6 @@
"integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==",
"dev": true
},
- "magic-string": {
- "version": "0.30.0",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.0.tgz",
- "integrity": "sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==",
- "dev": true,
- "peer": true,
- "requires": {
- "@jridgewell/sourcemap-codec": "^1.4.13"
- }
- },
"make-dir": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
@@ -13661,13 +13399,6 @@
"once": "^1.3.1"
}
},
- "punycode": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
- "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==",
- "dev": true,
- "peer": true
- },
"puppeteer": {
"version": "21.11.0",
"resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-21.11.0.tgz",
@@ -13690,14 +13421,6 @@
"js-yaml": "^4.1.0",
"parse-json": "^5.2.0"
}
- },
- "typescript": {
- "version": "5.3.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
- "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==",
- "dev": true,
- "optional": true,
- "peer": true
}
}
},
@@ -13994,13 +13717,6 @@
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
"dev": true
},
- "require-from-string": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
- "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
- "dev": true,
- "peer": true
- },
"resolve": {
"version": "1.22.1",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
@@ -14660,16 +14376,6 @@
"integrity": "sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==",
"dev": true
},
- "uri-js": {
- "version": "4.4.1",
- "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
- "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
- "dev": true,
- "peer": true,
- "requires": {
- "punycode": "^2.1.0"
- }
- },
"urlpattern-polyfill": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz",
@@ -14914,8 +14620,7 @@
"version": "8.16.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz",
"integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==",
- "dev": true,
- "requires": {}
+ "dev": true
},
"xtend": {
"version": "4.0.2",
diff --git a/packages/devextreme-cli/package-lock.json b/packages/devextreme-cli/package-lock.json
index 7f35170dd..f4043a38f 100644
--- a/packages/devextreme-cli/package-lock.json
+++ b/packages/devextreme-cli/package-lock.json
@@ -26,6 +26,7 @@
"@typescript-eslint/eslint-plugin": "^4.33.0",
"@typescript-eslint/parser": "^4.33.0",
"babel-eslint": "^10.1.0",
+ "create-vite": "6.3.1",
"cross-env": "^5.2.1",
"eslint": "^7.32.0",
"eslint-config-angular": "^0.5.0",
@@ -45,7 +46,8 @@
"tree-kill": "^1.2.2",
"tree-kill-promise": "^1.0.12",
"typescript": "^4.0.2",
- "typescript-eslint-parser": "^22.0.0"
+ "typescript-eslint-parser": "^22.0.0",
+ "wait-on": "8.0.0"
},
"engines": {
"node": ">12.6.0",
@@ -641,6 +643,23 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/@hapi/hoek": {
+ "version": "9.3.0",
+ "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz",
+ "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@hapi/topo": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz",
+ "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/hoek": "^9.0.0"
+ }
+ },
"node_modules/@humanwhocodes/config-array": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz",
@@ -1702,6 +1721,30 @@
"url": "https://opencollective.com/parcel"
}
},
+ "node_modules/@sideway/address": {
+ "version": "4.1.5",
+ "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz",
+ "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/hoek": "^9.0.0"
+ }
+ },
+ "node_modules/@sideway/formula": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz",
+ "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@sideway/pinpoint": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz",
+ "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
"node_modules/@sinclair/typebox": {
"version": "0.27.8",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
@@ -2264,6 +2307,13 @@
"node": ">=8"
}
},
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/available-typed-arrays": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
@@ -2280,6 +2330,18 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/axios": {
+ "version": "1.8.4",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz",
+ "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "follow-redirects": "^1.15.6",
+ "form-data": "^4.0.0",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
"node_modules/babel-eslint": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz",
@@ -2763,6 +2825,19 @@
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
"dev": true
},
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -2877,6 +2952,22 @@
"node": ">=8"
}
},
+ "node_modules/create-vite": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/create-vite/-/create-vite-6.3.1.tgz",
+ "integrity": "sha512-kI3S6OgiBYnRlmhr4gNY76Fj62fMh+KukEd2PSAvMPdnY95rHhJCjCoUlt4BuHVNjfmRMbsFBBtO5dryFoSyUw==",
+ "dev": true,
+ "bin": {
+ "create-vite": "index.js",
+ "cva": "index.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ }
+ },
"node_modules/cross-env": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-5.2.1.tgz",
@@ -3044,6 +3135,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
"node_modules/detect-libc": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
@@ -3295,15 +3396,16 @@
}
},
"node_modules/es-set-tostringtag": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz",
- "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==",
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "get-intrinsic": "^1.2.4",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
"has-tostringtag": "^1.0.2",
- "hasown": "^2.0.1"
+ "hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
@@ -4338,6 +4440,27 @@
"integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==",
"dev": true
},
+ "node_modules/follow-redirects": {
+ "version": "1.15.9",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
+ "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
"node_modules/for-each": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
@@ -4348,6 +4471,22 @@
"is-callable": "^1.1.3"
}
},
+ "node_modules/form-data": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
+ "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -7056,6 +7195,20 @@
"url": "https://github.com/chalk/supports-color?sponsor=1"
}
},
+ "node_modules/joi": {
+ "version": "17.13.3",
+ "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz",
+ "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/hoek": "^9.3.0",
+ "@hapi/topo": "^5.1.0",
+ "@sideway/address": "^4.1.5",
+ "@sideway/formula": "^3.0.1",
+ "@sideway/pinpoint": "^2.0.0"
+ }
+ },
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -7352,6 +7505,29 @@
"node": ">=4"
}
},
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/mimic-fn": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
@@ -7922,6 +8098,13 @@
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"dev": true
},
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/prr": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
@@ -8165,6 +8348,23 @@
"queue-microtask": "^1.2.2"
}
},
+ "node_modules/rxjs": {
+ "version": "7.8.2",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
+ "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/rxjs/node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "dev": true,
+ "license": "0BSD"
+ },
"node_modules/safe-array-concat": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz",
@@ -9148,6 +9348,26 @@
"semver": "bin/semver.js"
}
},
+ "node_modules/wait-on": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-8.0.0.tgz",
+ "integrity": "sha512-fNE5SXinLr2Bt7cJvjvLg2PcXfqznlqRvtE3f8AqYdRZ9BhE+XpsCp1mwQbRoO7s1q7uhAuCw0Ro3mG/KdZjEw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "axios": "^1.7.4",
+ "joi": "^17.13.3",
+ "lodash": "^4.17.21",
+ "minimist": "^1.2.8",
+ "rxjs": "^7.8.1"
+ },
+ "bin": {
+ "wait-on": "bin/wait-on"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
"node_modules/walker": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
@@ -9831,6 +10051,21 @@
}
}
},
+ "@hapi/hoek": {
+ "version": "9.3.0",
+ "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz",
+ "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==",
+ "dev": true
+ },
+ "@hapi/topo": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz",
+ "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==",
+ "dev": true,
+ "requires": {
+ "@hapi/hoek": "^9.0.0"
+ }
+ },
"@humanwhocodes/config-array": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz",
@@ -10518,6 +10753,27 @@
"integrity": "sha512-lPrxve92zEHdgeff3aiu4gDOIt4u7sJYha6wbdEZDCDUhtjTsOMiaJzG5lMY4GkWH8p0fMmO2Ppq5G5XXG+DQw==",
"optional": true
},
+ "@sideway/address": {
+ "version": "4.1.5",
+ "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz",
+ "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==",
+ "dev": true,
+ "requires": {
+ "@hapi/hoek": "^9.0.0"
+ }
+ },
+ "@sideway/formula": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz",
+ "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==",
+ "dev": true
+ },
+ "@sideway/pinpoint": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz",
+ "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==",
+ "dev": true
+ },
"@sinclair/typebox": {
"version": "0.27.8",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
@@ -10769,8 +11025,7 @@
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
"integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
- "dev": true,
- "requires": {}
+ "dev": true
},
"ajv": {
"version": "6.12.6",
@@ -10923,6 +11178,12 @@
"integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==",
"dev": true
},
+ "asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "dev": true
+ },
"available-typed-arrays": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
@@ -10932,6 +11193,17 @@
"possible-typed-array-names": "^1.0.0"
}
},
+ "axios": {
+ "version": "1.8.4",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz",
+ "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==",
+ "dev": true,
+ "requires": {
+ "follow-redirects": "^1.15.6",
+ "form-data": "^4.0.0",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
"babel-eslint": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz",
@@ -11273,6 +11545,15 @@
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
"dev": true
},
+ "combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "dev": true,
+ "requires": {
+ "delayed-stream": "~1.0.0"
+ }
+ },
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -11359,6 +11640,12 @@
}
}
},
+ "create-vite": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/create-vite/-/create-vite-6.3.1.tgz",
+ "integrity": "sha512-kI3S6OgiBYnRlmhr4gNY76Fj62fMh+KukEd2PSAvMPdnY95rHhJCjCoUlt4BuHVNjfmRMbsFBBtO5dryFoSyUw==",
+ "dev": true
+ },
"cross-env": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-5.2.1.tgz",
@@ -11427,8 +11714,7 @@
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz",
"integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==",
- "dev": true,
- "requires": {}
+ "dev": true
},
"deep-is": {
"version": "0.1.4",
@@ -11464,6 +11750,12 @@
"object-keys": "^1.1.1"
}
},
+ "delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "dev": true
+ },
"detect-libc": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
@@ -11657,14 +11949,15 @@
}
},
"es-set-tostringtag": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz",
- "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==",
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"dev": true,
"requires": {
- "get-intrinsic": "^1.2.4",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
"has-tostringtag": "^1.0.2",
- "hasown": "^2.0.1"
+ "hasown": "^2.0.2"
}
},
"es-shim-unscopables": {
@@ -11911,8 +12204,7 @@
"version": "8.10.0",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz",
"integrity": "sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg==",
- "dev": true,
- "requires": {}
+ "dev": true
},
"eslint-plugin-angular": {
"version": "4.1.0",
@@ -12041,8 +12333,7 @@
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz",
"integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==",
- "dev": true,
- "requires": {}
+ "dev": true
},
"eslint-plugin-unused-imports": {
"version": "1.1.5",
@@ -12406,6 +12697,12 @@
"integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==",
"dev": true
},
+ "follow-redirects": {
+ "version": "1.15.9",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
+ "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
+ "dev": true
+ },
"for-each": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
@@ -12415,6 +12712,18 @@
"is-callable": "^1.1.3"
}
},
+ "form-data": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
+ "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
+ "dev": true,
+ "requires": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "mime-types": "^2.1.12"
+ }
+ },
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -13753,8 +14062,7 @@
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz",
"integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==",
- "dev": true,
- "requires": {}
+ "dev": true
},
"jest-regex-util": {
"version": "29.6.3",
@@ -14321,6 +14629,19 @@
}
}
},
+ "joi": {
+ "version": "17.13.3",
+ "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz",
+ "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==",
+ "dev": true,
+ "requires": {
+ "@hapi/hoek": "^9.3.0",
+ "@hapi/topo": "^5.1.0",
+ "@sideway/address": "^4.1.5",
+ "@sideway/formula": "^3.0.1",
+ "@sideway/pinpoint": "^2.0.0"
+ }
+ },
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -14546,6 +14867,21 @@
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
"optional": true
},
+ "mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "dev": true
+ },
+ "mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "dev": true,
+ "requires": {
+ "mime-db": "1.52.0"
+ }
+ },
"mimic-fn": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
@@ -14957,6 +15293,12 @@
}
}
},
+ "proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+ "dev": true
+ },
"prr": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
@@ -15102,6 +15444,23 @@
"queue-microtask": "^1.2.2"
}
},
+ "rxjs": {
+ "version": "7.8.2",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
+ "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
+ "dev": true,
+ "requires": {
+ "tslib": "^2.1.0"
+ },
+ "dependencies": {
+ "tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "dev": true
+ }
+ }
+ },
"safe-array-concat": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz",
@@ -15795,6 +16154,19 @@
}
}
},
+ "wait-on": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-8.0.0.tgz",
+ "integrity": "sha512-fNE5SXinLr2Bt7cJvjvLg2PcXfqznlqRvtE3f8AqYdRZ9BhE+XpsCp1mwQbRoO7s1q7uhAuCw0Ro3mG/KdZjEw==",
+ "dev": true,
+ "requires": {
+ "axios": "^1.7.4",
+ "joi": "^17.13.3",
+ "lodash": "^4.17.21",
+ "minimist": "^1.2.8",
+ "rxjs": "^7.8.1"
+ }
+ },
"walker": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
diff --git a/packages/devextreme-cli/package.json b/packages/devextreme-cli/package.json
index e0965ac5f..2433b20bf 100644
--- a/packages/devextreme-cli/package.json
+++ b/packages/devextreme-cli/package.json
@@ -52,6 +52,7 @@
"@typescript-eslint/parser": "^4.33.0",
"babel-eslint": "^10.1.0",
"cross-env": "^5.2.1",
+ "create-vite": "6.3.1",
"eslint": "^7.32.0",
"eslint-config-angular": "^0.5.0",
"eslint-config-prettier": "^8.10.0",
@@ -70,6 +71,7 @@
"tree-kill": "^1.2.2",
"tree-kill-promise": "^1.0.12",
"typescript": "^4.0.2",
- "typescript-eslint-parser": "^22.0.0"
+ "typescript-eslint-parser": "^22.0.0",
+ "wait-on": "8.0.0"
}
}
diff --git a/packages/devextreme-cli/src/application.js b/packages/devextreme-cli/src/application.js
index 35a64a81d..a5a65228b 100644
--- a/packages/devextreme-cli/src/application.js
+++ b/packages/devextreme-cli/src/application.js
@@ -1,12 +1,34 @@
const angularApplication = require('./applications/application.angular');
const reactApplication = require('./applications/application.react');
+const nextjsApplication = require('./applications/application.nextjs');
const vueApplication = require('./applications/application.vue');
+const getReactAppType = require('./utility/prompts/react-app-type');
const printHelp = require('./help').printHelp;
const isApplicationCommand = (command) => {
return [ 'new', 'add' ].includes(command);
};
+const handleWrongAppType = (appType, command) => {
+ console.error(`The '${appType}' application type is not valid`);
+ printHelp(command);
+};
+
+const createReact = async(appName, options, command) => {
+ const reactAppType = await getReactAppType(options['app-type']);
+
+ switch(reactAppType) {
+ case 'vite':
+ await reactApplication.create(appName, options);
+ return;
+ case 'nextjs':
+ await nextjsApplication.create(appName, options);
+ return;
+ default:
+ handleWrongAppType(reactAppType, command);
+ }
+};
+
const run = async(commands, options, devextremeConfig) => {
if(!commands[1]) {
console.error('Command is incomplete. Please specify parameters.');
@@ -23,15 +45,15 @@ const run = async(commands, options, devextremeConfig) => {
await angularApplication.create(appName, options);
return;
case 'react-app':
- await reactApplication.create(appName, options);
+ await createReact(appName, options, commands[0]);
return;
case 'vue-app':
await vueApplication.create(appName, options);
return;
default:
- console.error(`The '${app}' application type is not valid`);
- printHelp(commands[0]);
+ handleWrongAppType(app, commands[0]);
}
+
} else {
if(commands[0] === 'add') {
if(commands[1] === 'devextreme-angular') {
@@ -40,7 +62,12 @@ const run = async(commands, options, devextremeConfig) => {
}
if(commands[1] === 'devextreme-react') {
- reactApplication.install(options);
+ if(nextjsApplication.isNextJsApp()) {
+ nextjsApplication.install(options);
+ } else {
+ reactApplication.install(options);
+ }
+
return;
}
@@ -54,23 +81,16 @@ const run = async(commands, options, devextremeConfig) => {
return;
}
- if(devextremeConfig.applicationEngine === 'angular') {
- if(commands[1] === 'view') {
- angularApplication.addView(commands[2], options);
- } else {
- console.error('Invalid command');
- printHelp(commands[0]);
- }
- } else if(devextremeConfig.applicationEngine === 'react') {
- if(commands[1] === 'view') {
- reactApplication.addView(commands[2], options);
- } else {
- console.error('Invalid command');
- printHelp(commands[0]);
- }
- } else if(devextremeConfig.applicationEngine === 'vue') {
+ const app = {
+ 'angular': angularApplication,
+ 'react': reactApplication,
+ 'nextjs': nextjsApplication,
+ 'vue': vueApplication,
+ }[devextremeConfig.applicationEngine];
+
+ if(app) {
if(commands[1] === 'view') {
- vueApplication.addView(commands[2], options);
+ app.addView(commands[2], options);
} else {
console.error('Invalid command');
printHelp(commands[0]);
diff --git a/packages/devextreme-cli/src/applications/application.angular.js b/packages/devextreme-cli/src/applications/application.angular.js
index c164f8c97..222feab71 100644
--- a/packages/devextreme-cli/src/applications/application.angular.js
+++ b/packages/devextreme-cli/src/applications/application.angular.js
@@ -7,10 +7,13 @@ const fs = require('fs');
const dasherize = require('../utility/string').dasherize;
const ngVersion = require('../utility/ng-version');
const latestVersions = require('../utility/latest-versions');
-const { extractToolingVersion, toolingVersionOptionName } = require('../utility/extract-tooling-version');
+const { extractDepsVersionTag, depsVersionTagOptionName } = require('../utility/extract-deps-version-tag');
+const { getPackageJsonPath } = require('../utility/package-json-utils');
+const modifyJson = require('../utility/modify-json-file');
const schematicsVersion = latestVersions['devextreme-schematics'] || 'latest';
const minNgCliVersion = new semver('17.0.0');
+const ngCliWithZoneless = new semver('20.0.0');
async function runSchematicCommand(schematicCommand, options, evaluatingOptions) {
const collectionName = 'devextreme-schematics';
@@ -27,7 +30,7 @@ async function runSchematicCommand(schematicCommand, options, evaluatingOptions)
const commandArguments = ['g', `${collectionName}:${schematicCommand}`];
- const { [toolingVersionOptionName]: _, ...optionsToArguments } = options; // eslint-disable-line no-unused-vars
+ const { [depsVersionTagOptionName]: _, ...optionsToArguments } = options; // eslint-disable-line no-unused-vars
for(let option in optionsToArguments) {
commandArguments.push(`--${dasherize(option)}=${options[option]}`);
}
@@ -37,12 +40,13 @@ async function runSchematicCommand(schematicCommand, options, evaluatingOptions)
async function runNgCommand(commandArguments, commandOptions, commandConfig) {
const hasNg = await hasSutableNgCli();
- const toolingVersion = extractToolingVersion(commandOptions);
- const npmCommandName = hasNg && !toolingVersion ? 'ng' : 'npx';
+ const depsVersionTag = extractDepsVersionTag(commandOptions);
+ const npmCommandName = hasNg && !depsVersionTag ? 'ng' : 'npx';
const [minCliLtsVersion] = minNgCliVersion.version.split('.');
- const ngCommandArguments = hasNg && !toolingVersion
+
+ const ngCommandArguments = hasNg && !depsVersionTag
? []
- : ['-p', `@angular/cli@v${minCliLtsVersion}-lts`, 'ng'];
+ : ['-p', `@angular/cli@${depsVersionTag || `v${minCliLtsVersion}-lts`}`, 'ng'];
ngCommandArguments.push(...commandArguments);
return runCommand(npmCommandName, ngCommandArguments, commandConfig);
@@ -60,6 +64,7 @@ function localPackageExists(packageName) {
const hasSutableNgCli = async() => {
const localVersion = ngVersion.getLocalNgVersion();
+
if(!localVersion) {
return false;
}
@@ -74,8 +79,28 @@ const install = async(options) => {
});
};
+const bumpAngular = (appPath, versionTag) => {
+ modifyJson(getPackageJsonPath(appPath), ({ dependencies, devDependencies, ...rest }) => {
+ const bump = (section) => {
+ for(const depName in section) {
+ section[depName] = depName.startsWith('@angular') ? versionTag : section[depName];
+ }
+ return section;
+ };
+
+ return {
+ dependencies: bump(dependencies),
+ devDependencies: bump(devDependencies),
+ ...rest,
+ };
+ });
+
+};
+
const create = async(appName, options) => {
const layout = await getLayoutInfo(options.layout);
+ const depsVersionTag = extractDepsVersionTag(options);
+ const currentNgVersion = ngVersion.getNgCliVersion().version;
const commandArguments = [
'new',
@@ -84,14 +109,22 @@ const create = async(appName, options) => {
'--routing=false',
'--skip-tests=true',
'--skip-install=true',
- '--standalone=false',
+ '--standalone=true',
'--ssr=false'
];
+ if(ngCliWithZoneless.compare(currentNgVersion) <= 0) {
+ commandArguments.push('--zoneless=false');
+ }
+
await runNgCommand(commandArguments, options);
const appPath = path.join(process.cwd(), appName);
+ if(depsVersionTag) {
+ bumpAngular(appPath, depsVersionTag);
+ }
+
options.resolveConflicts = 'override';
options.updateBudgets = true;
options.layout = layout;
@@ -125,9 +158,9 @@ const changeMainTs = (appPath) => {
moduleWorker.insertImport(filePath, 'devextreme/ui/themes', 'themes', true);
const fileContent = fs.readFileSync(filePath).toString();
- const bootstrapPattern = /platformBrowserDynamic\(\)\.bootstrapModule\(\s*AppModule\s*(?:,\s*\{[^}]*\})?\s*\)/;
+ const bootstrapPattern = /bootstrapApplication\([^)]+\)/;
const firstChaptStr = fileContent.match(bootstrapPattern)[0];
- const lastChaptStr = '.catch(err => console.error(err));';
+ const lastChaptStr = '.catch((err) => console.error(err));';
fs.writeFileSync(
filePath,
diff --git a/packages/devextreme-cli/src/applications/application.nextjs.js b/packages/devextreme-cli/src/applications/application.nextjs.js
new file mode 100644
index 000000000..de7b9fd63
--- /dev/null
+++ b/packages/devextreme-cli/src/applications/application.nextjs.js
@@ -0,0 +1,231 @@
+const runCommand = require('../utility/run-command');
+const path = require('path');
+const fs = require('fs');
+const getLayoutInfo = require('../utility/prompts/layout');
+const getTemplateTypeInfo = require('../utility/prompts/typescript');
+const templateCreator = require('../utility/template-creator');
+const packageManager = require('../utility/package-manager');
+const packageJsonUtils = require('../utility/package-json-utils');
+const insertItemToArray = require('../utility/file-content').insertItemToArray;
+const stringUtils = require('../utility/string');
+const typescriptUtils = require('../utility/typescript-extension');
+const removeFile = require('../utility/file-operations').remove;
+const latestVersions = require('../utility/latest-versions');
+const { extractDepsVersionTag } = require('../utility/extract-deps-version-tag');
+const {
+ updateJsonPropName,
+ bumpReact,
+ getCorrectPath,
+ addStylesToApp,
+ getComponentPageName,
+} = require('./application.react');
+
+const defaultStyles = [
+ 'devextreme/dist/css/dx.light.css'
+];
+
+const isNextJsApp = () => {
+ const appPath = process.cwd();
+
+ return fs.existsSync(path.join(appPath, 'next.config.ts')) || fs.existsSync(path.join(appPath, 'next.config.mjs'));
+};
+
+const isTsApp = (appPath) => {
+ return fs.existsSync(path.join(appPath, 'next.config.ts'));
+};
+
+const getExtension = (appPath) => {
+ return fs.existsSync(path.join(appPath, 'src/app', 'layout.tsx')) ? '.tsx' : '.jsx';
+};
+
+const pathToPagesIndex = () => {
+ const extension = getExtension(process.cwd());
+ return path.join(process.cwd(), 'src', 'views', `index${extension}`);
+};
+
+const preparePackageJsonForTemplate = (appPath, appName) => {
+ const dependencies = [
+ { name: 'devextreme-cli', version: latestVersions['devextreme-cli'], dev: true },
+ { name: 'jose', version: latestVersions['jose'] },
+ ];
+ const scripts = [
+ { name: 'build-themes', value: 'devextreme build' },
+ { name: 'postinstall', value: 'npm run build-themes' }
+ ];
+
+ packageJsonUtils.addDependencies(appPath, dependencies);
+ packageJsonUtils.updateScripts(appPath, scripts);
+ packageJsonUtils.updateName(appPath, appName);
+};
+
+const create = async(appName, options) => {
+ const templateType = await getTemplateTypeInfo(options.template);
+ const layoutType = await getLayoutInfo(options.layout);
+
+ const templateOptions = Object.assign({}, options, {
+ project: stringUtils.humanize(appName),
+ layout: stringUtils.classify(layoutType),
+ isTypeScript: typescriptUtils.isTypeScript(templateType)
+ });
+ const depsVersionTag = extractDepsVersionTag(options);
+
+ let commandArguments = [`-p=create-next-app@${depsVersionTag || latestVersions['create-next-app']}`, 'create-next-app', appName];
+
+ commandArguments = [
+ ...commandArguments,
+ `${templateOptions.isTypeScript ? '--typescript' : '--javascript'}`,
+ '--eslint',
+ '--no-tailwind',
+ '--src-dir',
+ '--app',
+ '--no-turbopack',
+ '--import-alias "@/*"',
+ ];
+
+ await runCommand('npx', commandArguments);
+
+ const appPath = path.join(process.cwd(), appName);
+
+ if(depsVersionTag) {
+ bumpReact(appPath, depsVersionTag, templateOptions.isTypeScript);
+ }
+
+ addTemplate(appPath, appName, templateOptions);
+ modifyAppFiles(appPath, templateOptions);
+};
+
+const modifyAppFiles = (appPath, { project, isTypeScript }) => {
+ const entryFilePath = path.join(appPath, `src/app/layout.${isTypeScript ? 'tsx' : 'jsx'}`);
+
+ let content = fs.readFileSync(entryFilePath).toString();
+ content = content.replace(/
[^<]+<\/title>/, `${project}<\/title>`);
+
+ fs.writeFileSync(entryFilePath, content);
+};
+
+const addTemplate = (appPath, appName, templateOptions) => {
+ const applicationTemplatePath = path.join(
+ templateCreator.getTempaltePath('nextjs'),
+ 'application'
+ );
+
+ const manifestPath = path.join(appPath, 'public', 'manifest.json');
+
+ const styles = [
+ '../dx-styles.scss',
+ '../themes/generated/theme.additional.css',
+ '../themes/generated/theme.additional.dark.css',
+ '../themes/generated/theme.base.css',
+ '../themes/generated/theme.base.dark.css',
+ 'devextreme/dist/css/dx.common.css'
+ ];
+
+ templateCreator.moveTemplateFilesToProject(applicationTemplatePath, appPath, templateOptions, getCorrectPath);
+
+ !templateOptions.isTypeScript && removeFile(path.join(appPath, 'src', 'types.jsx'));
+ removeFile(path.join(appPath, 'src/app', 'page.js'));
+ removeFile(path.join(appPath, 'src/app', 'layout.js'));
+ removeFile(path.join(appPath, 'src/app', 'globals.scss'));
+
+ if(!templateOptions.empty) {
+ addSamplePages(appPath, templateOptions);
+ }
+
+ preparePackageJsonForTemplate(appPath, appName, templateOptions.isTypeScript);
+ updateJsonPropName(manifestPath, appName);
+ install({ isTypeScript: templateOptions.isTypeScript }, appPath, styles);
+};
+
+const getEntryFilePath = (options, appPath) => {
+ const extension = options.isTypeScript || isTsApp(appPath) ? 'ts' : 'js';
+ const srcFolder = fs.existsSync(path.join(appPath, 'src')) ? 'src' : '';
+ const isAppRouterApp = fs.existsSync(path.join(appPath, srcFolder, 'app')) && fs.lstatSync(appPath).isDirectory();
+
+ const entryFilePath = isAppRouterApp
+ ? path.join('app', `layout.${extension}`)
+ : path.join('pages', `_app.${extension}`);
+
+ const jsx = fs.existsSync(path.join(appPath, srcFolder, entryFilePath + 'x')) ? 'x' : '';
+
+ return path.join(srcFolder, entryFilePath + jsx);
+};
+
+const install = (options, appPath, styles) => {
+ appPath = appPath ? appPath : process.cwd();
+
+ const pathToMainComponent = path.join(appPath, getEntryFilePath(options, appPath));
+
+ addStylesToApp(pathToMainComponent, styles || defaultStyles);
+ packageJsonUtils.addDevextreme(appPath, options.dxversion, 'react');
+
+ packageManager.runInstall({ cwd: appPath });
+};
+
+const getNavigationData = (viewName, componentName, icon) => {
+ const pagePath = stringUtils.dasherize(viewName);
+ return {
+ navigation: `\n {\n text: \'${stringUtils.humanize(viewName)}\',\n path: \'/pages/${pagePath}\',\n icon: \'${icon}\'\n }`
+ };
+};
+
+const createPathToPage = (pageName) => {
+ const pagesPath = path.join(process.cwd(), 'src', 'app/pages');
+ const newPageFolderPath = path.join(pagesPath, pageName);
+
+ if(!fs.existsSync(pagesPath)) {
+ fs.mkdirSync(pagesPath);
+ fs.writeFileSync(pathToPagesIndex(), '');
+ }
+
+ if(!fs.existsSync(newPageFolderPath)) {
+ fs.mkdirSync(newPageFolderPath);
+ }
+
+ return newPageFolderPath;
+};
+
+const addSamplePages = (appPath, templateOptions) => {
+ const samplePageTemplatePath = path.join(
+ templateCreator.getTempaltePath('nextjs'),
+ 'sample-pages'
+ );
+
+ const pagesPath = path.join(appPath, 'src', 'app/pages');
+
+ templateCreator.moveTemplateFilesToProject(samplePageTemplatePath, pagesPath, {
+ isTypeScript: templateOptions.isTypeScript
+ }, getCorrectPath);
+};
+
+const addView = (pageName, options) => {
+ const pageTemplatePath = path.join(
+ templateCreator.getTempaltePath('nextjs'),
+ 'page'
+ );
+ const extension = getExtension(process.cwd());
+
+ const componentName = getComponentPageName(pageName);
+ const pathToPage = createPathToPage(pageName);
+ const navigationModulePath = path.join(process.cwd(), 'src', `app-navigation${extension}`);
+ const navigationData = getNavigationData(pageName, componentName, options && options.icon || 'folder');
+
+ const getCorrectExtension = (fileExtension) => {
+ return fileExtension === '.tsx' ? extension : fileExtension;
+ };
+
+ const getPageFileName = (pageName, pageItem) => {
+ return pageItem === 'page.tsx' ? 'page' : pageName;
+ };
+
+ templateCreator.addPageToApp(pageName, pathToPage, pageTemplatePath, getCorrectExtension, { getPageFileName });
+
+ insertItemToArray(navigationModulePath, navigationData.navigation);
+};
+
+module.exports = {
+ isNextJsApp,
+ install,
+ create,
+ addTemplate,
+ addView
+};
diff --git a/packages/devextreme-cli/src/applications/application.react.js b/packages/devextreme-cli/src/applications/application.react.js
index 9be907fa6..1b3f8c36c 100644
--- a/packages/devextreme-cli/src/applications/application.react.js
+++ b/packages/devextreme-cli/src/applications/application.react.js
@@ -3,6 +3,7 @@ const path = require('path');
const fs = require('fs');
const getLayoutInfo = require('../utility/prompts/layout');
const getTemplateTypeInfo = require('../utility/prompts/typescript');
+const getTranspilerTypeInfo = require('../utility/prompts/transpiler');
const templateCreator = require('../utility/template-creator');
const packageManager = require('../utility/package-manager');
const packageJsonUtils = require('../utility/package-json-utils');
@@ -13,13 +14,13 @@ const stringUtils = require('../utility/string');
const typescriptUtils = require('../utility/typescript-extension');
const removeFile = require('../utility/file-operations').remove;
const latestVersions = require('../utility/latest-versions');
-const { extractToolingVersion } = require('../utility/extract-tooling-version');
+const { extractDepsVersionTag } = require('../utility/extract-deps-version-tag');
const defaultStyles = [
'devextreme/dist/css/dx.light.css'
];
const getExtension = (appPath) => {
- return fs.existsSync(path.join(appPath, 'src', 'App.tsx')) ? '.tsx' : '.js';
+ return fs.existsSync(path.join(appPath, 'src', 'App.tsx')) ? '.tsx' : '.jsx';
};
const pathToPagesIndex = () => {
@@ -27,9 +28,9 @@ const pathToPagesIndex = () => {
return path.join(process.cwd(), 'src', 'pages', `index${extension}`);
};
-const preparePackageJsonForTemplate = (appPath, appName, isTypeScript) => {
+const preparePackageJsonForTemplate = (appPath, appName) => {
const dependencies = [
- { name: 'sass', version: '^1.34.1' },
+ { name: 'sass-embedded', version: '^1.85.1' },
{ name: 'devextreme-cli', version: latestVersions['devextreme-cli'], dev: true },
{ name: 'react-router-dom', version: '^6.3.0' },
];
@@ -51,8 +52,25 @@ const updateJsonPropName = (path, name) => {
});
};
+const bumpReact = (appPath, versionTag, isTypeScript) => {
+ const dependencies = [
+ { name: 'react', version: versionTag },
+ { name: 'react-dom', version: versionTag },
+ ];
+
+ if(isTypeScript) {
+ dependencies.push(
+ { name: '@types/react', version: versionTag, dev: true },
+ { name: '@types/react-dom', version: versionTag, dev: true },
+ );
+ }
+
+ packageJsonUtils.addDependencies(appPath, dependencies);
+};
+
const create = async(appName, options) => {
const templateType = await getTemplateTypeInfo(options.template);
+ const transpiler = await getTranspilerTypeInfo(options.transpiler);
const layoutType = await getLayoutInfo(options.layout);
const templateOptions = Object.assign({}, options, {
@@ -60,27 +78,30 @@ const create = async(appName, options) => {
layout: stringUtils.classify(layoutType),
isTypeScript: typescriptUtils.isTypeScript(templateType)
});
- const toolingVersion = extractToolingVersion(options);
- const commandArguments = [`-p=create-react-app${toolingVersion}`, 'create-react-app', appName];
+ const depsVersionTag = extractDepsVersionTag(options);
- const templateSuffix = templateOptions.isTypeScript ? '-typescript' : '';
- const templatePath = path.resolve(__dirname, `../templates/cra-template${templateSuffix}`);
+ const commandArguments = [`-p=create-vite@${depsVersionTag || latestVersions['create-vite']}`, 'create-vite', appName];
- commandArguments.push(`--template file:${templatePath}`);
+ commandArguments.push(`--template react${transpiler === 'swc' ? '-swc' : ''}${templateOptions.isTypeScript ? '-ts' : ''}`);
await runCommand('npx', commandArguments);
const appPath = path.join(process.cwd(), appName);
modifyIndexHtml(appPath, templateOptions.project);
+
+ if(depsVersionTag) {
+ bumpReact(appPath, depsVersionTag, templateOptions.isTypeScript);
+ }
+
addTemplate(appPath, appName, templateOptions);
};
const modifyIndexHtml = (appPath, appName) => {
- const indexHtmlPath = path.join(appPath, 'public', 'index.html');
+ const indexHtmlPath = path.join(appPath, 'index.html');
let htmlContent = fs.readFileSync(indexHtmlPath).toString();
- htmlContent = htmlContent.replace(/(\w+\s*)+<\/title>/, `${appName}<\/title>`);
+ htmlContent = htmlContent.replace(/[^<]+<\/title>/, `${appName}<\/title>`);
htmlContent = htmlContent.replace(' ', '');
fs.writeFileSync(indexHtmlPath, htmlContent);
@@ -97,7 +118,6 @@ const addTemplate = (appPath, appName, templateOptions) => {
);
const manifestPath = path.join(appPath, 'public', 'manifest.json');
- const indexPath = path.join(appPath, 'src', templateOptions.isTypeScript ? 'index.tsx' : 'index.js');
const styles = [
'./themes/generated/theme.additional.css',
@@ -108,15 +128,15 @@ const addTemplate = (appPath, appName, templateOptions) => {
];
templateCreator.moveTemplateFilesToProject(applicationTemplatePath, appPath, templateOptions, getCorrectPath);
- removeFile(path.join(appPath, 'src', 'App.css'));
- !templateOptions.isTypeScript && removeFile(path.join(appPath, 'src', 'types.js'));
+
+ !templateOptions.isTypeScript && removeFile(path.join(appPath, 'src', 'types.jsx'));
+
if(!templateOptions.empty) {
addSamplePages(appPath, templateOptions);
}
preparePackageJsonForTemplate(appPath, appName, templateOptions.isTypeScript);
updateJsonPropName(manifestPath, appName);
- addPolyfills(packageJsonUtils.getPackageJsonPath(), indexPath);
install({}, appPath, styles);
};
@@ -130,15 +150,6 @@ const install = (options, appPath, styles) => {
packageManager.runInstall({ cwd: appPath });
};
-const addPolyfills = (packagePath, indexPath) => {
- const packages = [
- { name: 'react-app-polyfill', version: '^1.0.0' }
- ];
-
- packageJsonUtils.addDependencies(packagePath, packages);
- moduleUtils.insertImport(indexPath, './polyfills');
-};
-
const addStylesToApp = (filePath, styles) => {
styles.forEach(style => {
moduleUtils.insertImport(filePath, style);
@@ -213,5 +224,10 @@ module.exports = {
install,
create,
addTemplate,
- addView
+ addView,
+ updateJsonPropName,
+ bumpReact,
+ getCorrectPath,
+ addStylesToApp,
+ getComponentPageName,
};
diff --git a/packages/devextreme-cli/src/applications/application.vue.js b/packages/devextreme-cli/src/applications/application.vue.js
index 0f4890b0c..9d02f04fa 100644
--- a/packages/devextreme-cli/src/applications/application.vue.js
+++ b/packages/devextreme-cli/src/applications/application.vue.js
@@ -9,7 +9,7 @@ const insertItemToArray = require('../utility/file-content').insertItemToArray;
const moduleUtils = require('../utility/module');
const stringUtils = require('../utility/string');
const latestVersions = require('../utility/latest-versions');
-const { toolingVersionOptionName, extractToolingVersion } = require('../utility/extract-tooling-version');
+const { depsVersionTagOptionName, extractDepsVersionTag } = require('../utility/extract-deps-version-tag');
const defaultStyles = [
'devextreme/dist/css/dx.light.css'
];
@@ -37,25 +37,42 @@ const preparePackageJsonForTemplate = (appPath, appName) => {
packageJsonUtils.updateName(appPath, appName);
};
-async function createVueApp(name, templateOptions) {
- const toolingVersion = extractToolingVersion(templateOptions);
- const argList = ['-p', `@vue/cli${toolingVersion}`, 'vue', 'create', name, '--registry', 'https://registry.npmjs.org/', '-p "Default (Vue 3)"'];
+async function createVueApp(name, depsVersionTag) {
+ const argList = ['-p', `@vue/cli@${depsVersionTag}`, 'vue', 'create', name, '--registry', 'https://registry.npmjs.org/', '-p "Default (Vue 3)"'];
return runCommand('npx', argList);
}
+const bumpVue = (appPath, versionTag) => {
+ const dependencies = [
+ { name: 'vue', version: versionTag },
+ { name: 'vue-router', version: versionTag },
+ { name: '@vue/cli-plugin-babel', version: versionTag, dev: true },
+ { name: '@vue/cli-plugin-eslint', version: versionTag, dev: true },
+ { name: '@vue/cli-service', version: versionTag, dev: true },
+ ];
+
+ packageJsonUtils.addDependencies(appPath, dependencies);
+};
+
const create = async(appName, options) => {
const layout = await getLayoutInfo(options.layout);
+ const depsVersionTag = extractDepsVersionTag(options);
const templateOptions = {
project: stringUtils.humanize(appName),
layout: layout,
- [toolingVersionOptionName]: options[toolingVersionOptionName]
+ [depsVersionTagOptionName]: options[depsVersionTagOptionName]
};
- await createVueApp(appName, templateOptions);
+ await createVueApp(appName, depsVersionTag);
const appPath = path.join(process.cwd(), appName);
+
+ if(depsVersionTag) {
+ bumpVue(appPath, depsVersionTag);
+ }
+
modifyIndexHtml(appPath, templateOptions.project);
addTemplate(appPath, appName, templateOptions);
};
diff --git a/packages/devextreme-cli/src/templates/cra-template-typescript/LICENSE b/packages/devextreme-cli/src/templates/cra-template-typescript/LICENSE
deleted file mode 100644
index 5930f2b8d..000000000
--- a/packages/devextreme-cli/src/templates/cra-template-typescript/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-MIT License
-
-Copyright (c) 2013-present, Facebook, Inc.
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
\ No newline at end of file
diff --git a/packages/devextreme-cli/src/templates/cra-template-typescript/README.md b/packages/devextreme-cli/src/templates/cra-template-typescript/README.md
deleted file mode 100644
index e7197568d..000000000
--- a/packages/devextreme-cli/src/templates/cra-template-typescript/README.md
+++ /dev/null
@@ -1,20 +0,0 @@
-# cra-template-typescript
-
-This is the official TypeScript template for [Create React App](https://github.com/facebook/create-react-app).
-
-To use this template, add `--template typescript` when creating a new app.
-
-For example:
-
-```sh
-npx create-react-app my-app --template typescript
-
-# or
-
-yarn create react-app my-app --template typescript
-```
-
-For more information, please refer to:
-
-- [Getting Started](https://create-react-app.dev/docs/getting-started) – How to create a new app.
-- [User Guide](https://create-react-app.dev) – How to develop apps bootstrapped with Create React App.
diff --git a/packages/devextreme-cli/src/templates/cra-template-typescript/package.json b/packages/devextreme-cli/src/templates/cra-template-typescript/package.json
deleted file mode 100644
index 88de656d7..000000000
--- a/packages/devextreme-cli/src/templates/cra-template-typescript/package.json
+++ /dev/null
@@ -1,27 +0,0 @@
-{
- "name": "cra-template-typescript",
- "version": "1.2.0",
- "keywords": [
- "react",
- "create-react-app",
- "template",
- "typescript"
- ],
- "description": "The base TypeScript template for Create React App.",
- "repository": {
- "type": "git",
- "url": "https://github.com/facebook/create-react-app.git",
- "directory": "packages/cra-template-typescript"
- },
- "license": "MIT",
- "engines": {
- "node": ">=14.21.3"
- },
- "bugs": {
- "url": "https://github.com/facebook/create-react-app/issues"
- },
- "files": [
- "template",
- "template.json"
- ]
-}
diff --git a/packages/devextreme-cli/src/templates/cra-template-typescript/template.json b/packages/devextreme-cli/src/templates/cra-template-typescript/template.json
deleted file mode 100644
index 3678a922d..000000000
--- a/packages/devextreme-cli/src/templates/cra-template-typescript/template.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "package": {
- "dependencies": {
- "@testing-library/jest-dom": "^6.6.3",
- "@testing-library/react": "^16.2.0",
- "@testing-library/dom": "^10.4.0",
- "@testing-library/user-event": "^14.6.0",
- "@types/jest": "^29.5.14",
- "@types/node": "^22.0.0",
- "@types/react": "^19.0.0",
- "@types/react-dom": "^19.0.0",
- "typescript": "^4.0.0",
- "web-vitals": "^4.2.4"
- },
- "eslintConfig": {
- "extends": ["react-app", "react-app/jest"]
- }
- }
-}
diff --git a/packages/devextreme-cli/src/templates/cra-template-typescript/template/README.md b/packages/devextreme-cli/src/templates/cra-template-typescript/template/README.md
deleted file mode 100644
index b87cb0044..000000000
--- a/packages/devextreme-cli/src/templates/cra-template-typescript/template/README.md
+++ /dev/null
@@ -1,46 +0,0 @@
-# Getting Started with Create React App
-
-This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
-
-## Available Scripts
-
-In the project directory, you can run:
-
-### `npm start`
-
-Runs the app in the development mode.\
-Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
-
-The page will reload if you make edits.\
-You will also see any lint errors in the console.
-
-### `npm test`
-
-Launches the test runner in the interactive watch mode.\
-See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
-
-### `npm run build`
-
-Builds the app for production to the `build` folder.\
-It correctly bundles React in production mode and optimizes the build for the best performance.
-
-The build is minified and the filenames include the hashes.\
-Your app is ready to be deployed!
-
-See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
-
-### `npm run eject`
-
-**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
-
-If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
-
-Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
-
-You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
-
-## Learn More
-
-You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
-
-To learn React, check out the [React documentation](https://reactjs.org/).
diff --git a/packages/devextreme-cli/src/templates/cra-template-typescript/template/gitignore b/packages/devextreme-cli/src/templates/cra-template-typescript/template/gitignore
deleted file mode 100644
index 4d29575de..000000000
--- a/packages/devextreme-cli/src/templates/cra-template-typescript/template/gitignore
+++ /dev/null
@@ -1,23 +0,0 @@
-# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
-
-# dependencies
-/node_modules
-/.pnp
-.pnp.js
-
-# testing
-/coverage
-
-# production
-/build
-
-# misc
-.DS_Store
-.env.local
-.env.development.local
-.env.test.local
-.env.production.local
-
-npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
diff --git a/packages/devextreme-cli/src/templates/cra-template-typescript/template/public/index.html b/packages/devextreme-cli/src/templates/cra-template-typescript/template/public/index.html
deleted file mode 100644
index aa069f27c..000000000
--- a/packages/devextreme-cli/src/templates/cra-template-typescript/template/public/index.html
+++ /dev/null
@@ -1,43 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
- React App
-
-
- You need to enable JavaScript to run this app.
-
-
-
-
diff --git a/packages/devextreme-cli/src/templates/cra-template-typescript/template/src/App.css b/packages/devextreme-cli/src/templates/cra-template-typescript/template/src/App.css
deleted file mode 100644
index 74b5e0534..000000000
--- a/packages/devextreme-cli/src/templates/cra-template-typescript/template/src/App.css
+++ /dev/null
@@ -1,38 +0,0 @@
-.App {
- text-align: center;
-}
-
-.App-logo {
- height: 40vmin;
- pointer-events: none;
-}
-
-@media (prefers-reduced-motion: no-preference) {
- .App-logo {
- animation: App-logo-spin infinite 20s linear;
- }
-}
-
-.App-header {
- background-color: #282c34;
- min-height: 100vh;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- font-size: calc(10px + 2vmin);
- color: white;
-}
-
-.App-link {
- color: #61dafb;
-}
-
-@keyframes App-logo-spin {
- from {
- transform: rotate(0deg);
- }
- to {
- transform: rotate(360deg);
- }
-}
diff --git a/packages/devextreme-cli/src/templates/cra-template-typescript/template/src/App.test.tsx b/packages/devextreme-cli/src/templates/cra-template-typescript/template/src/App.test.tsx
deleted file mode 100644
index f0db7d3d4..000000000
--- a/packages/devextreme-cli/src/templates/cra-template-typescript/template/src/App.test.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import React from 'react';
-import { render } from '@testing-library/react';
-import { screen } from '@testing-library/dom';
-import App from './App';
-
-test('renders learn react link', () => {
- render( );
- const linkElement = screen.getByText(/learn react/i);
- expect(linkElement).toBeInTheDocument();
-});
diff --git a/packages/devextreme-cli/src/templates/cra-template-typescript/template/src/App.tsx b/packages/devextreme-cli/src/templates/cra-template-typescript/template/src/App.tsx
deleted file mode 100644
index a53698aab..000000000
--- a/packages/devextreme-cli/src/templates/cra-template-typescript/template/src/App.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import React from 'react';
-import logo from './logo.svg';
-import './App.css';
-
-function App() {
- return (
-
- );
-}
-
-export default App;
diff --git a/packages/devextreme-cli/src/templates/cra-template-typescript/template/src/index.tsx b/packages/devextreme-cli/src/templates/cra-template-typescript/template/src/index.tsx
deleted file mode 100644
index 032464fb6..000000000
--- a/packages/devextreme-cli/src/templates/cra-template-typescript/template/src/index.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import React from 'react';
-import ReactDOM from 'react-dom/client';
-import './index.css';
-import App from './App';
-import reportWebVitals from './reportWebVitals';
-
-const root = ReactDOM.createRoot(
- document.getElementById('root') as HTMLElement
-);
-root.render(
-
-
-
-);
-
-// If you want to start measuring performance in your app, pass a function
-// to log results (for example: reportWebVitals(console.log))
-// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
-reportWebVitals();
diff --git a/packages/devextreme-cli/src/templates/cra-template-typescript/template/src/logo.svg b/packages/devextreme-cli/src/templates/cra-template-typescript/template/src/logo.svg
deleted file mode 100644
index 9dfc1c058..000000000
--- a/packages/devextreme-cli/src/templates/cra-template-typescript/template/src/logo.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/packages/devextreme-cli/src/templates/cra-template-typescript/template/src/reportWebVitals.ts b/packages/devextreme-cli/src/templates/cra-template-typescript/template/src/reportWebVitals.ts
deleted file mode 100644
index 1e8febd4a..000000000
--- a/packages/devextreme-cli/src/templates/cra-template-typescript/template/src/reportWebVitals.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-const reportWebVitals = (onPerfEntry?: () => any) => {
- if (onPerfEntry && onPerfEntry instanceof Function) {
- import('web-vitals').then(({ onLCP, onINP, onCLS, onFCP, onTTFB }) => {
- onCLS(onPerfEntry);
- onINP(onPerfEntry);
- onLCP(onPerfEntry);
- onFCP(onPerfEntry);
- onTTFB(onPerfEntry);
- });
- }
-};
-
-export default reportWebVitals;
diff --git a/packages/devextreme-cli/src/templates/cra-template-typescript/template/src/setupTests.ts b/packages/devextreme-cli/src/templates/cra-template-typescript/template/src/setupTests.ts
deleted file mode 100644
index 8f2609b7b..000000000
--- a/packages/devextreme-cli/src/templates/cra-template-typescript/template/src/setupTests.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-// jest-dom adds custom jest matchers for asserting on DOM nodes.
-// allows you to do things like:
-// expect(element).toHaveTextContent(/react/i)
-// learn more: https://github.com/testing-library/jest-dom
-import '@testing-library/jest-dom';
diff --git a/packages/devextreme-cli/src/templates/cra-template/LICENSE b/packages/devextreme-cli/src/templates/cra-template/LICENSE
deleted file mode 100644
index 5930f2b8d..000000000
--- a/packages/devextreme-cli/src/templates/cra-template/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-MIT License
-
-Copyright (c) 2013-present, Facebook, Inc.
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
\ No newline at end of file
diff --git a/packages/devextreme-cli/src/templates/cra-template/README.md b/packages/devextreme-cli/src/templates/cra-template/README.md
deleted file mode 100644
index 9e8434b78..000000000
--- a/packages/devextreme-cli/src/templates/cra-template/README.md
+++ /dev/null
@@ -1,10 +0,0 @@
-# cra-template
-
-This is the official base template for [Create React App](https://github.com/facebook/create-react-app).
-
-If you don't specify a template (for example, `--template typescript`), this template will be used by default.
-
-For more information, please refer to:
-
-- [Getting Started](https://create-react-app.dev/docs/getting-started) – How to create a new app.
-- [User Guide](https://create-react-app.dev) – How to develop apps bootstrapped with Create React App.
diff --git a/packages/devextreme-cli/src/templates/cra-template/package.json b/packages/devextreme-cli/src/templates/cra-template/package.json
deleted file mode 100644
index 53350728e..000000000
--- a/packages/devextreme-cli/src/templates/cra-template/package.json
+++ /dev/null
@@ -1,26 +0,0 @@
-{
- "name": "cra-template",
- "version": "1.2.0",
- "keywords": [
- "react",
- "create-react-app",
- "template"
- ],
- "description": "The base template for Create React App.",
- "repository": {
- "type": "git",
- "url": "https://github.com/facebook/create-react-app.git",
- "directory": "packages/cra-template"
- },
- "license": "MIT",
- "engines": {
- "node": ">=14.21.3"
- },
- "bugs": {
- "url": "https://github.com/facebook/create-react-app/issues"
- },
- "files": [
- "template",
- "template.json"
- ]
-}
diff --git a/packages/devextreme-cli/src/templates/cra-template/template.json b/packages/devextreme-cli/src/templates/cra-template/template.json
deleted file mode 100644
index 65327dcb1..000000000
--- a/packages/devextreme-cli/src/templates/cra-template/template.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "package": {
- "dependencies": {
- "@testing-library/jest-dom": "^6.6.3",
- "@testing-library/react": "^16.2.0",
- "@testing-library/dom": "^10.4.0",
- "@testing-library/user-event": "^14.6.0",
- "web-vitals": "^4.2.4"
- },
- "eslintConfig": {
- "extends": ["react-app", "react-app/jest"]
- }
- }
-}
diff --git a/packages/devextreme-cli/src/templates/cra-template/template/README.md b/packages/devextreme-cli/src/templates/cra-template/template/README.md
deleted file mode 100644
index 58beeaccd..000000000
--- a/packages/devextreme-cli/src/templates/cra-template/template/README.md
+++ /dev/null
@@ -1,70 +0,0 @@
-# Getting Started with Create React App
-
-This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
-
-## Available Scripts
-
-In the project directory, you can run:
-
-### `npm start`
-
-Runs the app in the development mode.\
-Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
-
-The page will reload when you make changes.\
-You may also see any lint errors in the console.
-
-### `npm test`
-
-Launches the test runner in the interactive watch mode.\
-See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
-
-### `npm run build`
-
-Builds the app for production to the `build` folder.\
-It correctly bundles React in production mode and optimizes the build for the best performance.
-
-The build is minified and the filenames include the hashes.\
-Your app is ready to be deployed!
-
-See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
-
-### `npm run eject`
-
-**Note: this is a one-way operation. Once you `eject`, you can't go back!**
-
-If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
-
-Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
-
-You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
-
-## Learn More
-
-You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
-
-To learn React, check out the [React documentation](https://reactjs.org/).
-
-### Code Splitting
-
-This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
-
-### Analyzing the Bundle Size
-
-This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
-
-### Making a Progressive Web App
-
-This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
-
-### Advanced Configuration
-
-This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
-
-### Deployment
-
-This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
-
-### `npm run build` fails to minify
-
-This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
diff --git a/packages/devextreme-cli/src/templates/cra-template/template/gitignore b/packages/devextreme-cli/src/templates/cra-template/template/gitignore
deleted file mode 100644
index 4d29575de..000000000
--- a/packages/devextreme-cli/src/templates/cra-template/template/gitignore
+++ /dev/null
@@ -1,23 +0,0 @@
-# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
-
-# dependencies
-/node_modules
-/.pnp
-.pnp.js
-
-# testing
-/coverage
-
-# production
-/build
-
-# misc
-.DS_Store
-.env.local
-.env.development.local
-.env.test.local
-.env.production.local
-
-npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
diff --git a/packages/devextreme-cli/src/templates/cra-template/template/public/favicon.ico b/packages/devextreme-cli/src/templates/cra-template/template/public/favicon.ico
deleted file mode 100644
index a11777cc4..000000000
Binary files a/packages/devextreme-cli/src/templates/cra-template/template/public/favicon.ico and /dev/null differ
diff --git a/packages/devextreme-cli/src/templates/cra-template/template/public/index.html b/packages/devextreme-cli/src/templates/cra-template/template/public/index.html
deleted file mode 100644
index aa069f27c..000000000
--- a/packages/devextreme-cli/src/templates/cra-template/template/public/index.html
+++ /dev/null
@@ -1,43 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
- React App
-
-
- You need to enable JavaScript to run this app.
-
-
-
-
diff --git a/packages/devextreme-cli/src/templates/cra-template/template/src/App.css b/packages/devextreme-cli/src/templates/cra-template/template/src/App.css
deleted file mode 100644
index 74b5e0534..000000000
--- a/packages/devextreme-cli/src/templates/cra-template/template/src/App.css
+++ /dev/null
@@ -1,38 +0,0 @@
-.App {
- text-align: center;
-}
-
-.App-logo {
- height: 40vmin;
- pointer-events: none;
-}
-
-@media (prefers-reduced-motion: no-preference) {
- .App-logo {
- animation: App-logo-spin infinite 20s linear;
- }
-}
-
-.App-header {
- background-color: #282c34;
- min-height: 100vh;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- font-size: calc(10px + 2vmin);
- color: white;
-}
-
-.App-link {
- color: #61dafb;
-}
-
-@keyframes App-logo-spin {
- from {
- transform: rotate(0deg);
- }
- to {
- transform: rotate(360deg);
- }
-}
diff --git a/packages/devextreme-cli/src/templates/cra-template/template/src/App.js b/packages/devextreme-cli/src/templates/cra-template/template/src/App.js
deleted file mode 100644
index 378457572..000000000
--- a/packages/devextreme-cli/src/templates/cra-template/template/src/App.js
+++ /dev/null
@@ -1,25 +0,0 @@
-import logo from './logo.svg';
-import './App.css';
-
-function App() {
- return (
-
- );
-}
-
-export default App;
diff --git a/packages/devextreme-cli/src/templates/cra-template/template/src/App.test.js b/packages/devextreme-cli/src/templates/cra-template/template/src/App.test.js
deleted file mode 100644
index 23b8367e5..000000000
--- a/packages/devextreme-cli/src/templates/cra-template/template/src/App.test.js
+++ /dev/null
@@ -1,9 +0,0 @@
-import { render } from '@testing-library/react';
-import { screen } from '@testing-library/dom';
-import App from './App';
-
-test('renders learn react link', () => {
- render( );
- const linkElement = screen.getByText(/learn react/i);
- expect(linkElement).toBeInTheDocument();
-});
diff --git a/packages/devextreme-cli/src/templates/cra-template/template/src/index.js b/packages/devextreme-cli/src/templates/cra-template/template/src/index.js
deleted file mode 100644
index d563c0fb1..000000000
--- a/packages/devextreme-cli/src/templates/cra-template/template/src/index.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import React from 'react';
-import ReactDOM from 'react-dom/client';
-import './index.css';
-import App from './App';
-import reportWebVitals from './reportWebVitals';
-
-const root = ReactDOM.createRoot(document.getElementById('root'));
-root.render(
-
-
-
-);
-
-// If you want to start measuring performance in your app, pass a function
-// to log results (for example: reportWebVitals(console.log))
-// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
-reportWebVitals();
diff --git a/packages/devextreme-cli/src/templates/cra-template/template/src/logo.svg b/packages/devextreme-cli/src/templates/cra-template/template/src/logo.svg
deleted file mode 100644
index 9dfc1c058..000000000
--- a/packages/devextreme-cli/src/templates/cra-template/template/src/logo.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/packages/devextreme-cli/src/templates/cra-template/template/src/reportWebVitals.js b/packages/devextreme-cli/src/templates/cra-template/template/src/reportWebVitals.js
deleted file mode 100644
index 3c0ccb0dc..000000000
--- a/packages/devextreme-cli/src/templates/cra-template/template/src/reportWebVitals.js
+++ /dev/null
@@ -1,13 +0,0 @@
-const reportWebVitals = onPerfEntry => {
- if (onPerfEntry && onPerfEntry instanceof Function) {
- import('web-vitals').then(({ onLCP, onINP, onCLS, onFCP, onTTFB }) => {
- onCLS(onPerfEntry);
- onINP(onPerfEntry);
- onLCP(onPerfEntry);
- onFCP(onPerfEntry);
- onTTFB(onPerfEntry);
- });
- }
-};
-
-export default reportWebVitals;
diff --git a/packages/devextreme-cli/src/templates/cra-template/template/src/setupTests.js b/packages/devextreme-cli/src/templates/cra-template/template/src/setupTests.js
deleted file mode 100644
index 8f2609b7b..000000000
--- a/packages/devextreme-cli/src/templates/cra-template/template/src/setupTests.js
+++ /dev/null
@@ -1,5 +0,0 @@
-// jest-dom adds custom jest matchers for asserting on DOM nodes.
-// allows you to do things like:
-// expect(element).toHaveTextContent(/react/i)
-// learn more: https://github.com/testing-library/jest-dom
-import '@testing-library/jest-dom';
diff --git a/packages/devextreme-cli/src/templates/nextjs/application/.env b/packages/devextreme-cli/src/templates/nextjs/application/.env
new file mode 100644
index 000000000..1d70c522d
--- /dev/null
+++ b/packages/devextreme-cli/src/templates/nextjs/application/.env
@@ -0,0 +1 @@
+SESSION_SECRET=
\ No newline at end of file
diff --git a/packages/devextreme-cli/src/templates/nextjs/application/devextreme.json b/packages/devextreme-cli/src/templates/nextjs/application/devextreme.json
new file mode 100644
index 000000000..b6bf64dac
--- /dev/null
+++ b/packages/devextreme-cli/src/templates/nextjs/application/devextreme.json
@@ -0,0 +1,63 @@
+{
+ "applicationEngine": "nextjs",
+ "build": {
+ "commands": [
+ {
+ "command": "build-theme",
+ "options": {
+ "inputFile": "src/themes/metadata.base.json",
+ "outputFile": "src/themes/generated/theme.base.css"
+ }
+ },
+ {
+ "command": "build-theme",
+ "options": {
+ "inputFile": "src/themes/metadata.base.dark.json",
+ "outputFile": "src/themes/generated/theme.base.dark.css"
+ }
+ },
+ {
+ "command": "build-theme",
+ "options": {
+ "inputFile": "src/themes/metadata.additional.json",
+ "outputFile": "src/themes/generated/theme.additional.css"
+ }
+ },
+ {
+ "command": "build-theme",
+ "options": {
+ "inputFile": "src/themes/metadata.additional.dark.json",
+ "outputFile": "src/themes/generated/theme.additional.dark.css"
+ }
+ },
+ {
+ "command": "export-theme-vars",
+ "options": {
+ "inputFile": "src/themes/metadata.base.json",
+ "outputFile": "src/themes/generated/variables.base.scss"
+ }
+ },
+ {
+ "command": "export-theme-vars",
+ "options": {
+ "inputFile": "src/themes/metadata.base.dark.json",
+ "outputFile": "src/themes/generated/variables.base.dark.scss"
+ }
+ },
+ {
+ "command": "export-theme-vars",
+ "options": {
+ "inputFile": "src/themes/metadata.additional.json",
+ "outputFile": "src/themes/generated/variables.additional.scss"
+ }
+ },
+ {
+ "command": "export-theme-vars",
+ "options": {
+ "inputFile": "src/themes/metadata.additional.dark.json",
+ "outputFile": "src/themes/generated/variables.additional.dark.scss"
+ }
+ }
+ ]
+ }
+}
diff --git a/packages/devextreme-cli/src/templates/nextjs/application/next.config.mjs b/packages/devextreme-cli/src/templates/nextjs/application/next.config.mjs
new file mode 100644
index 000000000..377a4d1eb
--- /dev/null
+++ b/packages/devextreme-cli/src/templates/nextjs/application/next.config.mjs
@@ -0,0 +1,32 @@
+/** @type {import('next').NextConfig} */
+const nextConfig = {
+ async redirects() {
+ return [
+ {
+ source: '/',
+ destination: '/pages/home',
+ permanent: true,
+ },
+ {
+ source: '/login',
+ destination: '/auth/login',
+ permanent: true,
+ },
+ {
+ source: '/reset-password',
+ destination: '/auth/reset-password',
+ permanent: true,
+ },
+ {
+ source: '/create-account',
+ destination: '/auth/create-account',
+ permanent: true,
+ },
+ ]
+ },
+ images: {
+ remotePatterns: [new URL('https://js.devexpress.com/**')]
+ },
+}
+
+export default nextConfig;
diff --git a/packages/devextreme-cli/src/templates/cra-template-typescript/template/public/logo192.png b/packages/devextreme-cli/src/templates/nextjs/application/public/logo192.png
similarity index 100%
rename from packages/devextreme-cli/src/templates/cra-template-typescript/template/public/logo192.png
rename to packages/devextreme-cli/src/templates/nextjs/application/public/logo192.png
diff --git a/packages/devextreme-cli/src/templates/cra-template-typescript/template/public/logo512.png b/packages/devextreme-cli/src/templates/nextjs/application/public/logo512.png
similarity index 100%
rename from packages/devextreme-cli/src/templates/cra-template-typescript/template/public/logo512.png
rename to packages/devextreme-cli/src/templates/nextjs/application/public/logo512.png
diff --git a/packages/devextreme-cli/src/templates/cra-template-typescript/template/public/manifest.json b/packages/devextreme-cli/src/templates/nextjs/application/public/manifest.json
similarity index 100%
rename from packages/devextreme-cli/src/templates/cra-template-typescript/template/public/manifest.json
rename to packages/devextreme-cli/src/templates/nextjs/application/public/manifest.json
diff --git a/packages/devextreme-cli/src/templates/cra-template-typescript/template/public/robots.txt b/packages/devextreme-cli/src/templates/nextjs/application/public/robots.txt
similarity index 100%
rename from packages/devextreme-cli/src/templates/cra-template-typescript/template/public/robots.txt
rename to packages/devextreme-cli/src/templates/nextjs/application/public/robots.txt
diff --git a/packages/devextreme-cli/src/templates/nextjs/application/src/app-info.tsx b/packages/devextreme-cli/src/templates/nextjs/application/src/app-info.tsx
new file mode 100644
index 000000000..9e15c1476
--- /dev/null
+++ b/packages/devextreme-cli/src/templates/nextjs/application/src/app-info.tsx
@@ -0,0 +1,5 @@
+const appInfo = {
+ title: '<%=project%>'
+};
+export default appInfo;
+
diff --git a/packages/devextreme-cli/src/templates/nextjs/application/src/app-navigation.tsx b/packages/devextreme-cli/src/templates/nextjs/application/src/app-navigation.tsx
new file mode 100644
index 000000000..8e39088af
--- /dev/null
+++ b/packages/devextreme-cli/src/templates/nextjs/application/src/app-navigation.tsx
@@ -0,0 +1,21 @@
+export const navigation = [<%=^empty%>
+ {
+ text: 'Home',
+ path: '/pages/home',
+ icon: 'home'
+ },
+ {
+ text: 'Examples',
+ icon: 'folder',
+ items: [
+ {
+ text: 'Profile',
+ path: '/pages/profile'
+ },
+ {
+ text: 'Tasks',
+ path: '/pages/tasks'
+ }
+ ]
+ }
+ <%=/empty%>];
diff --git a/packages/devextreme-cli/src/templates/nextjs/application/src/app/actions/auth.ts b/packages/devextreme-cli/src/templates/nextjs/application/src/app/actions/auth.ts
new file mode 100644
index 000000000..f16cafb86
--- /dev/null
+++ b/packages/devextreme-cli/src/templates/nextjs/application/src/app/actions/auth.ts
@@ -0,0 +1,76 @@
+'use server'
+import { redirect } from 'next/navigation'
+import defaultUser from '@/utils/default-user';
+import { createSession, deleteSession } from '@/app/lib/session'
+
+export async function signUp(email<%=#isTypeScript%>: string<%=/isTypeScript%>, password<%=#isTypeScript%>: string<%=/isTypeScript%>) {
+ try {
+ // 1. Check if the user exists in the database and return isOk: false if so;
+ // 2. Otherwise, add the user to the database.
+ console.log(email, password);
+
+ return {
+ isOk: true,
+ }
+ } catch {
+ return {
+ isOk: false,
+ message: 'Unable to create an account',
+ }
+ }
+}
+
+export async function signIn(email<%=#isTypeScript%>: string<%=/isTypeScript%>, password<%=#isTypeScript%>: string<%=/isTypeScript%>) {
+ try {
+ // Verify that a user exists
+ console.log(email, password);
+
+ await createSession(defaultUser.id);
+
+ return {
+ isOk: true,
+ }
+ } catch {
+ return {
+ isOk: false,
+ message: 'Unable to sign in',
+ }
+ }
+}
+
+export async function signOut() {
+ await deleteSession();
+ redirect('/login');
+}
+
+export async function changePassword(email<%=#isTypeScript%>: string<%=/isTypeScript%>, recoveryCode<%=#isTypeScript%>?: string<%=/isTypeScript%>) {
+ try {
+ // Verify the recovery code
+ console.log(email, recoveryCode);
+
+ return {
+ isOk: true,
+ }
+ } catch {
+ return {
+ isOk: false,
+ message: 'Unable to change the password',
+ }
+ }
+}
+
+export async function resetPassword(email<%=#isTypeScript%>: string<%=/isTypeScript%>) {
+ try {
+ // Reset password
+ console.log(email);
+
+ return {
+ isOk: true,
+ }
+ } catch {
+ return {
+ isOk: false,
+ message: 'Unable to reset password',
+ }
+ }
+}
diff --git a/packages/devextreme-cli/src/templates/nextjs/application/src/app/auth/[type]/page.tsx b/packages/devextreme-cli/src/templates/nextjs/application/src/app/auth/[type]/page.tsx
new file mode 100644
index 000000000..a208cc522
--- /dev/null
+++ b/packages/devextreme-cli/src/templates/nextjs/application/src/app/auth/[type]/page.tsx
@@ -0,0 +1,49 @@
+'use client'
+import { use } from 'react';
+import { notFound } from 'next/navigation';
+import { SingleCard } from '@/layouts';
+import {
+ LoginForm,
+ CreateAccountForm,
+ ResetPasswordForm,
+ ChangePasswordForm,
+} from '@/components';
+
+const formText<%=#isTypeScript%>: Record><%=/isTypeScript%> = {
+ 'login': {
+ title: 'Sign In'
+ },
+ 'create-account': {
+ title: 'Sign Up'
+ },
+ 'reset-password': {
+ title: 'Reset Password',
+ description: 'Please enter the email address that you used to register, and we will send you a link to reset your password via Email.'
+ },
+ 'change-password': {
+ title: 'Change Password',
+ }
+}
+
+function AuthForm({name}<%=#isTypeScript%>: {name: string}<%=/isTypeScript%>) {
+ switch (name) {
+ case 'login': return ;
+ case 'create-account': return ;
+ case 'reset-password': return ;
+ case 'change-password': return ;
+ }
+}
+
+export default function AuthPage({ params }<%=#isTypeScript%>: {params: Promise<{type: string}>}<%=/isTypeScript%>) {
+ const { type } = use(params)
+
+ if (!formText[type]) {
+ notFound();
+ }
+
+ const { title, description } = formText[type];
+
+ return
+
+
+}
diff --git a/packages/devextreme-cli/src/templates/nextjs/application/src/app/layout.tsx b/packages/devextreme-cli/src/templates/nextjs/application/src/app/layout.tsx
new file mode 100644
index 000000000..daaaf4484
--- /dev/null
+++ b/packages/devextreme-cli/src/templates/nextjs/application/src/app/layout.tsx
@@ -0,0 +1,17 @@
+<%=#isTypeScript%>import type { PropsWithChildren } from 'react';
+<%=/isTypeScript%>import { ThemeProvider } from "@/theme";
+
+export default function RootLayout({ children }<%=#isTypeScript%>: PropsWithChildren<%=/isTypeScript%>) {
+ return (
+
+ NextJs Dx App
+
+
+
+ {children}
+
+
+
+
+ );
+}
diff --git a/packages/devextreme-cli/src/templates/nextjs/application/src/app/lib/session.ts b/packages/devextreme-cli/src/templates/nextjs/application/src/app/lib/session.ts
new file mode 100644
index 000000000..2e3e5df69
--- /dev/null
+++ b/packages/devextreme-cli/src/templates/nextjs/application/src/app/lib/session.ts
@@ -0,0 +1,47 @@
+import 'server-only';
+import { SignJWT, jwtVerify } from 'jose';
+import { cookies } from 'next/headers';
+<%=#isTypeScript%>import type { SessionPayload } from '@/types';
+<%=/isTypeScript%>
+const secretKey = process.env.SESSION_SECRET;
+const encoder = new TextEncoder();
+const encodedKey = encoder.encode(secretKey);
+
+export async function encrypt(payload<%=#isTypeScript%>: SessionPayload<%=/isTypeScript%>) {
+ return new SignJWT(payload)
+ .setProtectedHeader({ alg: 'HS256' })
+ .setIssuedAt()
+ .setExpirationTime('7d')
+ .sign(encodedKey);
+}
+
+export async function decrypt(session<%=#isTypeScript%>: string | undefined = ''<%=/isTypeScript%>) {
+ try {
+ const { payload } = await jwtVerify(session, encodedKey, {
+ algorithms: ['HS256'],
+ });
+
+ return payload;
+ } catch {
+ console.log('Failed to verify session');
+ }
+}
+
+export async function createSession(userId<%=#isTypeScript%>: string<%=/isTypeScript%>) {
+ const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);
+ const session = await encrypt({ userId, expiresAt });
+ const cookieStore = await cookies();
+
+ cookieStore.set('session', session, {
+ httpOnly: true,
+ secure: true,
+ expires: expiresAt,
+ sameSite: 'lax',
+ path: '/',
+ });
+}
+
+export async function deleteSession() {
+ const cookieStore = await cookies();
+ cookieStore.delete('session');
+}
diff --git a/packages/devextreme-cli/src/templates/nextjs/application/src/app/pages/layout.tsx b/packages/devextreme-cli/src/templates/nextjs/application/src/app/pages/layout.tsx
new file mode 100644
index 000000000..12cb05321
--- /dev/null
+++ b/packages/devextreme-cli/src/templates/nextjs/application/src/app/pages/layout.tsx
@@ -0,0 +1,18 @@
+<%=#isTypeScript%>import type { PropsWithChildren } from 'react';
+<%=/isTypeScript%>import appInfo from '@/app-info';
+import { Footer } from '@/components';
+import { <%=layout%> as SideNavBarLayout } from '@/layouts';
+
+export default function Content({children}<%=#isTypeScript%>: PropsWithChildren<%=/isTypeScript%>) {
+ return (
+
+ {children}
+
+ Copyright © 2011-{new Date().getFullYear()} {appInfo.title} Inc.
+
+ All trademarks or registered trademarks are property of their
+ respective owners.
+
+
+ );
+}
diff --git a/packages/devextreme-cli/src/templates/nextjs/application/src/components/change-password-form/ChangePasswordForm.tsx b/packages/devextreme-cli/src/templates/nextjs/application/src/components/change-password-form/ChangePasswordForm.tsx
new file mode 100644
index 000000000..6c779f650
--- /dev/null
+++ b/packages/devextreme-cli/src/templates/nextjs/application/src/components/change-password-form/ChangePasswordForm.tsx
@@ -0,0 +1,86 @@
+'use client'
+import <%=#isTypeScript%>React, <%=/isTypeScript%>{ useState, useRef, useCallback } from 'react';
+import { useRouter } from 'next/navigation';
+import Form, {
+ Item,
+ Label,
+ ButtonItem,
+ ButtonOptions,
+ RequiredRule,
+ CustomRule,
+} from 'devextreme-react/form';
+import LoadIndicator from 'devextreme-react/load-indicator';
+import notify from 'devextreme/ui/notify';
+<%=#isTypeScript%>import { ValidationCallbackData } from 'devextreme-react/common';<%=/isTypeScript%>
+import { changePassword } from '@/app/actions/auth';
+
+export default function ChangePasswordForm() {
+ const router = useRouter();
+ const [loading, setLoading] = useState(false);
+ const formData = useRef({ password: '' });
+
+ const onSubmit = useCallback(async (e<%=#isTypeScript%>: React.FormEvent<%=/isTypeScript%>) => {
+ e.preventDefault();
+ const { password } = formData.current;
+ setLoading(true);
+
+ const result = await changePassword(password);
+ setLoading(false);
+
+ if (result.isOk) {
+ router.push('/login');
+ } else {
+ notify(result.message, 'error', 2000);
+ }
+}, [router]);
+
+const confirmPassword = useCallback(
+ ({ value }<%=#isTypeScript%>: ValidationCallbackData<%=/isTypeScript%>) => value === formData.current.password,
+ []
+);
+
+return (
+
+
+);
+}
+
+const passwordEditorOptions = { stylingMode: 'filled', placeholder: 'Password', mode: 'password' };
+const confirmedPasswordEditorOptions = { stylingMode: 'filled', placeholder: 'Confirm Password', mode: 'password' };
diff --git a/packages/devextreme-cli/src/templates/nextjs/application/src/components/create-account-form/CreateAccountForm.scss b/packages/devextreme-cli/src/templates/nextjs/application/src/components/create-account-form/CreateAccountForm.scss
new file mode 100644
index 000000000..830ab7065
--- /dev/null
+++ b/packages/devextreme-cli/src/templates/nextjs/application/src/components/create-account-form/CreateAccountForm.scss
@@ -0,0 +1,19 @@
+.create-account-form {
+ .policy-info {
+ color: var(--base-text-color-alpha-7);
+ font-size: 12px;
+ font-style: normal;
+
+ a {
+ color: var(--base-text-color-alpha-7);
+ }
+ }
+
+ .login-link {
+ color: var(--base-accent);
+ font-size: 12px;
+ text-align: center;
+ padding: 6px 0 32px 0;
+ border-bottom: 1px solid var(--border-color);
+ }
+}
diff --git a/packages/devextreme-cli/src/templates/nextjs/application/src/components/create-account-form/CreateAccountForm.tsx b/packages/devextreme-cli/src/templates/nextjs/application/src/components/create-account-form/CreateAccountForm.tsx
new file mode 100644
index 000000000..1d7c8fe4f
--- /dev/null
+++ b/packages/devextreme-cli/src/templates/nextjs/application/src/components/create-account-form/CreateAccountForm.tsx
@@ -0,0 +1,107 @@
+'use client'
+import <%=#isTypeScript%>React, <%=/isTypeScript%>{ useState, useRef, useCallback } from 'react';
+import { useRouter } from 'next/navigation';
+import Link from 'next/link';
+import Form, {
+ Item,
+ Label,
+ ButtonItem,
+ ButtonOptions,
+ RequiredRule,
+ CustomRule,
+ EmailRule
+} from 'devextreme-react/form';
+import notify from 'devextreme/ui/notify';
+import LoadIndicator from 'devextreme-react/load-indicator';
+import { signUp } from '@/app/actions/auth';
+<%=#isTypeScript%>import { ValidationCallbackData } from 'devextreme-react/common';<%=/isTypeScript%>
+import './CreateAccountForm.scss';
+
+export default function CreateAccountForm() {
+ const router = useRouter();
+ const [loading, setLoading] = useState(false);
+ const formData = useRef({ email: '', password: '' });
+
+ const onSubmit = useCallback(async (e<%=#isTypeScript%>: React.FormEvent<%=/isTypeScript%>) => {
+ e.preventDefault();
+ const { email, password } = formData.current;
+ setLoading(true);
+
+ const result = await signUp(email, password);
+ setLoading(false);
+
+ if (result.isOk) {
+ router.push('/login');
+ } else {
+ notify(result.message, 'error', 2000);
+ }
+ }, [router]);
+
+ const confirmPassword = useCallback(
+ ({ value }<%=#isTypeScript%>: ValidationCallbackData<%=/isTypeScript%>) => value === formData.current.password,
+ []
+ );
+
+ return (
+
+
+ Have an account? Sign In
+
+
+ );
+}
+
+const emailEditorOptions = { stylingMode: 'filled', placeholder: 'Email', mode: 'email' };
+const passwordEditorOptions = { stylingMode: 'filled', placeholder: 'Password', mode: 'password' };
+const confirmedPasswordEditorOptions = { stylingMode: 'filled', placeholder: 'Confirm Password', mode: 'password' };
diff --git a/packages/devextreme-cli/src/templates/nextjs/application/src/components/footer/Footer.scss b/packages/devextreme-cli/src/templates/nextjs/application/src/components/footer/Footer.scss
new file mode 100644
index 000000000..c1dc94dea
--- /dev/null
+++ b/packages/devextreme-cli/src/templates/nextjs/application/src/components/footer/Footer.scss
@@ -0,0 +1,12 @@
+.footer {
+ display: block;
+ color: var(--base-text-color-alpha-7);
+ border-top: 1px solid var(--footer-border-color);
+ padding-top: 20px;
+ padding-bottom: 24px;
+ margin: 0 40px;
+
+ @media (max-width: 599.99px) {
+ margin: 0 20px;
+ }
+}
diff --git a/packages/devextreme-cli/src/templates/nextjs/application/src/components/footer/Footer.tsx b/packages/devextreme-cli/src/templates/nextjs/application/src/components/footer/Footer.tsx
new file mode 100644
index 000000000..bc82bfbd5
--- /dev/null
+++ b/packages/devextreme-cli/src/templates/nextjs/application/src/components/footer/Footer.tsx
@@ -0,0 +1,5 @@
+import './Footer.scss';
+
+export default function Footer({ ...rest }) {
+ return ;
+}
diff --git a/packages/devextreme-cli/src/templates/nextjs/application/src/components/header/Header.scss b/packages/devextreme-cli/src/templates/nextjs/application/src/components/header/Header.scss
new file mode 100644
index 000000000..942b0a9d3
--- /dev/null
+++ b/packages/devextreme-cli/src/templates/nextjs/application/src/components/header/Header.scss
@@ -0,0 +1,40 @@
+@use "../../dx-styles.scss" as *;
+
+header {
+ background-color: var(--base-bg);
+}
+
+.header-component {
+ flex: 0 0 auto;
+ z-index: 1;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
+}
+
+.dx-toolbar.header-toolbar .dx-toolbar-items-container .dx-toolbar-after {
+ padding: 0 40px;
+
+ @media (max-width: 599.99px) {
+ padding: 0 20px;
+ }
+}
+
+.dx-toolbar .dx-toolbar-item.dx-toolbar-button.menu-button {
+ width: $side-panel-min-width;
+ text-align: center;
+ padding: 0;
+}
+
+.header-title .dx-item-content {
+ padding: 0;
+ margin: 0;
+}
+
+.dx-theme-generic {
+ .dx-toolbar {
+ padding: 10px 0;
+ }
+
+ .user-button>.dx-button-content {
+ padding: 3px;
+ }
+}
diff --git a/packages/devextreme-cli/src/templates/nextjs/application/src/components/header/Header.tsx b/packages/devextreme-cli/src/templates/nextjs/application/src/components/header/Header.tsx
new file mode 100644
index 000000000..8815c3f2c
--- /dev/null
+++ b/packages/devextreme-cli/src/templates/nextjs/application/src/components/header/Header.tsx
@@ -0,0 +1,38 @@
+import Toolbar, { Item } from 'devextreme-react/toolbar';
+import Button from 'devextreme-react/button';
+import UserPanel from '@/components/user-panel/UserPanel';
+import './Header.scss';
+import { ThemeSwitcher } from '@/components/theme-switcher/ThemeSwitcher';
+<%=#isTypeScript%>import type { HeaderProps } from '@/types';<%=/isTypeScript%>
+
+const renderMenuItem = () => ;
+
+export default function Header({ menuToggleEnabled, title, toggleMenu }<%=#isTypeScript%>: HeaderProps<%=/isTypeScript%>) {
+ return (
+
+
+ -
+
+
+
+ -
+
+
+ -
+
+
+
+
+)}
diff --git a/packages/devextreme-cli/src/templates/nextjs/application/src/components/index.tsx b/packages/devextreme-cli/src/templates/nextjs/application/src/components/index.tsx
new file mode 100644
index 000000000..651b3bde0
--- /dev/null
+++ b/packages/devextreme-cli/src/templates/nextjs/application/src/components/index.tsx
@@ -0,0 +1,7 @@
+export { default as Header } from './header/Header';
+export { default as Footer } from './footer/Footer';
+export { default as LoginForm } from './login-form/LoginForm';
+export { default as ResetPasswordForm } from './reset-password-form/ResetPasswordForm';
+export { default as ChangePasswordForm } from './change-password-form/ChangePasswordForm';
+export { default as CreateAccountForm } from './create-account-form/CreateAccountForm';
+export { default as SideNavigationMenu } from './side-navigation-menu/SideNavigationMenu';
diff --git a/packages/devextreme-cli/src/templates/nextjs/application/src/components/login-form/LoginForm.scss b/packages/devextreme-cli/src/templates/nextjs/application/src/components/login-form/LoginForm.scss
new file mode 100644
index 000000000..702162654
--- /dev/null
+++ b/packages/devextreme-cli/src/templates/nextjs/application/src/components/login-form/LoginForm.scss
@@ -0,0 +1,12 @@
+.login-form {
+ .link {
+ text-align: center;
+ font-size: 12px;
+ font-style: normal;
+ margin: 6px 0 50px;
+ }
+
+ .form-text {
+ color: var(--base-text-color-alpha-7);
+ }
+}
diff --git a/packages/devextreme-cli/src/templates/nextjs/application/src/components/login-form/LoginForm.tsx b/packages/devextreme-cli/src/templates/nextjs/application/src/components/login-form/LoginForm.tsx
new file mode 100644
index 000000000..65e97280b
--- /dev/null
+++ b/packages/devextreme-cli/src/templates/nextjs/application/src/components/login-form/LoginForm.tsx
@@ -0,0 +1,101 @@
+'use client'
+import <%=#isTypeScript%>React, <%=/isTypeScript%>{ useState, useRef, useCallback } from 'react';
+import { useRouter } from 'next/navigation';
+import Link from 'next/link';
+import Form, {
+ Item,
+ Label,
+ ButtonItem,
+ ButtonOptions,
+ RequiredRule,
+ EmailRule
+} from 'devextreme-react/form';
+import LoadIndicator from 'devextreme-react/load-indicator';
+import Button from 'devextreme-react/button';
+import notify from 'devextreme/ui/notify';
+import { signIn } from '@/app/actions/auth';
+
+import './LoginForm.scss';
+
+export default function LoginForm() {
+ const router = useRouter();
+ const [loading, setLoading] = useState(false);
+ const formData = useRef({ email: '', password: '' });
+
+ const onSubmit = useCallback(async (e<%=#isTypeScript%>: React.FormEvent<%=/isTypeScript%>) => {
+ e.preventDefault();
+ const { email, password } = formData.current;
+ setLoading(true);
+
+ const result = await signIn(email, password);
+ if (!result.isOk) {
+ setLoading(false);
+ notify(result.message, 'error', 2000);
+ } else {
+ router.push('/');
+ }
+ }, [router]);
+
+ const onCreateAccountClick = useCallback(() => {
+ router.push('/auth/create-account');
+ }, [router]);
+
+ return (
+
+
+ Forgot password?
+
+
+
+ );
+}
+
+const emailEditorOptions = { stylingMode: 'filled', placeholder: 'Email', mode: 'email' };
+const passwordEditorOptions = { stylingMode: 'filled', placeholder: 'Password', mode: 'password' };
+const rememberMeEditorOptions = { text: 'Remember me', elementAttr: { class: 'form-text' } };
diff --git a/packages/devextreme-cli/src/templates/nextjs/application/src/components/reset-password-form/ResetPasswordForm.scss b/packages/devextreme-cli/src/templates/nextjs/application/src/components/reset-password-form/ResetPasswordForm.scss
new file mode 100644
index 000000000..f9bf5090b
--- /dev/null
+++ b/packages/devextreme-cli/src/templates/nextjs/application/src/components/reset-password-form/ResetPasswordForm.scss
@@ -0,0 +1,12 @@
+.reset-password-form {
+ .submit-button {
+ margin-top: 18px;
+ }
+
+ .login-link {
+ color: var(--base-accent);
+ font-size: 12px;
+ text-align: center;
+ margin-top: 6px;
+ }
+}
diff --git a/packages/devextreme-cli/src/templates/nextjs/application/src/components/reset-password-form/ResetPasswordForm.tsx b/packages/devextreme-cli/src/templates/nextjs/application/src/components/reset-password-form/ResetPasswordForm.tsx
new file mode 100644
index 000000000..4b74cb2ac
--- /dev/null
+++ b/packages/devextreme-cli/src/templates/nextjs/application/src/components/reset-password-form/ResetPasswordForm.tsx
@@ -0,0 +1,78 @@
+'use client'
+import <%=#isTypeScript%>React, <%=/isTypeScript%>{ useState, useRef, useCallback } from 'react';
+import { useRouter } from 'next/navigation';
+import Link from 'next/link';
+import Form, {
+ Item,
+ Label,
+ ButtonItem,
+ ButtonOptions,
+ RequiredRule,
+ EmailRule
+} from 'devextreme-react/form';
+import LoadIndicator from 'devextreme-react/load-indicator';
+import notify from 'devextreme/ui/notify';
+import { resetPassword } from '@/app/actions/auth';
+import './ResetPasswordForm.scss';
+
+const notificationText = 'We\'ve sent a link to reset your password. Check your inbox.';
+
+export default function ResetPasswordForm() {
+ const router = useRouter();
+ const [loading, setLoading] = useState(false);
+ const formData = useRef({ email: '', password: '' });
+
+ const onSubmit = useCallback(async (e<%=#isTypeScript%>: React.FormEvent<%=/isTypeScript%>) => {
+ e.preventDefault();
+ const { email } = formData.current;
+ setLoading(true);
+
+ const result = await resetPassword(email);
+ setLoading(false);
+
+ if (result.isOk) {
+ router.push('/login');
+ notify(notificationText, 'success', 2500);
+ } else {
+ notify(result.message, 'error', 2000);
+ }
+ }, [router]);
+
+ return (
+
+
+ Return to Sign In
+
+
+ );
+}
+
+const emailEditorOptions = { stylingMode: 'filled', placeholder: 'Email', mode: 'email' };
+const submitButtonAttributes = { class: 'submit-button' };
diff --git a/packages/devextreme-cli/src/templates/nextjs/application/src/components/side-navigation-menu/SideNavigationMenu.scss b/packages/devextreme-cli/src/templates/nextjs/application/src/components/side-navigation-menu/SideNavigationMenu.scss
new file mode 100644
index 000000000..a2e6331b7
--- /dev/null
+++ b/packages/devextreme-cli/src/templates/nextjs/application/src/components/side-navigation-menu/SideNavigationMenu.scss
@@ -0,0 +1,71 @@
+@use "../../dx-styles" as *;
+
+.dx-swatch-additional, .dx-swatch-additional-dark {
+ &.side-navigation-menu {
+ display: flex;
+ flex-direction: column;
+ min-height: 100%;
+ height: 100%;
+ width: 250px !important;
+ background-color: var(--base-bg);
+
+ .menu-container {
+ min-height: 100%;
+ display: flex;
+ flex: 1;
+
+ .dx-treeview {
+ // ## Long text positioning
+ white-space: nowrap;
+ // ##
+
+ .dx-treeview-node-container:empty {
+ display: none;
+ }
+
+ // ## Icon width customization
+ .dx-treeview-item {
+ padding-left: 0;
+ border-radius: 0;
+ flex-direction: row-reverse;
+
+ .dx-icon {
+ width: $side-panel-min-width !important;
+ margin: 0 !important;
+ }
+ }
+
+ // ##
+
+ // ## Arrow customization
+ .dx-treeview-node {
+ padding: 0 0 !important;
+ }
+
+ .dx-treeview-toggle-item-visibility {
+ right: 10px;
+ left: auto;
+ }
+
+ .dx-rtl .dx-treeview-toggle-item-visibility {
+ left: 10px;
+ right: auto;
+ }
+ // ##
+
+ // ## Item levels customization
+ .dx-treeview-node {
+ &[aria-level="1"] {
+ font-weight: bold;
+ }
+
+ &[aria-level="2"] .dx-treeview-item-content {
+ font-weight: normal;
+ padding: 0 $side-panel-min-width;
+ }
+ }
+ // ##
+ }
+ }
+ }
+}
diff --git a/packages/devextreme-cli/src/templates/nextjs/application/src/components/side-navigation-menu/SideNavigationMenu.tsx b/packages/devextreme-cli/src/templates/nextjs/application/src/components/side-navigation-menu/SideNavigationMenu.tsx
new file mode 100644
index 000000000..bc9fa2246
--- /dev/null
+++ b/packages/devextreme-cli/src/templates/nextjs/application/src/components/side-navigation-menu/SideNavigationMenu.tsx
@@ -0,0 +1,88 @@
+'use client'
+import React, { useEffect, useRef, useCallback, useMemo, useContext } from 'react';
+import { TreeView<%=#isTypeScript%>, TreeViewRef<%=/isTypeScript%> } from 'devextreme-react/tree-view';
+import * as events from 'devextreme-react/common/core/events';
+import { navigation } from '@/app-navigation';
+import { usePathname } from 'next/navigation';
+import { useScreenSize } from '@/utils/media-query';
+import './SideNavigationMenu.scss';
+<%=#isTypeScript%>import type { SideNavigationMenuProps } from '@/types';
+<%=/isTypeScript%>import { ThemeContext } from '@/theme';
+
+export default function SideNavigationMenu(props<%=#isTypeScript%>: React.PropsWithChildren<%=/isTypeScript%>) {
+ const {
+ children,
+ selectedItemChanged,
+ openMenu,
+ compactMode,
+ onMenuReady
+ } = props;
+
+ const theme = useContext(ThemeContext);
+ const { isLarge } = useScreenSize();
+ function normalizePath () {
+ return navigation.map((item) => (
+ { ...item, expanded: isLarge, path: item.path && !(/^\//.test(item.path)) ? `/${item.path}` : item.path }
+ ))
+ }
+
+ const items = useMemo(
+ normalizePath,
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ []
+ );
+
+ const pathname = usePathname();
+
+ const treeViewRef = useRef<%=#isTypeScript%><%=/isTypeScript%>(null);
+ const wrapperRef = useRef<%=#isTypeScript%><%=/isTypeScript%>(null);
+ const getWrapperRef = useCallback((element<%=#isTypeScript%>: HTMLDivElement<%=/isTypeScript%>) => {
+ const prevElement = wrapperRef.current;
+ if (prevElement) {
+ events.off(prevElement, 'dxclick');
+ }
+
+ wrapperRef.current = element;
+ events.on(element, 'dxclick', (e<%=#isTypeScript%>: React.PointerEvent<%=/isTypeScript%>) => {
+ openMenu(e);
+ });
+ }, [openMenu]);
+
+ useEffect(() => {
+ const treeView = treeViewRef.current && treeViewRef.current.instance();
+ if (!treeView) {
+ return;
+ }
+
+ if (pathname !== undefined) {
+ treeView.selectItem(pathname);
+ treeView.expandItem(pathname);
+ }
+
+ if (compactMode) {
+ treeView.collapseAll();
+ }
+ }, [pathname, compactMode]);
+
+ return (
+
+ );
+}
diff --git a/packages/devextreme-cli/src/templates/nextjs/application/src/components/theme-switcher/ThemeSwitcher.tsx b/packages/devextreme-cli/src/templates/nextjs/application/src/components/theme-switcher/ThemeSwitcher.tsx
new file mode 100644
index 000000000..e28d2734b
--- /dev/null
+++ b/packages/devextreme-cli/src/templates/nextjs/application/src/components/theme-switcher/ThemeSwitcher.tsx
@@ -0,0 +1,21 @@
+'use client'
+import { useCallback, useContext } from 'react';
+import Button from 'devextreme-react/button';
+import { ThemeContext } from '@/theme';
+
+export const ThemeSwitcher = () => {
+ const themeContext = useContext(ThemeContext);
+
+ const onButtonClick = useCallback(() => {
+ themeContext?.switchTheme();
+ }, [themeContext]);
+
+ return
+
+
;
+};
diff --git a/packages/devextreme-cli/src/templates/nextjs/application/src/components/user-panel/UserPanel.scss b/packages/devextreme-cli/src/templates/nextjs/application/src/components/user-panel/UserPanel.scss
new file mode 100644
index 000000000..24b75e3c4
--- /dev/null
+++ b/packages/devextreme-cli/src/templates/nextjs/application/src/components/user-panel/UserPanel.scss
@@ -0,0 +1,51 @@
+.app .header-toolbar .user-panel .user-button.dx-dropdownbutton img.dx-icon {
+ height: 100%;
+ width: auto;
+
+ .dx-theme-generic & {
+ max-height: 32px;
+ }
+}
+
+.user-panel {
+ display: flex;
+ flex-direction: column;
+
+ .user-button.dx-dropdownbutton {
+ margin-left: 5px;
+
+ img.dx-icon {
+ border-radius: 50%;
+ margin: 0;
+ width: auto;
+ aspect-ratio: 1 / 1;
+ box-sizing: border-box;
+ border: 1px solid var(--dx-color-border);
+ object-fit: cover;
+ object-position: top;
+ background: rgb(255, 255, 255);
+ background-clip: padding-box;
+ }
+
+
+
+ .dx-buttongroup {
+ vertical-align: middle;
+
+ .dx-button.dx-button-has-icon:not(.dx-button-has-text) {
+ .dx-button-content {
+ padding: 0;
+ }
+
+ &.dx-state-hover,
+ &.dx-state-focused {
+ background-color: transparent;
+
+ img.dx-icon {
+ border-color: var(--dx-color-primary);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/devextreme-cli/src/templates/nextjs/application/src/components/user-panel/UserPanel.tsx b/packages/devextreme-cli/src/templates/nextjs/application/src/components/user-panel/UserPanel.tsx
new file mode 100644
index 000000000..a175762c1
--- /dev/null
+++ b/packages/devextreme-cli/src/templates/nextjs/application/src/components/user-panel/UserPanel.tsx
@@ -0,0 +1,55 @@
+'use client'
+import { useMemo, useCallback } from 'react';
+import { useRouter } from 'next/navigation';
+import DropDownButton from 'devextreme-react/drop-down-button';
+import List from 'devextreme-react/list';
+import { signOut } from '@/app/actions/auth';
+import './UserPanel.scss';
+<%=#isTypeScript%>import type { UserPanelProps } from '@/types';<%=/isTypeScript%>
+
+export default function UserPanel({ menuMode }<%=#isTypeScript%>: UserPanelProps<%=/isTypeScript%>) {
+ const router = useRouter();
+
+ const navigateToProfile = useCallback(() => {
+ router.push("/pages/profile");
+ }, [router]);
+
+ const menuItems = useMemo(() => ([
+ {
+ text: 'Profile',
+ icon: 'user',
+ onClick: navigateToProfile
+ },
+ {
+ text: 'Logout',
+ icon: 'runner',
+ onClick: signOut
+ }
+ ]), [navigateToProfile]);
+
+ const dropDownButtonAttributes = {
+ class: 'user-button'
+ };
+
+ const buttonDropDownOptions = {
+ width: '150px'
+ };
+
+ return (
+
+ {menuMode === 'context' && (
+
+
+ )}
+ {menuMode === 'list' && (
+
+ )}
+
+ );
+}
diff --git a/packages/devextreme-cli/src/templates/nextjs/application/src/dx-styles.scss b/packages/devextreme-cli/src/templates/nextjs/application/src/dx-styles.scss
new file mode 100644
index 000000000..77cde66ae
--- /dev/null
+++ b/packages/devextreme-cli/src/templates/nextjs/application/src/dx-styles.scss
@@ -0,0 +1,106 @@
+@use "variables.scss" as *;
+
+$side-panel-min-width: 60px;
+
+html,
+body {
+ margin: 0;
+ min-height: 100%;
+ height: 100%;
+}
+
+.dx-viewport {
+ .dx-popup-wrapper {
+ z-index: 1510 !important;
+ }
+
+ #root {
+ height: 100%;
+ }
+
+ * {
+ box-sizing: border-box;
+ }
+
+ .app {
+ background-color: var(--base-bg-darken-5);
+ display: flex;
+ height: 100%;
+ width: 100%;
+ min-width: 320px;
+ }
+
+ .content {
+ line-height: 1.5;
+ flex-grow: 1;
+ padding: 20px 40px;
+
+ h2 {
+ font-size: 32px;
+ margin: 0;
+ line-height: 40px;
+ }
+ }
+
+ @media (max-width: 599.99px) {
+ :not(.dx-card).content {
+ padding: 20px;
+ }
+ }
+
+ .container {
+ height: 100%;
+ flex-direction: column;
+ display: flex;
+ }
+
+ .layout-body {
+ flex: 1;
+ min-height: 0;
+ }
+
+ .side-nav-outer-toolbar .dx-drawer {
+ height: calc(100% - 56px)
+ }
+
+ .content-block {
+ margin-top: 20px;
+ }
+
+ .responsive-paddings {
+ padding: 20px;
+
+ @media (min-width: 1280px) {
+ padding: 40px;
+ }
+ }
+
+ .dx-card.wide-card {
+ border-radius: 0;
+ margin-left: 0;
+ margin-right: 0;
+ border-right: 0;
+ border-left: 0;
+ }
+
+ .with-footer > .dx-scrollable-wrapper >
+ .dx-scrollable-container > .dx-scrollable-content {
+ height: 100%;
+
+ & > .dx-scrollview-content {
+ display: flex;
+ flex-direction: column;
+ min-height: 100%;
+ }
+ }
+
+}
+
+.dx-theme-fluent {
+ .dx-drawer-wrapper {
+ .dx-drawer-panel-content,
+ .dx-overlay-content {
+ box-shadow: 0 4px 4px 0 var(--shadow-color-first), 0 1px 2px 0 var(--shadow-color-second);
+ }
+ }
+}
diff --git a/packages/devextreme-cli/src/templates/cra-template-typescript/template/src/index.css b/packages/devextreme-cli/src/templates/nextjs/application/src/index.css
similarity index 100%
rename from packages/devextreme-cli/src/templates/cra-template-typescript/template/src/index.css
rename to packages/devextreme-cli/src/templates/nextjs/application/src/index.css
diff --git a/packages/devextreme-cli/src/templates/nextjs/application/src/layouts/index.tsx b/packages/devextreme-cli/src/templates/nextjs/application/src/layouts/index.tsx
new file mode 100644
index 000000000..668981de3
--- /dev/null
+++ b/packages/devextreme-cli/src/templates/nextjs/application/src/layouts/index.tsx
@@ -0,0 +1,3 @@
+export { default as SideNavOuterToolbar } from './side-nav-outer-toolbar/side-nav-outer-toolbar';
+export { default as SideNavInnerToolbar } from './side-nav-inner-toolbar/side-nav-inner-toolbar';
+export { default as SingleCard } from './single-card/single-card';
diff --git a/packages/devextreme-cli/src/templates/nextjs/application/src/layouts/side-nav-inner-toolbar/side-nav-inner-toolbar.scss b/packages/devextreme-cli/src/templates/nextjs/application/src/layouts/side-nav-inner-toolbar/side-nav-inner-toolbar.scss
new file mode 100644
index 000000000..eac0242d5
--- /dev/null
+++ b/packages/devextreme-cli/src/templates/nextjs/application/src/layouts/side-nav-inner-toolbar/side-nav-inner-toolbar.scss
@@ -0,0 +1,17 @@
+.side-nav-inner-toolbar {
+ width: 100%;
+}
+
+#navigation-header {
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
+ background-color: var(--base-bg);
+
+ @media (max-width: 599.99px) {
+ padding-left: 20px;
+ }
+
+ .dx-theme-generic & {
+ padding-top: 10px;
+ padding-bottom: 10px;
+ }
+}
diff --git a/packages/devextreme-cli/src/templates/nextjs/application/src/layouts/side-nav-inner-toolbar/side-nav-inner-toolbar.tsx b/packages/devextreme-cli/src/templates/nextjs/application/src/layouts/side-nav-inner-toolbar/side-nav-inner-toolbar.tsx
new file mode 100644
index 000000000..9eec054cf
--- /dev/null
+++ b/packages/devextreme-cli/src/templates/nextjs/application/src/layouts/side-nav-inner-toolbar/side-nav-inner-toolbar.tsx
@@ -0,0 +1,133 @@
+'use client'
+import Button from 'devextreme-react/button';
+import Drawer from 'devextreme-react/drawer';
+import { ScrollView<%=#isTypeScript%>, ScrollViewRef<%=/isTypeScript%> } from 'devextreme-react/scroll-view';
+import Toolbar, { Item } from 'devextreme-react/toolbar';
+import React, { useState, useCallback, useRef } from 'react';
+import { useRouter } from 'next/navigation';
+import { Header, SideNavigationMenu } from '@/components';
+import './side-nav-inner-toolbar.scss';
+import { useScreenSize } from '@/utils/media-query';
+import { Template } from 'devextreme-react/core/template';
+<%=#isTypeScript%>import type { TreeViewTypes } from 'devextreme-react/tree-view';
+import type { SideNavToolbarProps } from '@/types';
+import type { ButtonTypes } from 'devextreme-react/button';
+<%=/isTypeScript%>
+export default function SideNavInnerToolbar({ title, children }<%=#isTypeScript%>: React.PropsWithChildren<%=/isTypeScript%>) {
+ const scrollViewRef = useRef<%=#isTypeScript%><%=/isTypeScript%>(null);
+ const router = useRouter();
+ const { isXSmall, isLarge } = useScreenSize();
+ const [menuStatus, setMenuStatus] = useState(
+ isLarge ? MenuStatus.Opened : MenuStatus.Closed
+ );
+
+ const toggleMenu = useCallback(({ event }<%=#isTypeScript%>: ButtonTypes.ClickEvent<%=/isTypeScript%>) => {
+ setMenuStatus(
+ prevMenuStatus => prevMenuStatus === MenuStatus.Closed
+ ? MenuStatus.Opened
+ : MenuStatus.Closed
+ );
+ event<%=#isTypeScript%>?<%=/isTypeScript%>.stopPropagation();
+ }, []);
+
+ const temporaryOpenMenu = useCallback(() => {
+ setMenuStatus(
+ prevMenuStatus => prevMenuStatus === MenuStatus.Closed
+ ? MenuStatus.TemporaryOpened
+ : prevMenuStatus
+ );
+ }, []);
+
+ const onOutsideClick = useCallback(() => {
+ setMenuStatus(
+ prevMenuStatus => prevMenuStatus !== MenuStatus.Closed && !isLarge
+ ? MenuStatus.Closed
+ : prevMenuStatus
+ );
+ return menuStatus === MenuStatus.Closed ? true : false;
+ }, [isLarge, menuStatus]);
+
+ const onNavigationChanged = useCallback(({ itemData, event, node }<%=#isTypeScript%>: TreeViewTypes.ItemClickEvent<%=/isTypeScript%>) => {
+ if (menuStatus === MenuStatus.Closed || !itemData<%=#isTypeScript%>?<%=/isTypeScript%>.path || node<%=#isTypeScript%>?<%=/isTypeScript%>.selected) {
+ event<%=#isTypeScript%>?<%=/isTypeScript%>.preventDefault();
+ return;
+ }
+
+ router.push(itemData.path);
+ scrollViewRef.current<%=#isTypeScript%>?<%=/isTypeScript%>.instance().scrollTo(0);
+
+ if (!isLarge || menuStatus === MenuStatus.TemporaryOpened) {
+ setMenuStatus(MenuStatus.Closed);
+ event<%=#isTypeScript%>?<%=/isTypeScript%>.stopPropagation();
+ }
+ }, [router, menuStatus, isLarge]);
+
+ return (
+
+
+
+
+
+
+ {React.Children.map(children, (item) => {
+ if (<%=#isTypeScript%>React.isValidElement(item) && <%=/isTypeScript%>item.type !== "footer") {
+ return item;
+ }
+ return null;
+ })}
+
+
+ {React.Children.map(children, (item) => {
+ if (<%=#isTypeScript%>React.isValidElement(item) && <%=/isTypeScript%>item.type === "footer") {
+ return item;
+ }
+ return null;
+ })}
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+const MenuStatus = {
+ Closed: 1,
+ Opened: 2,
+ TemporaryOpened: 3
+};
+
diff --git a/packages/devextreme-cli/src/templates/nextjs/application/src/layouts/side-nav-outer-toolbar/side-nav-outer-toolbar.scss b/packages/devextreme-cli/src/templates/nextjs/application/src/layouts/side-nav-outer-toolbar/side-nav-outer-toolbar.scss
new file mode 100644
index 000000000..4c39e5c8b
--- /dev/null
+++ b/packages/devextreme-cli/src/templates/nextjs/application/src/layouts/side-nav-outer-toolbar/side-nav-outer-toolbar.scss
@@ -0,0 +1,10 @@
+.side-nav-outer-toolbar {
+ flex-direction: column;
+ display: flex;
+ height: 100%;
+ width: 100%;
+
+ .header-component {
+ z-index: 1505;
+ }
+}
diff --git a/packages/devextreme-cli/src/templates/nextjs/application/src/layouts/side-nav-outer-toolbar/side-nav-outer-toolbar.tsx b/packages/devextreme-cli/src/templates/nextjs/application/src/layouts/side-nav-outer-toolbar/side-nav-outer-toolbar.tsx
new file mode 100644
index 000000000..c0cd4abe5
--- /dev/null
+++ b/packages/devextreme-cli/src/templates/nextjs/application/src/layouts/side-nav-outer-toolbar/side-nav-outer-toolbar.tsx
@@ -0,0 +1,119 @@
+'use client'
+import Drawer from 'devextreme-react/drawer';
+import { ScrollView<%=#isTypeScript%>, ScrollViewRef<%=/isTypeScript%> } from 'devextreme-react/scroll-view';
+import React, { useState, useCallback, useRef } from 'react';
+import { useRouter } from 'next/navigation';
+import { Header, SideNavigationMenu } from '@/components';
+import './side-nav-outer-toolbar.scss';
+import { useScreenSize } from '@/utils/media-query';
+import { Template } from 'devextreme-react/core/template';
+<%=#isTypeScript%>import type { ButtonTypes } from 'devextreme-react/button';
+import type { TreeViewTypes } from 'devextreme-react/tree-view';
+import type { SideNavToolbarProps } from '@/types';
+<%=/isTypeScript%>
+export default function SideNavOuterToolbar({ title, children }<%=#isTypeScript%>: React.PropsWithChildren<%=/isTypeScript%>) {
+ const scrollViewRef = useRef<%=#isTypeScript%><%=/isTypeScript%>(null);
+const router = useRouter();
+ const { isXSmall, isLarge } = useScreenSize();
+ const [menuStatus, setMenuStatus] = useState(
+ isLarge ? MenuStatus.Opened : MenuStatus.Closed
+ );
+
+ const toggleMenu = useCallback(({ event }<%=#isTypeScript%>: ButtonTypes.ClickEvent<%=/isTypeScript%>) => {
+ setMenuStatus(
+ prevMenuStatus => prevMenuStatus === MenuStatus.Closed
+ ? MenuStatus.Opened
+ : MenuStatus.Closed
+ );
+ event<%=#isTypeScript%>?<%=/isTypeScript%>.stopPropagation();
+ }, []);
+
+ const temporaryOpenMenu = useCallback(() => {
+ setMenuStatus(
+ prevMenuStatus => prevMenuStatus === MenuStatus.Closed
+ ? MenuStatus.TemporaryOpened
+ : prevMenuStatus
+ );
+ }, []);
+
+ const onOutsideClick = useCallback(() => {
+ setMenuStatus(
+ prevMenuStatus => prevMenuStatus !== MenuStatus.Closed && !isLarge
+ ? MenuStatus.Closed
+ : prevMenuStatus
+ );
+ return menuStatus === MenuStatus.Closed ? true : false;
+ }, [isLarge, menuStatus]);
+
+ const onNavigationChanged = useCallback(({ itemData, event, node }<%=#isTypeScript%>: TreeViewTypes.ItemClickEvent<%=/isTypeScript%>) => {
+ if (menuStatus === MenuStatus.Closed || !itemData<%=#isTypeScript%>?<%=/isTypeScript%>.path || node<%=#isTypeScript%>?<%=/isTypeScript%>.selected) {
+ event<%=#isTypeScript%>?<%=/isTypeScript%>.preventDefault();
+ return;
+ }
+
+router.push(itemData.path);
+ scrollViewRef.current<%=#isTypeScript%>?<%=/isTypeScript%>.instance().scrollTo(0);
+
+ if (!isLarge || menuStatus === MenuStatus.TemporaryOpened) {
+ setMenuStatus(MenuStatus.Closed);
+ event<%=#isTypeScript%>?<%=/isTypeScript%>.stopPropagation();
+ }
+ }, [router, menuStatus, isLarge]);
+
+ return (
+
+
+
+
+
+
+ {React.Children.map(children, (item) => {
+ if (<%=#isTypeScript%>React.isValidElement(item) && <%=/isTypeScript%>item.type !== "footer") {
+ return item;
+ }
+ return null;
+ })}
+
+
+ {React.Children.map(children, (item) => {
+ if (<%=#isTypeScript%>React.isValidElement(item) && <%=/isTypeScript%>item.type === "footer") {
+ return item;
+ }
+ return null;
+ })}
+
+
+
+
+
+
+
+
+
+ );
+}
+
+const MenuStatus = {
+ Closed: 1,
+ Opened: 2,
+ TemporaryOpened: 3
+};
diff --git a/packages/devextreme-cli/src/templates/nextjs/application/src/layouts/single-card/single-card.scss b/packages/devextreme-cli/src/templates/nextjs/application/src/layouts/single-card/single-card.scss
new file mode 100644
index 000000000..9fa4761ff
--- /dev/null
+++ b/packages/devextreme-cli/src/templates/nextjs/application/src/layouts/single-card/single-card.scss
@@ -0,0 +1,42 @@
+.single-card {
+ width: 100%;
+ height: 100%;
+
+ .dx-card {
+ width: 360px;
+ margin: auto auto;
+ padding: 24px;
+ flex-grow: 0;
+ border-radius: 8px;
+
+ @media (max-width: 599.99px) {
+ width: 100%;
+ height: 100%;
+ border-radius: 0;
+ box-shadow: none;
+ margin: 0;
+ border: 0;
+ flex-grow: 1;
+ }
+
+ .header {
+ margin: 24px 0;
+
+ .title {
+ color: var(--base-text-color);
+ font-weight: 500;
+ font-size: 24px;
+ text-align: center;
+ line-height: 24px;
+ }
+
+ .description {
+ color: var(--base-text-color-alpha-7);
+ line-height: 16px;
+ font-size: 12px;
+ margin-top: 32px;
+ text-align: center;
+ }
+ }
+ }
+}
diff --git a/packages/devextreme-cli/src/templates/nextjs/application/src/layouts/single-card/single-card.tsx b/packages/devextreme-cli/src/templates/nextjs/application/src/layouts/single-card/single-card.tsx
new file mode 100644
index 000000000..f253b0dcd
--- /dev/null
+++ b/packages/devextreme-cli/src/templates/nextjs/application/src/layouts/single-card/single-card.tsx
@@ -0,0 +1,16 @@
+import ScrollView from 'devextreme-react/scroll-view';
+import './single-card.scss';
+<%=#isTypeScript%>import type { SingleCardProps } from '@/types';<%=/isTypeScript%>
+
+export default function SingleCard({ title, description, children }<%=#isTypeScript%>: React.PropsWithChildren<%=/isTypeScript%>) {
+ return (
+
+
+
+
{title}
+
{description}
+
+ {children}
+
+
+)}
diff --git a/packages/devextreme-cli/src/templates/nextjs/application/src/middleware.ts b/packages/devextreme-cli/src/templates/nextjs/application/src/middleware.ts
new file mode 100644
index 000000000..f7487feae
--- /dev/null
+++ b/packages/devextreme-cli/src/templates/nextjs/application/src/middleware.ts
@@ -0,0 +1,46 @@
+import { NextResponse<%=#isTypeScript%>, type NextRequest<%=/isTypeScript%> } from 'next/server';
+import { cookies } from 'next/headers';
+import { decrypt, createSession } from '@/app/lib/session';
+import defaultUser from '@/utils/default-user';
+
+const isProtectedRoute = (path<%=#isTypeScript%>: string<%=/isTypeScript%>) => path.startsWith('/pages');
+
+async function _DEMO_logIn() {
+ await createSession(defaultUser.id);
+
+ return NextResponse.next();
+}
+
+<%=#isTypeScript%>// eslint-disable-next-line @typescript-eslint/no-unused-vars
+<%=/isTypeScript%>async function redirectUnauthorized(req<%=#isTypeScript%>: NextRequest<%=/isTypeScript%>) {
+ return await _DEMO_logIn();
+
+ // In production, you will need to redirect unauthorized users
+ // return NextResponse.redirect(new URL('/auth/login', req.nextUrl))
+}
+
+export default async function middleware(req<%=#isTypeScript%>: NextRequest<%=/isTypeScript%>) {
+ const path = req.nextUrl.pathname;
+
+ if (!isProtectedRoute(path)) {
+ return NextResponse.next();
+ }
+
+ const cookie = (await cookies()).get('session')?.value;
+
+ if (!cookie) {
+ return await redirectUnauthorized(req);
+ }
+
+ const session = await decrypt(cookie);
+
+ if (!session?.userId) {
+ return await redirectUnauthorized(req);
+ }
+
+ return NextResponse.next();
+}
+
+export const config = {
+ matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'],
+}
diff --git a/packages/devextreme-cli/src/templates/nextjs/application/src/theme.tsx b/packages/devextreme-cli/src/templates/nextjs/application/src/theme.tsx
new file mode 100644
index 000000000..3123f707c
--- /dev/null
+++ b/packages/devextreme-cli/src/templates/nextjs/application/src/theme.tsx
@@ -0,0 +1,66 @@
+'use client'
+import React, { useCallback, useMemo, useState, useLayoutEffect } from 'react';
+
+const themes = ['light', 'dark'];
+const themeClassNamePrefix = 'dx-swatch-';
+let currentTheme = getNextTheme();
+
+function getNextTheme(theme = '') {
+ return themes[themes.indexOf(theme) + 1] || themes[0];
+}
+
+function getCurrentTheme() {
+ return currentTheme;
+}
+
+function toggleTheme(prevTheme<%=#isTypeScript%>: string<%=/isTypeScript%>) {
+ const isCurrentThemeDark = prevTheme === 'dark';
+ const newTheme = getNextTheme(prevTheme);
+
+ if (typeof window !== 'undefined') {
+ document.body.classList.replace(
+ themeClassNamePrefix + prevTheme,
+ themeClassNamePrefix + newTheme
+ );
+
+ const additionalClassNamePrefix = themeClassNamePrefix + 'additional';
+ const additionalClassNamePostfix = isCurrentThemeDark ? '-' + prevTheme : '';
+ const additionalClassName = `${additionalClassNamePrefix}${additionalClassNamePostfix}`
+
+ document.body
+ .querySelector(`.${additionalClassName}`)?.classList
+ .replace(additionalClassName, additionalClassNamePrefix + (isCurrentThemeDark ? '' : '-dark'));
+
+ currentTheme = newTheme;
+ }
+
+ return newTheme;
+}
+
+export function useThemeContext() {
+ const [theme, setTheme] = useState(getCurrentTheme());
+ const switchTheme = useCallback(() => setTheme((currentTheme) => toggleTheme(currentTheme)), []);
+ const isDark = useCallback(()<%=#isTypeScript%>: boolean<%=/isTypeScript%> => {
+ return currentTheme === 'dark';
+ }, []);
+
+ useLayoutEffect(() => {
+ if (typeof window !== 'undefined' && !document.body.className.includes(themeClassNamePrefix)) {
+ document.body.classList.add(themeClassNamePrefix + theme);
+ }
+ }, [theme]);
+
+ return useMemo(()=> ({ theme, switchTheme, isDark }), [theme, switchTheme, isDark]);
+}
+
+export const ThemeContext = React.createContext<%=#isTypeScript%> | null><%=/isTypeScript%>(null);
+
+export const ThemeProvider = ({ children }<%=#isTypeScript%>: React.PropsWithChildren<%=/isTypeScript%>) => {
+ const themeContext = useThemeContext();
+
+ return (
+
+ { children }
+
+ );
+};
diff --git a/packages/devextreme-cli/src/templates/nextjs/application/src/themes/metadata.additional.dark.json b/packages/devextreme-cli/src/templates/nextjs/application/src/themes/metadata.additional.dark.json
new file mode 100644
index 000000000..0835b919e
--- /dev/null
+++ b/packages/devextreme-cli/src/templates/nextjs/application/src/themes/metadata.additional.dark.json
@@ -0,0 +1,11 @@
+{
+ "items": [],
+ "baseTheme": "fluent.blue.dark",
+ "assetsBasePath": "../../../node_modules/devextreme/dist/css/",
+ "outputColorScheme": "additional-dark",
+ "makeSwatch": true,
+ "base": true,
+ "widgets": [
+ "treeview"
+ ]
+}
diff --git a/packages/devextreme-cli/src/templates/nextjs/application/src/themes/metadata.additional.json b/packages/devextreme-cli/src/templates/nextjs/application/src/themes/metadata.additional.json
new file mode 100644
index 000000000..277084b16
--- /dev/null
+++ b/packages/devextreme-cli/src/templates/nextjs/application/src/themes/metadata.additional.json
@@ -0,0 +1,11 @@
+{
+ "items": [],
+ "baseTheme": "fluent.blue.light",
+ "assetsBasePath": "../../../node_modules/devextreme/dist/css/",
+ "outputColorScheme": "additional",
+ "makeSwatch": true,
+ "base": true,
+ "widgets": [
+ "treeview"
+ ]
+}
diff --git a/packages/devextreme-cli/src/templates/nextjs/application/src/themes/metadata.base.dark.json b/packages/devextreme-cli/src/templates/nextjs/application/src/themes/metadata.base.dark.json
new file mode 100644
index 000000000..5a863b85c
--- /dev/null
+++ b/packages/devextreme-cli/src/templates/nextjs/application/src/themes/metadata.base.dark.json
@@ -0,0 +1,8 @@
+{
+ "items": [],
+ "baseTheme": "fluent.blue.dark",
+ "assetsBasePath": "../../../node_modules/devextreme/dist/css/",
+ "outputColorScheme": "dark",
+ "base": true,
+ "makeSwatch": true
+}
diff --git a/packages/devextreme-cli/src/templates/nextjs/application/src/themes/metadata.base.json b/packages/devextreme-cli/src/templates/nextjs/application/src/themes/metadata.base.json
new file mode 100644
index 000000000..57fe3f826
--- /dev/null
+++ b/packages/devextreme-cli/src/templates/nextjs/application/src/themes/metadata.base.json
@@ -0,0 +1,7 @@
+{
+ "items": [],
+ "baseTheme": "fluent.blue.light",
+ "assetsBasePath": "../../../node_modules/devextreme/dist/css/",
+ "outputColorScheme": "base",
+ "base": true
+}
diff --git a/packages/devextreme-cli/src/templates/nextjs/application/src/types.tsx b/packages/devextreme-cli/src/templates/nextjs/application/src/types.tsx
new file mode 100644
index 000000000..fd87bd8f5
--- /dev/null
+++ b/packages/devextreme-cli/src/templates/nextjs/application/src/types.tsx
@@ -0,0 +1,60 @@
+import { TreeViewTypes } from 'devextreme-react/tree-view';
+import { ButtonTypes } from 'devextreme-react/button';
+
+export interface HeaderProps {
+ menuToggleEnabled: boolean;
+ title?: string;
+ toggleMenu: (e: ButtonTypes.ClickEvent) => void;
+}
+
+export interface SideNavigationMenuProps {
+ selectedItemChanged: (e: TreeViewTypes.ItemClickEvent) => void;
+ openMenu: (e: React.PointerEvent) => void;
+ compactMode: boolean;
+ onMenuReady?: (e: TreeViewTypes.ContentReadyEvent) => void;
+}
+
+export interface UserPanelProps {
+ menuMode: 'context' | 'list';
+}
+
+export type SessionPayload = {
+ userId: string;
+ expiresAt: Date;
+}
+
+export interface User {
+ email: string;
+ avatarUrl: string;
+}
+
+export type AuthContextType = {
+ user?: User;
+ signIn: (email: string, password: string) => Promise<{isOk: boolean, data?: User, message?: string}>;
+ signOut: () => void;
+ loading: boolean;
+}
+
+export interface SideNavToolbarProps {
+ title: string;
+}
+
+export interface SingleCardProps {
+ title?: string;
+ description?: string;
+}
+
+export type Handle = () => void;
+
+interface NavigationData {
+ currentPath: string;
+}
+
+export type NavigationContextType = {
+ setNavigationData?: ({ currentPath }: NavigationData) => void;
+ navigationData: NavigationData;
+}
+
+export type ValidationType = {
+ value: string;
+}
diff --git a/packages/devextreme-cli/src/templates/nextjs/application/src/utils/default-user.tsx b/packages/devextreme-cli/src/templates/nextjs/application/src/utils/default-user.tsx
new file mode 100644
index 000000000..1191f1095
--- /dev/null
+++ b/packages/devextreme-cli/src/templates/nextjs/application/src/utils/default-user.tsx
@@ -0,0 +1,7 @@
+const userInfo = {
+ id: '1',
+ email: 'sandra@example.com',
+ avatarUrl: 'https://js.devexpress.com/Demos/WidgetsGallery/JSDemos/images/employees/06.png'
+}
+
+export default userInfo;
diff --git a/packages/devextreme-cli/src/templates/nextjs/application/src/utils/media-query.tsx b/packages/devextreme-cli/src/templates/nextjs/application/src/utils/media-query.tsx
new file mode 100644
index 000000000..198c38efa
--- /dev/null
+++ b/packages/devextreme-cli/src/templates/nextjs/application/src/utils/media-query.tsx
@@ -0,0 +1,56 @@
+'use client'
+import { useState, useCallback, useEffect } from 'react';
+<%=#isTypeScript%>import type { Handle } from '@/types';
+<%=/isTypeScript%>
+export const useScreenSize = () => {
+ const [screenSize, setScreenSize] = useState(getScreenSize());
+ const onSizeChanged = useCallback(() => {
+ setScreenSize(getScreenSize());
+ }, []);
+
+ useEffect(() => {
+ subscribe(onSizeChanged);
+
+ return () => {
+ unsubscribe(onSizeChanged);
+ };
+ }, [onSizeChanged]);
+
+ return screenSize;
+};
+
+let handlers<%=#isTypeScript%>: Handle[]<%=/isTypeScript%> = [];
+let xSmallMedia<%=#isTypeScript%>: MediaQueryList<%=/isTypeScript%>;
+let smallMedia<%=#isTypeScript%>: MediaQueryList<%=/isTypeScript%>;
+let mediumMedia<%=#isTypeScript%>: MediaQueryList<%=/isTypeScript%>;
+let largeMedia<%=#isTypeScript%>: MediaQueryList<%=/isTypeScript%>;
+
+if (typeof window !== 'undefined') {
+ xSmallMedia = window.matchMedia('(max-width: 599.99px)');
+ smallMedia = window.matchMedia('(min-width: 600px) and (max-width: 959.99px)');
+ mediumMedia = window.matchMedia('(min-width: 960px) and (max-width: 1279.99px)');
+ largeMedia = window.matchMedia('(min-width: 1280px)');
+
+ [xSmallMedia, smallMedia, mediumMedia, largeMedia].forEach(media => {
+ media.addListener((e) => {
+ if(e.matches) {
+ handlers.forEach(handler => handler())
+ }
+ });
+ });
+}
+
+const subscribe = (handler<%=#isTypeScript%>: Handle<%=/isTypeScript%>) => handlers.push(handler);
+
+const unsubscribe = (handler<%=#isTypeScript%>: Handle<%=/isTypeScript%>) => {
+ handlers = handlers.filter(item => item !== handler);
+};
+
+function getScreenSize() {
+ return {
+ isXSmall: xSmallMedia?.matches,
+ isSmall: smallMedia?.matches,
+ isMedium: mediumMedia?.matches,
+ isLarge: largeMedia?.matches
+ };
+}
diff --git a/packages/devextreme-cli/src/templates/nextjs/application/src/variables.scss b/packages/devextreme-cli/src/templates/nextjs/application/src/variables.scss
new file mode 100644
index 000000000..4f7116123
--- /dev/null
+++ b/packages/devextreme-cli/src/templates/nextjs/application/src/variables.scss
@@ -0,0 +1,53 @@
+@use 'sass:meta';
+@use 'sass:color';
+@use 'sass:map';
+@use "./themes/generated/variables.base.scss" as variablesBase;
+@use "./themes/generated/variables.base.dark.scss" as variablesBaseDark;
+@use "./themes/generated/variables.additional.scss" as variablesAdditional;
+@use "./themes/generated/variables.additional.dark.scss" as variablesAdditionalDark;
+
+@mixin theme-variables($theme-name) {
+ $theme: meta.module-variables($theme-name);
+ $base-text-color: map.get($theme, 'base-text-color');
+ $base-bg: map.get($theme, 'base-bg');
+
+ --base-text-color: #{$base-text-color};
+ --base-bg: #{$base-bg};
+ --base-bg-darken-5: #{color.adjust($base-bg, $lightness: -5%)};
+ --base-accent: #{map.get($theme, 'base-accent')};
+ --base-text-color-alpha-7: #{rgba($base-text-color, color.alpha($base-text-color) * 0.7)};
+}
+
+:root {
+ body {
+ @include theme-variables('variablesBase');
+
+ --footer-border-color: rgba(224, 224, 224, 1);
+ --plus-icon-color: #242424;
+ --devextreme-logo-color: #596C7D;
+ --vue-logo-text-color: #35495E;
+
+ --shadow-color-first: rgba(0, 0, 0, 0.06);
+ --shadow-color-second: rgba(0, 0, 0, 0.12);
+ }
+
+ .dx-swatch-additional {
+ @include theme-variables('variablesAdditional');
+ }
+
+ .dx-swatch-dark {
+ @include theme-variables('variablesBaseDark');;
+
+ --plus-icon-color: #fff;
+ --devextreme-logo-color: #fff;
+ --vue-logo-text-color: #fff;
+
+ --shadow-color-first: rgba(0, 0, 0, 0.12);
+ --shadow-color-second: rgba(0, 0, 0, 0.24);
+ --footer-border-color: rgba(97, 97, 97, 1);
+ }
+
+ .dx-swatch-additional-dark {
+ @include theme-variables('variablesAdditionalDark');
+ }
+}
diff --git a/packages/devextreme-cli/src/templates/nextjs/page/page.scss b/packages/devextreme-cli/src/templates/nextjs/page/page.scss
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/devextreme-cli/src/templates/nextjs/page/page.tsx b/packages/devextreme-cli/src/templates/nextjs/page/page.tsx
new file mode 100644
index 000000000..84355ca35
--- /dev/null
+++ b/packages/devextreme-cli/src/templates/nextjs/page/page.tsx
@@ -0,0 +1,13 @@
+import React from 'react';
+import './<%=pageName%>.scss';
+
+export default function Page() {
+ return
+ <%=title%>
+
+
+ Put your content here
+
+
+
+};
diff --git a/packages/devextreme-cli/src/templates/nextjs/sample-pages/home/home.scss b/packages/devextreme-cli/src/templates/nextjs/sample-pages/home/home.scss
new file mode 100644
index 000000000..f440f973f
--- /dev/null
+++ b/packages/devextreme-cli/src/templates/nextjs/sample-pages/home/home.scss
@@ -0,0 +1,37 @@
+.logos-container {
+ margin: 0 0 40px 0;
+
+ svg {
+ display: inline-block;
+ }
+}
+
+.devextreme-logo {
+ width: 255px;
+ height: 60px;
+
+ > path {
+ fill: var(--devextreme-logo-color);
+ }
+
+ .by-devexpress {
+ path {
+ fill: var(--devextreme-logo-color);
+ }
+ }
+}
+
+.react-logo {
+ width: 184px;
+ height: 60px;
+}
+
+.plus {
+ margin: 20px;
+ width: 22px;
+ height: 22px;
+
+ path {
+ fill: var(--plus-icon-color);
+ }
+}
diff --git a/packages/devextreme-cli/src/templates/nextjs/sample-pages/home/page.tsx b/packages/devextreme-cli/src/templates/nextjs/sample-pages/home/page.tsx
new file mode 100644
index 000000000..1b76cc188
--- /dev/null
+++ b/packages/devextreme-cli/src/templates/nextjs/sample-pages/home/page.tsx
@@ -0,0 +1,101 @@
+'use client'
+import React from 'react';
+import './home.scss';
+
+export default function Page() {
+ return (
+
+ Home
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Thanks for using the DevExtreme React App Template.
+
+ This application was built using
+ NextJs
+ and
+ DevExtreme CLI
+ and includes the following DevExtreme components:
+
+
+
+
To customize your DevExtreme React application further, please refer to the following help topics:
+
+
+
+
+ For technical content related to DevExtreme React components, feel free to explore our
+ online documentation
+ and
+ technical demos .
+
+
+
+
+)}
diff --git a/packages/devextreme-cli/src/templates/nextjs/sample-pages/profile/page.tsx b/packages/devextreme-cli/src/templates/nextjs/sample-pages/profile/page.tsx
new file mode 100644
index 000000000..c372d4221
--- /dev/null
+++ b/packages/devextreme-cli/src/templates/nextjs/sample-pages/profile/page.tsx
@@ -0,0 +1,61 @@
+'use client'
+import React, { useState } from 'react';
+import './profile.scss';
+import Form from 'devextreme-react/form';
+import Image from 'next/image';
+
+export default function Page() {
+ const [notes, setNotes] = useState(
+ 'Sandra is a CPA and has been our controller since 2008. She loves to interact with staff so if you`ve not met her, be certain to say hi.\r\n\r\nSandra has 2 daughters both of whom are accomplished gymnasts.'
+ );
+ const employee = {
+ ID: 7,
+ FirstName: 'Sandra',
+ LastName: 'Johnson',
+ Prefix: 'Mrs.',
+ Position: 'Controller',
+ Picture: 'images/employees/06.png',
+ BirthDate: new Date('1974/11/5'),
+ HireDate: new Date('2005/05/11'),
+ Notes: notes,
+ Address: '4600 N Virginia Rd.'
+ };
+
+ return (
+
+ Profile
+
+
+
+
+
+
+ );
+}
+
+const colCountByScreen = {
+ xs: 1,
+ sm: 2,
+ md: 3,
+ lg: 4
+};
diff --git a/packages/devextreme-cli/src/templates/nextjs/sample-pages/profile/profile.scss b/packages/devextreme-cli/src/templates/nextjs/sample-pages/profile/profile.scss
new file mode 100644
index 000000000..aa84f9927
--- /dev/null
+++ b/packages/devextreme-cli/src/templates/nextjs/sample-pages/profile/profile.scss
@@ -0,0 +1,19 @@
+.form-avatar {
+ float: left;
+ height: 120px;
+ width: 120px;
+ margin-right: 20px;
+ border: 1px solid rgba(0, 0, 0, 0.1);
+ background-size: contain;
+ background-repeat: no-repeat;
+ background-position: center;
+ background-color: #fff;
+ overflow: hidden;
+
+ img {
+ height: 120px;
+ width: auto;
+ display: block;
+ margin: 0 auto;
+ }
+}
diff --git a/packages/devextreme-cli/src/templates/nextjs/sample-pages/tasks/page.tsx b/packages/devextreme-cli/src/templates/nextjs/sample-pages/tasks/page.tsx
new file mode 100644
index 000000000..ece5f2163
--- /dev/null
+++ b/packages/devextreme-cli/src/templates/nextjs/sample-pages/tasks/page.tsx
@@ -0,0 +1,112 @@
+'use client'
+import React from 'react';
+import { DataSource } from 'devextreme-react/common/data';
+import DataGrid, {
+ Column,
+ Pager,
+ Paging,
+ FilterRow,
+ Lookup
+} from 'devextreme-react/data-grid';
+import './tasks.scss';
+
+export default function Task() {
+ return (
+
+ Tasks
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+)}
+
+const dataSource = new DataSource({
+ store: {
+ version: 2,
+ type: 'odata',
+ key: 'Task_ID',
+ url: 'https://js.devexpress.com/Demos/DevAV/odata/Tasks'
+ },
+ expand: 'ResponsibleEmployee',
+ select: [
+ 'Task_ID',
+ 'Task_Subject',
+ 'Task_Start_Date',
+ 'Task_Due_Date',
+ 'Task_Status',
+ 'Task_Priority',
+ 'Task_Completion',
+ 'ResponsibleEmployee/Employee_Full_Name'
+ ]
+});
+
+const priorities = [
+ { name: 'High', value: 4 },
+ { name: 'Urgent', value: 3 },
+ { name: 'Normal', value: 2 },
+ { name: 'Low', value: 1 }
+];
diff --git a/packages/devextreme-cli/src/templates/nextjs/sample-pages/tasks/tasks.scss b/packages/devextreme-cli/src/templates/nextjs/sample-pages/tasks/tasks.scss
new file mode 100644
index 000000000..e03dfcfac
--- /dev/null
+++ b/packages/devextreme-cli/src/templates/nextjs/sample-pages/tasks/tasks.scss
@@ -0,0 +1,3 @@
+.dx-datagrid-filter-row {
+ background-color: transparent;
+}
diff --git a/packages/devextreme-cli/src/templates/cra-template-typescript/template/public/favicon.ico b/packages/devextreme-cli/src/templates/react/application/public/favicon.ico
similarity index 100%
rename from packages/devextreme-cli/src/templates/cra-template-typescript/template/public/favicon.ico
rename to packages/devextreme-cli/src/templates/react/application/public/favicon.ico
diff --git a/packages/devextreme-cli/src/templates/cra-template/template/public/logo192.png b/packages/devextreme-cli/src/templates/react/application/public/logo192.png
similarity index 100%
rename from packages/devextreme-cli/src/templates/cra-template/template/public/logo192.png
rename to packages/devextreme-cli/src/templates/react/application/public/logo192.png
diff --git a/packages/devextreme-cli/src/templates/cra-template/template/public/logo512.png b/packages/devextreme-cli/src/templates/react/application/public/logo512.png
similarity index 100%
rename from packages/devextreme-cli/src/templates/cra-template/template/public/logo512.png
rename to packages/devextreme-cli/src/templates/react/application/public/logo512.png
diff --git a/packages/devextreme-cli/src/templates/cra-template/template/public/manifest.json b/packages/devextreme-cli/src/templates/react/application/public/manifest.json
similarity index 100%
rename from packages/devextreme-cli/src/templates/cra-template/template/public/manifest.json
rename to packages/devextreme-cli/src/templates/react/application/public/manifest.json
diff --git a/packages/devextreme-cli/src/templates/cra-template/template/public/robots.txt b/packages/devextreme-cli/src/templates/react/application/public/robots.txt
similarity index 100%
rename from packages/devextreme-cli/src/templates/cra-template/template/public/robots.txt
rename to packages/devextreme-cli/src/templates/react/application/public/robots.txt
diff --git a/packages/devextreme-cli/src/templates/react/application/src/App.test.tsx b/packages/devextreme-cli/src/templates/react/application/src/App.test.tsx
deleted file mode 100644
index 98f3f6bef..000000000
--- a/packages/devextreme-cli/src/templates/react/application/src/App.test.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import './matchMediaMock';
-import { render } from '@testing-library/react';
-import { screen } from '@testing-library/dom';
-import App from './App';
-import { act } from 'react-dom/test-utils';
-
-describe("App", () => {
- test('renders learn react link', async () => {
- await act( async () => { render( ) });
- const linkElement = screen.getByText(/create react app/i);
- expect(linkElement).toBeInTheDocument();
- });
-})
\ No newline at end of file
diff --git a/packages/devextreme-cli/src/templates/react/application/src/App.tsx b/packages/devextreme-cli/src/templates/react/application/src/App.tsx
index ba8178d88..bbc2abfdc 100644
--- a/packages/devextreme-cli/src/templates/react/application/src/App.tsx
+++ b/packages/devextreme-cli/src/templates/react/application/src/App.tsx
@@ -1,4 +1,3 @@
-import React from 'react';
import { HashRouter as Router } from 'react-router-dom';
import './dx-styles.scss';
import LoadPanel from 'devextreme-react/load-panel';
diff --git a/packages/devextreme-cli/src/templates/react/application/src/components/change-password-form/ChangePasswordForm.tsx b/packages/devextreme-cli/src/templates/react/application/src/components/change-password-form/ChangePasswordForm.tsx
index b6e76ade8..f04886bbc 100644
--- a/packages/devextreme-cli/src/templates/react/application/src/components/change-password-form/ChangePasswordForm.tsx
+++ b/packages/devextreme-cli/src/templates/react/application/src/components/change-password-form/ChangePasswordForm.tsx
@@ -1,4 +1,4 @@
-import React, { useState, useRef, useCallback } from 'react';
+import <%=#isTypeScript%>React, <%=/isTypeScript%>{ useState, useRef, useCallback } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import Form, {
Item,
@@ -19,7 +19,7 @@ export default function ChangePasswordForm() {
const formData = useRef({ password: '' });
const { recoveryCode } = useParams();
- const onSubmit = useCallback(async (e<%=#isTypeScript%>: any<%=/isTypeScript%>) => {
+ const onSubmit = useCallback(async (e<%=#isTypeScript%>: React.FormEvent<%=/isTypeScript%>) => {
e.preventDefault();
const { password } = formData.current;
setLoading(true);
diff --git a/packages/devextreme-cli/src/templates/react/application/src/components/create-account-form/CreateAccountForm.scss b/packages/devextreme-cli/src/templates/react/application/src/components/create-account-form/CreateAccountForm.scss
index d6fb5410a..830ab7065 100644
--- a/packages/devextreme-cli/src/templates/react/application/src/components/create-account-form/CreateAccountForm.scss
+++ b/packages/devextreme-cli/src/templates/react/application/src/components/create-account-form/CreateAccountForm.scss
@@ -1,5 +1,3 @@
-@import "../../themes/generated/variables.base.scss";
-
.create-account-form {
.policy-info {
color: var(--base-text-color-alpha-7);
diff --git a/packages/devextreme-cli/src/templates/react/application/src/components/create-account-form/CreateAccountForm.tsx b/packages/devextreme-cli/src/templates/react/application/src/components/create-account-form/CreateAccountForm.tsx
index b01342f02..983f5c849 100644
--- a/packages/devextreme-cli/src/templates/react/application/src/components/create-account-form/CreateAccountForm.tsx
+++ b/packages/devextreme-cli/src/templates/react/application/src/components/create-account-form/CreateAccountForm.tsx
@@ -1,4 +1,4 @@
-import React, { useState, useRef, useCallback } from 'react';
+import <%=#isTypeScript%>React, <%=/isTypeScript%>{ useState, useRef, useCallback } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import Form, {
Item,
@@ -20,7 +20,7 @@ export default function CreateAccountForm() {
const [loading, setLoading] = useState(false);
const formData = useRef({ email: '', password: '' });
- const onSubmit = useCallback(async (e<%=#isTypeScript%>: any<%=/isTypeScript%>) => {
+ const onSubmit = useCallback(async (e<%=#isTypeScript%>: React.FormEvent<%=/isTypeScript%>) => {
e.preventDefault();
const { email, password } = formData.current;
setLoading(true);
diff --git a/packages/devextreme-cli/src/templates/react/application/src/components/footer/Footer.tsx b/packages/devextreme-cli/src/templates/react/application/src/components/footer/Footer.tsx
index b20269670..bc82bfbd5 100644
--- a/packages/devextreme-cli/src/templates/react/application/src/components/footer/Footer.tsx
+++ b/packages/devextreme-cli/src/templates/react/application/src/components/footer/Footer.tsx
@@ -1,4 +1,3 @@
-import React from 'react';
import './Footer.scss';
export default function Footer({ ...rest }) {
diff --git a/packages/devextreme-cli/src/templates/react/application/src/components/header/Header.tsx b/packages/devextreme-cli/src/templates/react/application/src/components/header/Header.tsx
index 347fc555d..f020997de 100644
--- a/packages/devextreme-cli/src/templates/react/application/src/components/header/Header.tsx
+++ b/packages/devextreme-cli/src/templates/react/application/src/components/header/Header.tsx
@@ -1,4 +1,3 @@
-import React from 'react';
import Toolbar, { Item } from 'devextreme-react/toolbar';
import Button from 'devextreme-react/button';
import UserPanel from '../user-panel/UserPanel';
diff --git a/packages/devextreme-cli/src/templates/react/application/src/components/login-form/LoginForm.scss b/packages/devextreme-cli/src/templates/react/application/src/components/login-form/LoginForm.scss
index e8fea8fde..702162654 100644
--- a/packages/devextreme-cli/src/templates/react/application/src/components/login-form/LoginForm.scss
+++ b/packages/devextreme-cli/src/templates/react/application/src/components/login-form/LoginForm.scss
@@ -1,5 +1,3 @@
-@import "../../themes/generated/variables.base.scss";
-
.login-form {
.link {
text-align: center;
diff --git a/packages/devextreme-cli/src/templates/react/application/src/components/login-form/LoginForm.tsx b/packages/devextreme-cli/src/templates/react/application/src/components/login-form/LoginForm.tsx
index 5e150363d..beadddd2e 100644
--- a/packages/devextreme-cli/src/templates/react/application/src/components/login-form/LoginForm.tsx
+++ b/packages/devextreme-cli/src/templates/react/application/src/components/login-form/LoginForm.tsx
@@ -1,4 +1,4 @@
-import React, { useState, useRef, useCallback } from 'react';
+import <%=#isTypeScript%>React, <%=/isTypeScript%>{ useState, useRef, useCallback } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import Form, {
Item,
@@ -21,7 +21,7 @@ export default function LoginForm() {
const [loading, setLoading] = useState(false);
const formData = useRef({ email: '', password: '' });
- const onSubmit = useCallback(async (e<%=#isTypeScript%>: any<%=/isTypeScript%>) => {
+ const onSubmit = useCallback(async (e<%=#isTypeScript%>: React.FormEvent<%=/isTypeScript%>) => {
e.preventDefault();
const { email, password } = formData.current;
setLoading(true);
diff --git a/packages/devextreme-cli/src/templates/react/application/src/components/reset-password-form/ResetPasswordForm.scss b/packages/devextreme-cli/src/templates/react/application/src/components/reset-password-form/ResetPasswordForm.scss
index 8631e7323..f9bf5090b 100644
--- a/packages/devextreme-cli/src/templates/react/application/src/components/reset-password-form/ResetPasswordForm.scss
+++ b/packages/devextreme-cli/src/templates/react/application/src/components/reset-password-form/ResetPasswordForm.scss
@@ -1,5 +1,3 @@
-@import "../../themes/generated/variables.base.scss";
-
.reset-password-form {
.submit-button {
margin-top: 18px;
diff --git a/packages/devextreme-cli/src/templates/react/application/src/components/reset-password-form/ResetPasswordForm.tsx b/packages/devextreme-cli/src/templates/react/application/src/components/reset-password-form/ResetPasswordForm.tsx
index 313e53537..55402df1d 100644
--- a/packages/devextreme-cli/src/templates/react/application/src/components/reset-password-form/ResetPasswordForm.tsx
+++ b/packages/devextreme-cli/src/templates/react/application/src/components/reset-password-form/ResetPasswordForm.tsx
@@ -1,4 +1,4 @@
-import React, { useState, useRef, useCallback } from 'react';
+import <%=#isTypeScript%>React, <%=/isTypeScript%>{ useState, useRef, useCallback } from 'react';
import { Link, useNavigate } from "react-router-dom";
import Form, {
Item,
@@ -20,7 +20,7 @@ export default function ResetPasswordForm() {
const [loading, setLoading] = useState(false);
const formData = useRef({ email: '', password: '' });
- const onSubmit = useCallback(async (e<%=#isTypeScript%>: any<%=/isTypeScript%>) => {
+ const onSubmit = useCallback(async (e<%=#isTypeScript%>: React.FormEvent<%=/isTypeScript%>) => {
e.preventDefault();
const { email } = formData.current;
setLoading(true);
diff --git a/packages/devextreme-cli/src/templates/react/application/src/components/side-navigation-menu/SideNavigationMenu.scss b/packages/devextreme-cli/src/templates/react/application/src/components/side-navigation-menu/SideNavigationMenu.scss
index d6ea2297d..911d90e88 100644
--- a/packages/devextreme-cli/src/templates/react/application/src/components/side-navigation-menu/SideNavigationMenu.scss
+++ b/packages/devextreme-cli/src/templates/react/application/src/components/side-navigation-menu/SideNavigationMenu.scss
@@ -1,4 +1,5 @@
-@use "../../dx-styles.scss" as *;
+@use "../../dx-styles" as *;
+@use "../../utils/patches";
.dx-swatch-additional, .dx-swatch-additional-dark {
&.side-navigation-menu {
@@ -71,6 +72,5 @@
}
.dx-drawer-overlap.pre-init-blink-fix {
- @import "../../utils/patches.scss";
- @include menu-pre-init-patch;
+ @include patches.menu-pre-init-patch;
}
diff --git a/packages/devextreme-cli/src/templates/react/application/src/components/side-navigation-menu/SideNavigationMenu.tsx b/packages/devextreme-cli/src/templates/react/application/src/components/side-navigation-menu/SideNavigationMenu.tsx
index 26866775f..1b8ed001f 100644
--- a/packages/devextreme-cli/src/templates/react/application/src/components/side-navigation-menu/SideNavigationMenu.tsx
+++ b/packages/devextreme-cli/src/templates/react/application/src/components/side-navigation-menu/SideNavigationMenu.tsx
@@ -1,6 +1,6 @@
import React, { useEffect, useRef, useCallback, useMemo, useContext } from 'react';
import { TreeView<%=#isTypeScript%>, TreeViewRef<%=/isTypeScript%> } from 'devextreme-react/tree-view';
-import * as events from 'devextreme/events';
+import * as events from 'devextreme-react/common/core/events';
import { navigation } from '../../app-navigation';
import { useNavigation } from '../../contexts/navigation';
import { useScreenSize } from '../../utils/media-query';
diff --git a/packages/devextreme-cli/src/templates/react/application/src/components/theme-switcher/ThemeSwitcher.tsx b/packages/devextreme-cli/src/templates/react/application/src/components/theme-switcher/ThemeSwitcher.tsx
index bcd0bd535..87b26a376 100644
--- a/packages/devextreme-cli/src/templates/react/application/src/components/theme-switcher/ThemeSwitcher.tsx
+++ b/packages/devextreme-cli/src/templates/react/application/src/components/theme-switcher/ThemeSwitcher.tsx
@@ -1,4 +1,4 @@
-import React, { useCallback, useContext } from 'react';
+import { useCallback, useContext } from 'react';
import Button from 'devextreme-react/button';
import { ThemeContext } from '../../theme';
diff --git a/packages/devextreme-cli/src/templates/react/application/src/components/user-panel/UserPanel.tsx b/packages/devextreme-cli/src/templates/react/application/src/components/user-panel/UserPanel.tsx
index 6c2997d52..50a5c2269 100644
--- a/packages/devextreme-cli/src/templates/react/application/src/components/user-panel/UserPanel.tsx
+++ b/packages/devextreme-cli/src/templates/react/application/src/components/user-panel/UserPanel.tsx
@@ -1,4 +1,4 @@
-import React, { useMemo, useCallback } from 'react';
+import { useMemo, useCallback } from 'react';
import { useNavigate } from "react-router-dom";
import DropDownButton from 'devextreme-react/drop-down-button';
import List from 'devextreme-react/list';
diff --git a/packages/devextreme-cli/src/templates/react/application/src/dx-styles.scss b/packages/devextreme-cli/src/templates/react/application/src/dx-styles.scss
index a9accd9ff..cf0237c2e 100644
--- a/packages/devextreme-cli/src/templates/react/application/src/dx-styles.scss
+++ b/packages/devextreme-cli/src/templates/react/application/src/dx-styles.scss
@@ -1,3 +1,5 @@
+@use "variables.scss" as *;
+
$side-panel-min-width: 60px;
html,
@@ -65,10 +67,10 @@ body {
.responsive-paddings {
padding: 20px;
+ }
- .screen-large & {
- padding: 40px;
- }
+ .screen-large .responsive-paddings {
+ padding: 40px;
}
.dx-card.wide-card {
diff --git a/packages/devextreme-cli/src/templates/cra-template/template/src/index.css b/packages/devextreme-cli/src/templates/react/application/src/index.css
similarity index 100%
rename from packages/devextreme-cli/src/templates/cra-template/template/src/index.css
rename to packages/devextreme-cli/src/templates/react/application/src/index.css
diff --git a/packages/devextreme-cli/src/templates/react/application/src/layouts/side-nav-inner-toolbar/side-nav-inner-toolbar.tsx b/packages/devextreme-cli/src/templates/react/application/src/layouts/side-nav-inner-toolbar/side-nav-inner-toolbar.tsx
index e6335b526..eb0d8b108 100644
--- a/packages/devextreme-cli/src/templates/react/application/src/layouts/side-nav-inner-toolbar/side-nav-inner-toolbar.tsx
+++ b/packages/devextreme-cli/src/templates/react/application/src/layouts/side-nav-inner-toolbar/side-nav-inner-toolbar.tsx
@@ -3,7 +3,7 @@ import Drawer from 'devextreme-react/drawer';
import { ScrollView<%=#isTypeScript%>, ScrollViewRef<%=/isTypeScript%> } from 'devextreme-react/scroll-view';
import Toolbar, { Item } from 'devextreme-react/toolbar';
import React, { useState, useCallback, useRef } from 'react';
-import { useNavigate } from 'react-router';
+import { useNavigate } from 'react-router-dom';
import { Header, SideNavigationMenu, Footer } from '../../components';
import './side-nav-inner-toolbar.scss';
import { useScreenSize } from '../../utils/media-query';
@@ -84,13 +84,19 @@ export default function SideNavInnerToolbar({ title, children }<%=#isTypeScript%
/>
- {React.Children.map(children, (item<%=#isTypeScript%>: any<%=/isTypeScript%>) => {
- return item.type !== Footer && item;
+ {React.Children.map(children, (item) => {
+ if (<%=#isTypeScript%>React.isValidElement(item) && <%=/isTypeScript%>item.type !== Footer) {
+ return item;
+ }
+ return null;
})}
- {React.Children.map(children, (item<%=#isTypeScript%>: any<%=/isTypeScript%>) => {
- return item.type === Footer && item;
+ {React.Children.map(children, (item) => {
+ if (<%=#isTypeScript%>React.isValidElement(item) && <%=/isTypeScript%>item.type === Footer) {
+ return item;
+ }
+ return null;
})}
diff --git a/packages/devextreme-cli/src/templates/react/application/src/layouts/side-nav-outer-toolbar/side-nav-outer-toolbar.tsx b/packages/devextreme-cli/src/templates/react/application/src/layouts/side-nav-outer-toolbar/side-nav-outer-toolbar.tsx
index 637bf6e58..7d32cdad4 100644
--- a/packages/devextreme-cli/src/templates/react/application/src/layouts/side-nav-outer-toolbar/side-nav-outer-toolbar.tsx
+++ b/packages/devextreme-cli/src/templates/react/application/src/layouts/side-nav-outer-toolbar/side-nav-outer-toolbar.tsx
@@ -1,7 +1,7 @@
import Drawer from 'devextreme-react/drawer';
import { ScrollView<%=#isTypeScript%>, ScrollViewRef<%=/isTypeScript%> } from 'devextreme-react/scroll-view';
import React, { useState, useCallback, useRef } from 'react';
-import { useNavigate } from 'react-router';
+import { useNavigate } from 'react-router-dom';
import { Header, SideNavigationMenu, Footer } from '../../components';
import './side-nav-outer-toolbar.scss';
import { useScreenSize } from '../../utils/media-query';
@@ -83,13 +83,19 @@ export default function SideNavOuterToolbar({ title, children }<%=#isTypeScript%
- {React.Children.map(children, (item<%=#isTypeScript%>: any<%=/isTypeScript%>) => {
- return item.type !== Footer && item;
+ {React.Children.map(children, (item) => {
+ if (<%=#isTypeScript%>React.isValidElement(item) && <%=/isTypeScript%>item.type !== Footer) {
+ return item;
+ }
+ return null;
})}
- {React.Children.map(children, (item<%=#isTypeScript%>: any<%=/isTypeScript%>) => {
- return item.type === Footer && item;
+ {React.Children.map(children, (item) => {
+ if (<%=#isTypeScript%>React.isValidElement(item) && <%=/isTypeScript%>item.type === Footer) {
+ return item;
+ }
+ return null;
})}
diff --git a/packages/devextreme-cli/src/templates/react/application/src/layouts/single-card/single-card.scss b/packages/devextreme-cli/src/templates/react/application/src/layouts/single-card/single-card.scss
index 32fc3b6b2..e4d0f96bb 100644
--- a/packages/devextreme-cli/src/templates/react/application/src/layouts/single-card/single-card.scss
+++ b/packages/devextreme-cli/src/templates/react/application/src/layouts/single-card/single-card.scss
@@ -1,5 +1,3 @@
-@import "../../themes/generated/variables.base.scss";
-
.single-card {
width: 100%;
height: 100%;
diff --git a/packages/devextreme-cli/src/templates/react/application/src/layouts/single-card/single-card.tsx b/packages/devextreme-cli/src/templates/react/application/src/layouts/single-card/single-card.tsx
index 879233c1c..1e207fefc 100644
--- a/packages/devextreme-cli/src/templates/react/application/src/layouts/single-card/single-card.tsx
+++ b/packages/devextreme-cli/src/templates/react/application/src/layouts/single-card/single-card.tsx
@@ -1,4 +1,3 @@
-import React from 'react';
import ScrollView from 'devextreme-react/scroll-view';
import './single-card.scss';
<%=#isTypeScript%>import type { SingleCardProps } from '../../types';<%=/isTypeScript%>
diff --git a/packages/devextreme-cli/src/templates/react/application/src/matchMediaMock.tsx b/packages/devextreme-cli/src/templates/react/application/src/matchMediaMock.tsx
deleted file mode 100644
index 9b289b6a3..000000000
--- a/packages/devextreme-cli/src/templates/react/application/src/matchMediaMock.tsx
+++ /dev/null
@@ -1,14 +0,0 @@
-Object.defineProperty(window, 'matchMedia', {
- writable: true,
- value: jest.fn().mockImplementation(query => ({
- matches: false,
- media: query,
- onchange: null,
- addListener: jest.fn(), // deprecated
- removeListener: jest.fn(), // deprecated
- addEventListener: jest.fn(),
- removeEventListener: jest.fn(),
- dispatchEvent: jest.fn(),
- })),
-});
-export {};
\ No newline at end of file
diff --git a/packages/devextreme-cli/src/templates/react/application/src/polyfills.tsx b/packages/devextreme-cli/src/templates/react/application/src/polyfills.tsx
deleted file mode 100644
index 89b637058..000000000
--- a/packages/devextreme-cli/src/templates/react/application/src/polyfills.tsx
+++ /dev/null
@@ -1,2 +0,0 @@
-import 'react-app-polyfill/ie11';
-import 'react-app-polyfill/stable';
diff --git a/packages/devextreme-cli/src/templates/react/application/src/types.tsx b/packages/devextreme-cli/src/templates/react/application/src/types.tsx
index b152a5c41..8f75467f8 100644
--- a/packages/devextreme-cli/src/templates/react/application/src/types.tsx
+++ b/packages/devextreme-cli/src/templates/react/application/src/types.tsx
@@ -1,6 +1,5 @@
import { TreeViewTypes } from 'devextreme-react/tree-view';
import { ButtonTypes } from 'devextreme-react/button';
-import React from 'react';
export interface HeaderProps {
menuToggleEnabled: boolean;
@@ -53,4 +52,4 @@ export type NavigationContextType = {
export type ValidationType = {
value: string;
-}
\ No newline at end of file
+}
diff --git a/packages/devextreme-cli/src/templates/react/application/src/utils/media-query.tsx b/packages/devextreme-cli/src/templates/react/application/src/utils/media-query.tsx
index abe116dbe..9432f1671 100644
--- a/packages/devextreme-cli/src/templates/react/application/src/utils/media-query.tsx
+++ b/packages/devextreme-cli/src/templates/react/application/src/utils/media-query.tsx
@@ -44,7 +44,9 @@ const largeMedia = window.matchMedia('(min-width: 1280px)');
[xSmallMedia, smallMedia, mediumMedia, largeMedia].forEach(media => {
media.addListener((e) => {
- e.matches && handlers.forEach(handler => handler());
+ if(e.matches) {
+ handlers.forEach(handler => handler())
+ }
});
});
diff --git a/packages/devextreme-cli/src/templates/react/application/src/utils/patches.scss b/packages/devextreme-cli/src/templates/react/application/src/utils/patches.scss
index 0b9c544c0..a0e75b42e 100644
--- a/packages/devextreme-cli/src/templates/react/application/src/utils/patches.scss
+++ b/packages/devextreme-cli/src/templates/react/application/src/utils/patches.scss
@@ -1,12 +1,12 @@
-@import "../themes/generated/variables.additional.scss";
+@use "../themes/generated/variables.additional.scss" as *;
@mixin menu-pre-init-patch {
$menuMinSize: 60px;
-
+
.dx-drawer-content {
padding-left: $menuMinSize;
}
-
+
.dx-drawer-panel-content.dx-overlay::before {
content: "";
width: $menuMinSize;
@@ -15,7 +15,7 @@
position: absolute;
background-color: $base-bg;
}
-
+
.dx-overlay-content {
width: $menuMinSize !important;
}
diff --git a/packages/devextreme-cli/src/templates/react/sample-pages/home/home.scss b/packages/devextreme-cli/src/templates/react/sample-pages/home/home.scss
index d26e0dc76..f440f973f 100644
--- a/packages/devextreme-cli/src/templates/react/sample-pages/home/home.scss
+++ b/packages/devextreme-cli/src/templates/react/sample-pages/home/home.scss
@@ -1,5 +1,3 @@
-@use "../../variables.scss" as *;
-
.logos-container {
margin: 0 0 40px 0;
diff --git a/packages/devextreme-cli/src/templates/react/sample-pages/home/home.tsx b/packages/devextreme-cli/src/templates/react/sample-pages/home/home.tsx
index 852e02a51..a79858f2a 100644
--- a/packages/devextreme-cli/src/templates/react/sample-pages/home/home.tsx
+++ b/packages/devextreme-cli/src/templates/react/sample-pages/home/home.tsx
@@ -68,7 +68,7 @@ export default function Home() {
Thanks for using the DevExtreme React App Template.
This application was built using
- Create React App
+ create-vite
and
DevExtreme CLI
and includes the following DevExtreme components:
diff --git a/packages/devextreme-cli/src/templates/react/sample-pages/tasks/tasks.tsx b/packages/devextreme-cli/src/templates/react/sample-pages/tasks/tasks.tsx
index 796e40f96..d768d19e0 100644
--- a/packages/devextreme-cli/src/templates/react/sample-pages/tasks/tasks.tsx
+++ b/packages/devextreme-cli/src/templates/react/sample-pages/tasks/tasks.tsx
@@ -1,5 +1,5 @@
import React from 'react';
-import 'devextreme/data/odata/store';
+import { DataSource } from 'devextreme-react/common/data';
import DataGrid, {
Column,
Pager,
@@ -16,7 +16,7 @@ export default function Task() {
as any<%=/isTypeScript%>}
+ dataSource={dataSource}
showBorders={false}
focusedRowEnabled={true}
defaultFocusedRowIndex={0}
@@ -83,7 +83,7 @@ export default function Task() {
)}
-const dataSource = {
+const dataSource = new DataSource({
store: {
version: 2,
type: 'odata',
@@ -101,7 +101,7 @@ const dataSource = {
'Task_Completion',
'ResponsibleEmployee/Employee_Full_Name'
]
-};
+});
const priorities = [
{ name: 'High', value: 4 },
diff --git a/packages/devextreme-cli/src/templates/vue-v3/application/src/dx-styles.scss b/packages/devextreme-cli/src/templates/vue-v3/application/src/dx-styles.scss
index dc06a6255..f00e2fa43 100644
--- a/packages/devextreme-cli/src/templates/vue-v3/application/src/dx-styles.scss
+++ b/packages/devextreme-cli/src/templates/vue-v3/application/src/dx-styles.scss
@@ -40,12 +40,13 @@ $side-panel-min-width: 60px;
margin-top: 20px;
}
+
.responsive-paddings {
padding: 20px;
+ }
- .screen-large & {
- padding: 40px;
- }
+ .screen-large .responsive-paddings {
+ padding: 40px;
}
.dx-card.wide-card {
diff --git a/packages/devextreme-cli/src/templates/vue-v3/sample-pages/tasks-page.vue b/packages/devextreme-cli/src/templates/vue-v3/sample-pages/tasks-page.vue
index 898e010ca..2de95b5cd 100644
--- a/packages/devextreme-cli/src/templates/vue-v3/sample-pages/tasks-page.vue
+++ b/packages/devextreme-cli/src/templates/vue-v3/sample-pages/tasks-page.vue
@@ -80,7 +80,7 @@