├── src ├── app-info.js ├── themes │ ├── generated │ │ ├── variables.base.scss │ │ ├── variables.additional.scss │ │ ├── variables.base.dark.scss │ │ └── variables.additional.dark.scss │ ├── metadata.base.json │ ├── metadata.base.dark.json │ ├── metadata.additional.json │ └── metadata.additional.dark.json ├── app-navigation.js ├── components │ ├── theme-switcher.vue │ ├── app-footer.vue │ ├── user-panel.vue │ ├── header-toolbar.vue │ └── side-nav-menu.vue ├── main.js ├── utils │ └── media-query.js ├── theme-service.js ├── dx-styles.scss ├── variables.scss ├── layouts │ ├── single-card.vue │ ├── side-nav-outer-toolbar.vue │ └── side-nav-inner-toolbar.vue ├── auth.js ├── App.vue ├── views │ ├── profile-page.vue │ ├── reset-password-form.vue │ ├── change-password-form.vue │ ├── tasks-page.vue │ ├── login-form.vue │ ├── create-account-form.vue │ └── home-page.vue └── router.js ├── vue.config.js ├── vue-template.png ├── public └── favicon.ico ├── SECURITY.md ├── jsconfig.json ├── .gitignore ├── index.html ├── vite.config.js ├── eslint.config.js ├── package.json ├── README.md ├── .github └── workflows │ ├── deploy.yml │ └── codeql-analysis.yml └── devextreme.json /src/app-info.js: -------------------------------------------------------------------------------- 1 | export default { 2 | title: "DevExtreme App" 3 | }; 4 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | publicPath: "/devextreme-vue-template" 3 | }; 4 | -------------------------------------------------------------------------------- /vue-template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/devextreme-vue-template/HEAD/vue-template.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevExpress/devextreme-vue-template/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | ## Security 2 | 3 | Please refer to [DevExpress Security Policy](https://github.com/DevExpress/Shared/security/policy) 4 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "paths": { 4 | "@/*": ["./src/*"] 5 | } 6 | }, 7 | "exclude": ["node_modules", "dist"] 8 | } 9 | -------------------------------------------------------------------------------- /src/themes/generated/variables.base.scss: -------------------------------------------------------------------------------- 1 | $base-accent: #0f6cbd; 2 | $base-bg: #ffffff; 3 | $base-border-color: #e0e0e0; 4 | $base-text-color: #242424; 5 | $base-border-radius: 4px; 6 | -------------------------------------------------------------------------------- /src/themes/generated/variables.additional.scss: -------------------------------------------------------------------------------- 1 | $base-accent: #0f6cbd; 2 | $base-bg: #ffffff; 3 | $base-border-color: #e0e0e0; 4 | $base-text-color: #242424; 5 | $base-border-radius: 4px; 6 | -------------------------------------------------------------------------------- /src/themes/generated/variables.base.dark.scss: -------------------------------------------------------------------------------- 1 | $base-accent: #479ef5; 2 | $base-bg: #292929; 3 | $base-border-color: #616161; 4 | $base-text-color: #ffffff; 5 | $base-border-radius: 4px; 6 | -------------------------------------------------------------------------------- /src/themes/generated/variables.additional.dark.scss: -------------------------------------------------------------------------------- 1 | $base-accent: #479ef5; 2 | $base-bg: #292929; 3 | $base-border-color: #616161; 4 | $base-text-color: #ffffff; 5 | $base-border-radius: 4px; 6 | -------------------------------------------------------------------------------- /src/themes/metadata.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "items": [], 3 | "baseTheme": "fluent.blue.light", 4 | "assetsBasePath": "../../../node_modules/devextreme/dist/css/", 5 | "outputColorScheme": "base", 6 | "base": true 7 | } 8 | -------------------------------------------------------------------------------- /src/themes/metadata.base.dark.json: -------------------------------------------------------------------------------- 1 | { 2 | "items": [], 3 | "baseTheme": "fluent.blue.dark", 4 | "assetsBasePath": "../../../node_modules/devextreme/dist/css/", 5 | "outputColorScheme": "dark", 6 | "base": true, 7 | "makeSwatch": true 8 | } 9 | -------------------------------------------------------------------------------- /src/themes/metadata.additional.json: -------------------------------------------------------------------------------- 1 | { 2 | "items": [], 3 | "baseTheme": "fluent.blue.light", 4 | "assetsBasePath": "../../../node_modules/devextreme/dist/css/", 5 | "outputColorScheme": "additional", 6 | "makeSwatch": true, 7 | "base": true, 8 | "widgets": [ 9 | "treeview" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /src/themes/metadata.additional.dark.json: -------------------------------------------------------------------------------- 1 | { 2 | "items": [], 3 | "baseTheme": "fluent.blue.dark", 4 | "assetsBasePath": "../../../node_modules/devextreme/dist/css/", 5 | "outputColorScheme": "additional-dark", 6 | "makeSwatch": true, 7 | "base": true, 8 | "widgets": [ 9 | "treeview" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | logs 12 | *.log 13 | npm-debug.log* 14 | yarn-debug.log* 15 | yarn-error.log* 16 | pnpm-debug.log* 17 | 18 | # Editor directories and files 19 | .idea 20 | .vscode 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw? 26 | -------------------------------------------------------------------------------- /src/app-navigation.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | text: "Home", 4 | path: "/home", 5 | icon: "home" 6 | }, 7 | { 8 | text: "Examples", 9 | icon: "folder", 10 | items: [ 11 | { 12 | text: "Profile", 13 | path: "/profile" 14 | }, 15 | { 16 | text: "Tasks", 17 | path: "/tasks" 18 | } 19 | ] 20 | } 21 | ]; 22 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | DevExtreme App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { fileURLToPath, URL } from 'node:url' 2 | 3 | import { defineConfig } from 'vite' 4 | import vue from '@vitejs/plugin-vue' 5 | import vueDevTools from 'vite-plugin-vue-devtools' 6 | 7 | // https://vite.dev/config/ 8 | export default defineConfig({ 9 | plugins: [ 10 | vue(), 11 | vueDevTools(), 12 | ], 13 | resolve: { 14 | alias: { 15 | '@': fileURLToPath(new URL('./src', import.meta.url)) 16 | }, 17 | }, 18 | }) 19 | -------------------------------------------------------------------------------- /src/components/theme-switcher.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 20 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import 'devextreme/dist/css/dx.common.css'; 2 | import './themes/generated/theme.base.dark.css'; 3 | import './themes/generated/theme.base.css'; 4 | import './themes/generated/theme.additional.dark.css'; 5 | import './themes/generated/theme.additional.css'; 6 | import { createApp } from "vue"; 7 | import router from "./router"; 8 | import themes from "devextreme/ui/themes"; 9 | 10 | import App from "./App.vue"; 11 | import appInfo from "./app-info"; 12 | 13 | themes.initialized(() => { 14 | const app = createApp(App); 15 | app.use(router); 16 | app.config.globalProperties.$appInfo = appInfo; 17 | app.mount('#app'); 18 | }); 19 | -------------------------------------------------------------------------------- /src/components/app-footer.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 25 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig, globalIgnores } from 'eslint/config' 2 | import globals from 'globals' 3 | import js from '@eslint/js' 4 | import pluginVue from 'eslint-plugin-vue' 5 | 6 | export default defineConfig([ 7 | { 8 | name: 'app/files-to-lint', 9 | files: ['**/*.{js,mjs,jsx,vue}'], 10 | }, 11 | 12 | globalIgnores(['**/dist/**', '**/dist-ssr/**', '**/coverage/**']), 13 | 14 | { 15 | languageOptions: { 16 | globals: { 17 | ...globals.browser, 18 | }, 19 | }, 20 | }, 21 | 22 | js.configs.recommended, 23 | ...pluginVue.configs['flat/essential'], 24 | 25 | { 26 | name: 'disable-unused-vars-in-vue', 27 | files: ['**/*.vue'], 28 | rules: { 29 | 'no-unused-vars': 'off', 30 | }, 31 | }, 32 | ]) -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "devextreme-vue-template", 3 | "version": "0.1.0", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview", 10 | "lint": "eslint . --fix", 11 | "build-themes": "devextreme build", 12 | "postinstall": "npm run build-themes" 13 | }, 14 | "dependencies": { 15 | "vue": "^3.5.17", 16 | "sass-embedded": "^1.85.1", 17 | "vue-router": "^4.0.1", 18 | "devextreme": "25.1.3", 19 | "devextreme-vue": "25.1.3" 20 | }, 21 | "devDependencies": { 22 | "@eslint/js": "^9.29.0", 23 | "@vitejs/plugin-vue": "^6.0.0", 24 | "eslint": "^9.29.0", 25 | "eslint-plugin-vue": "~10.2.0", 26 | "globals": "^16.2.0", 27 | "vite": "^7.0.0", 28 | "vite-plugin-vue-devtools": "^7.7.7", 29 | "devextreme-cli": "1.11.0", 30 | "devextreme-themebuilder": "25.1.3" 31 | } 32 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DevExtreme Vue Template 2 | 3 | The DevExtreme Vue Template is a Vue application with a navigation menu and sample views in a responsive layout (see a [live preview](https://devexpress.github.io/devextreme-vue-template/)). This application is created with [Vite](https://vite.dev/guide/) and uses [DevExtreme Vue components](https://js.devexpress.com/Documentation/Guide/Vue_Components/DevExtreme_Vue_Components/). 4 | 5 | ![DevExtreme-Vue-Template](vue-template.png) 6 | 7 | ## Getting Started 8 | 9 | For more information about the DevExtreme Vue Template and how to customize it, refer to the following help topic: [Application Template](https://js.devexpress.com/Documentation/Guide/Vue_Components/Application_Template/). 10 | 11 | ## License 12 | 13 | **DevExtreme Vue Template is released as a MIT-licensed (free and open-source) add-on to DevExtreme.** 14 | 15 | - [DevExtreme License](https://js.devexpress.com/Licensing/) 16 | - [Free trial](http://js.devexpress.com/Buy/) -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | deploy: 8 | runs-on: ubuntu-latest 9 | timeout-minutes: 20 10 | 11 | steps: 12 | - name: Get sources 13 | uses: actions/checkout@v3 14 | 15 | - name: Use Node.js 16 | uses: actions/setup-node@v3 17 | with: 18 | node-version: '20' 19 | 20 | - name: Restore npm cache 21 | uses: actions/cache@v3 22 | with: 23 | path: ./node_modules 24 | key: ${{ runner.os }}-node-modules 25 | 26 | - name: Install dependencies (npm ci) 27 | run: npm ci --no-audit --no-fund 28 | 29 | - name: Install Internal Package 30 | uses: DevExpress/github-actions/install-internal-package@main 31 | 32 | - name: Build 33 | run: | 34 | rm -rf dist 35 | npx vite build --base "/devextreme-vue-template/" 36 | 37 | - name: Deploy 38 | uses: JamesIves/github-pages-deploy-action@ba1486788b0490a235422264426c45848eac35c6 39 | with: 40 | branch: gh-pages 41 | folder: dist 42 | target-folder: . 43 | -------------------------------------------------------------------------------- /src/utils/media-query.js: -------------------------------------------------------------------------------- 1 | const Breakpoints = { 2 | XSmall: "(max-width: 599.99px)", 3 | Small: "(min-width: 600px) and (max-width: 959.99px)", 4 | Medium: "(min-width: 960px) and (max-width: 1279.99px)", 5 | Large: "(min-width: 1280px)" 6 | }; 7 | 8 | let handlers = []; 9 | const xSmallMedia = window.matchMedia(Breakpoints.XSmall); 10 | const smallMedia = window.matchMedia(Breakpoints.Small); 11 | const mediumMedia = window.matchMedia(Breakpoints.Medium); 12 | const largeMedia = window.matchMedia(Breakpoints.Large); 13 | 14 | [xSmallMedia, smallMedia, mediumMedia, largeMedia].forEach(media => { 15 | media.addListener(() => { 16 | handlers.forEach(handler => handler()); 17 | }); 18 | }); 19 | 20 | export const sizes = () => { 21 | return { 22 | "screen-x-small": xSmallMedia.matches, 23 | "screen-small": smallMedia.matches, 24 | "screen-medium": mediumMedia.matches, 25 | "screen-large": largeMedia.matches 26 | }; 27 | }; 28 | 29 | export const subscribe = handler => handlers.push(handler); 30 | 31 | export const unsubscribe = handler => { 32 | handlers = handlers.filter(item => item !== handler); 33 | }; 34 | -------------------------------------------------------------------------------- /src/theme-service.js: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue'; 2 | 3 | class ThemeService { 4 | themes = ['light', 'dark'] 5 | themeClassNamePrefix = 'dx-swatch-'; 6 | currentTheme = ref(''); 7 | isDark = ref(false); 8 | 9 | constructor() { 10 | if (!document.getElementById('app').className.includes(this.themeClassNamePrefix)) { 11 | this.currentTheme.value = this.themes[0]; 12 | 13 | document.getElementById('app').classList.add(this.themeClassNamePrefix + this.currentTheme.value); 14 | } 15 | } 16 | 17 | switchAppTheme() { 18 | const prevTheme = this.currentTheme.value; 19 | const isCurrentThemeDark = prevTheme === 'dark'; 20 | 21 | this.currentTheme.value = this.themes[prevTheme === this.themes[0] ? 1 : 0]; 22 | 23 | document.getElementById('app').classList.replace( 24 | this.themeClassNamePrefix + prevTheme, 25 | this.themeClassNamePrefix + this.currentTheme.value 26 | ); 27 | 28 | const additionalClassNamePrefix = this.themeClassNamePrefix + 'additional'; 29 | const additionalClassNamePostfix = isCurrentThemeDark ? '-' + prevTheme : ''; 30 | const additionalClassName = `${additionalClassNamePrefix}${additionalClassNamePostfix}` 31 | 32 | document.getElementById('app') 33 | .querySelector(`.${additionalClassName}`)?.classList 34 | .replace(additionalClassName, additionalClassNamePrefix + (isCurrentThemeDark ? '' : '-dark')); 35 | 36 | this.isDark.value = this.currentTheme.value === 'dark'; 37 | } 38 | } 39 | 40 | export const themeService = new ThemeService(); 41 | -------------------------------------------------------------------------------- /src/dx-styles.scss: -------------------------------------------------------------------------------- 1 | $side-panel-min-width: 60px; 2 | 3 | .dx-viewport { 4 | .dx-popup-wrapper { 5 | z-index: 1510 !important; 6 | } 7 | 8 | .content { 9 | line-height: 1.5; 10 | flex-grow: 1; 11 | padding: 20px 40px; 12 | 13 | h2 { 14 | font-size: 32px; 15 | margin: 0; 16 | line-height: 40px; 17 | } 18 | } 19 | 20 | .screen-x-small :not(.dx-card).content { 21 | padding: 20px; 22 | } 23 | 24 | .container { 25 | height: 100%; 26 | flex-direction: column; 27 | display: flex; 28 | } 29 | 30 | .layout-body { 31 | flex: 1; 32 | min-height: 0; 33 | } 34 | 35 | .side-nav-outer-toolbar .dx-drawer { 36 | height: calc(100% - 56px) 37 | } 38 | 39 | .content-block { 40 | margin-top: 20px; 41 | } 42 | 43 | 44 | .responsive-paddings { 45 | padding: 20px; 46 | } 47 | 48 | .screen-large .responsive-paddings { 49 | padding: 40px; 50 | } 51 | 52 | .dx-card.wide-card { 53 | border-radius: 0; 54 | margin-left: 0; 55 | margin-right: 0; 56 | border-right: 0; 57 | border-left: 0; 58 | } 59 | 60 | .with-footer > .dx-scrollable-wrapper > 61 | .dx-scrollable-container > .dx-scrollable-content { 62 | height: 100%; 63 | 64 | & > .dx-scrollview-content { 65 | display: flex; 66 | flex-direction: column; 67 | min-height: 100%; 68 | } 69 | } 70 | 71 | #app { 72 | background-color: var(--base-bg-darken-5); 73 | height: 100%; 74 | } 75 | } 76 | 77 | .dx-theme-fluent { 78 | .dx-drawer-wrapper { 79 | .dx-drawer-panel-content, 80 | .dx-overlay-content { 81 | box-shadow: 0 4px 4px 0 var(--shadow-color-first), 0 1px 2px 0 var(--shadow-color-second); 82 | } 83 | } 84 | } 85 | 86 | -------------------------------------------------------------------------------- /src/variables.scss: -------------------------------------------------------------------------------- 1 | @use 'sass:meta'; 2 | @use 'sass:color'; 3 | @use 'sass:map'; 4 | @use "./themes/generated/variables.base.scss" as variablesBase; 5 | @use "./themes/generated/variables.base.dark.scss" as variablesBaseDark; 6 | @use "./themes/generated/variables.additional.scss" as variablesAdditional; 7 | @use "./themes/generated/variables.additional.dark.scss" as variablesAdditionalDark; 8 | 9 | @mixin theme-variables($theme-name) { 10 | $theme: meta.module-variables($theme-name); 11 | $base-text-color: map.get($theme, 'base-text-color'); 12 | $base-bg: map.get($theme, 'base-bg'); 13 | 14 | --base-text-color: #{$base-text-color}; 15 | --base-bg: #{$base-bg}; 16 | --base-bg-darken-5: #{color.adjust($base-bg, $lightness: -5%)}; 17 | --base-accent: #{map.get($theme, 'base-accent')}; 18 | --base-text-color-alpha-7: #{rgba($base-text-color, color.alpha($base-text-color) * 0.7)}; 19 | } 20 | 21 | :root { 22 | body { 23 | @include theme-variables('variablesBase'); 24 | 25 | --footer-border-color: rgba(224, 224, 224, 1); 26 | --plus-icon-color: #242424; 27 | --devextreme-logo-color: #596C7D; 28 | --vue-logo-text-color: #35495E; 29 | 30 | --shadow-color-first: rgba(0, 0, 0, 0.06); 31 | --shadow-color-second: rgba(0, 0, 0, 0.12); 32 | } 33 | 34 | .dx-swatch-additional { 35 | @include theme-variables('variablesAdditional'); 36 | } 37 | 38 | .dx-swatch-dark { 39 | @include theme-variables('variablesBaseDark');; 40 | 41 | --plus-icon-color: #fff; 42 | --devextreme-logo-color: #fff; 43 | --vue-logo-text-color: #fff; 44 | 45 | --shadow-color-first: rgba(0, 0, 0, 0.12); 46 | --shadow-color-second: rgba(0, 0, 0, 0.24); 47 | --footer-border-color: rgba(97, 97, 97, 1); 48 | } 49 | 50 | .dx-swatch-additional-dark { 51 | @include theme-variables('variablesAdditionalDark'); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/layouts/single-card.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 42 | 43 | 87 | -------------------------------------------------------------------------------- /src/auth.js: -------------------------------------------------------------------------------- 1 | const defaultUser = { 2 | email: 'sandra@example.com', 3 | avatarUrl: 'https://js.devexpress.com/Demos/WidgetsGallery/JSDemos/images/employees/06.png' 4 | }; 5 | 6 | export default { 7 | _user: defaultUser, 8 | loggedIn() { 9 | return !!this._user; 10 | }, 11 | 12 | async logIn(email, password) { 13 | try { 14 | // Send request 15 | console.log(email, password); 16 | this._user = { ...defaultUser, email }; 17 | 18 | return { 19 | isOk: true, 20 | data: this._user 21 | }; 22 | } 23 | catch { 24 | return { 25 | isOk: false, 26 | message: "Authentication failed" 27 | }; 28 | } 29 | }, 30 | 31 | async logOut() { 32 | this._user = null; 33 | }, 34 | 35 | async getUser() { 36 | try { 37 | // Send request 38 | 39 | return { 40 | isOk: true, 41 | data: this._user 42 | }; 43 | } 44 | catch { 45 | return { 46 | isOk: false 47 | }; 48 | } 49 | }, 50 | 51 | async resetPassword(email) { 52 | try { 53 | // Send request 54 | console.log(email); 55 | 56 | return { 57 | isOk: true 58 | }; 59 | } 60 | catch { 61 | return { 62 | isOk: false, 63 | message: "Failed to reset password" 64 | }; 65 | } 66 | }, 67 | 68 | async changePassword(email, recoveryCode) { 69 | try { 70 | // Send request 71 | console.log(email, recoveryCode); 72 | 73 | return { 74 | isOk: true 75 | }; 76 | } 77 | catch { 78 | return { 79 | isOk: false, 80 | message: "Failed to change password" 81 | } 82 | } 83 | }, 84 | 85 | async createAccount(email, password) { 86 | try { 87 | // Send request 88 | console.log(email, password); 89 | 90 | return { 91 | isOk: true 92 | }; 93 | } 94 | catch { 95 | return { 96 | isOk: false, 97 | message: "Failed to create account" 98 | }; 99 | } 100 | } 101 | }; 102 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 75 | 76 | 99 | -------------------------------------------------------------------------------- /src/components/user-panel.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 42 | 95 | -------------------------------------------------------------------------------- /src/views/profile-page.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 68 | 69 | 89 | -------------------------------------------------------------------------------- /devextreme.json: -------------------------------------------------------------------------------- 1 | { 2 | "applicationEngine": "vue", 3 | "vue": { 4 | "version": 3, 5 | "template": "javascript" 6 | }, 7 | "build": { 8 | "commands": [ 9 | { 10 | "command": "build-theme", 11 | "options": { 12 | "inputFile": "src/themes/metadata.base.json", 13 | "outputFile": "src/themes/generated/theme.base.css" 14 | } 15 | }, 16 | { 17 | "command": "build-theme", 18 | "options": { 19 | "inputFile": "src/themes/metadata.base.dark.json", 20 | "outputFile": "src/themes/generated/theme.base.dark.css" 21 | } 22 | }, 23 | { 24 | "command": "build-theme", 25 | "options": { 26 | "inputFile": "src/themes/metadata.additional.json", 27 | "outputFile": "src/themes/generated/theme.additional.css" 28 | } 29 | }, 30 | { 31 | "command": "build-theme", 32 | "options": { 33 | "inputFile": "src/themes/metadata.additional.dark.json", 34 | "outputFile": "src/themes/generated/theme.additional.dark.css" 35 | } 36 | }, 37 | { 38 | "command": "export-theme-vars", 39 | "options": { 40 | "inputFile": "src/themes/metadata.base.json", 41 | "outputFile": "src/themes/generated/variables.base.scss" 42 | } 43 | }, 44 | { 45 | "command": "export-theme-vars", 46 | "options": { 47 | "inputFile": "src/themes/metadata.base.dark.json", 48 | "outputFile": "src/themes/generated/variables.base.dark.scss" 49 | } 50 | }, 51 | { 52 | "command": "export-theme-vars", 53 | "options": { 54 | "inputFile": "src/themes/metadata.additional.json", 55 | "outputFile": "src/themes/generated/variables.additional.scss" 56 | } 57 | }, 58 | { 59 | "command": "export-theme-vars", 60 | "options": { 61 | "inputFile": "src/themes/metadata.additional.dark.json", 62 | "outputFile": "src/themes/generated/variables.additional.dark.scss" 63 | } 64 | } 65 | ] 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "master" ] 17 | pull_request: 18 | schedule: 19 | - cron: '0 0 * * 6' 20 | 21 | jobs: 22 | analyze: 23 | name: Analyze 24 | runs-on: ubuntu-latest 25 | permissions: 26 | actions: read 27 | contents: read 28 | security-events: write 29 | 30 | strategy: 31 | fail-fast: false 32 | matrix: 33 | language: [ 'javascript' ] 34 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 35 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 36 | 37 | steps: 38 | - name: Checkout repository 39 | uses: actions/checkout@v3 40 | 41 | # Initializes the CodeQL tools for scanning. 42 | - name: Initialize CodeQL 43 | uses: github/codeql-action/init@v2 44 | with: 45 | languages: ${{ matrix.language }} 46 | # If you wish to specify custom queries, you can do so here or in a config file. 47 | # By default, queries listed here will override any specified in a config file. 48 | # Prefix the list here with "+" to use these queries and those in the config file. 49 | 50 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 51 | # queries: security-extended,security-and-quality 52 | 53 | 54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 55 | # If this step fails, then you should remove it and run the build manually (see below) 56 | - name: Autobuild 57 | uses: github/codeql-action/autobuild@v2 58 | 59 | # ℹ️ Command-line programs to run using the OS shell. 60 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 61 | 62 | # If the Autobuild fails above, remove it and uncomment the following three lines. 63 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 64 | 65 | # - run: | 66 | # echo "Run, Build Application using script" 67 | # ./location_of_script_within_repo/buildscript.sh 68 | 69 | - name: Perform CodeQL Analysis 70 | uses: github/codeql-action/analyze@v2 71 | with: 72 | category: "/language:${{matrix.language}}" 73 | -------------------------------------------------------------------------------- /src/views/reset-password-form.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 98 | 99 | 113 | -------------------------------------------------------------------------------- /src/router.js: -------------------------------------------------------------------------------- 1 | import auth from "./auth"; 2 | import { createRouter, createWebHashHistory } from "vue-router"; 3 | 4 | import Home from "./views/home-page.vue"; 5 | import Profile from "./views/profile-page.vue"; 6 | import Tasks from "./views/tasks-page.vue"; 7 | import defaultLayout from "./layouts/side-nav-outer-toolbar.vue"; 8 | import simpleLayout from "./layouts/single-card.vue"; 9 | 10 | function loadView(view) { 11 | return () => import (/* webpackChunkName: "login" */ `./views/${view}.vue`) 12 | } 13 | 14 | const router = new createRouter({ 15 | routes: [ 16 | { 17 | path: "/home", 18 | name: "home", 19 | meta: { 20 | requiresAuth: true, 21 | layout: defaultLayout 22 | }, 23 | component: Home 24 | }, 25 | { 26 | path: "/profile", 27 | name: "profile", 28 | meta: { 29 | requiresAuth: true, 30 | layout: defaultLayout 31 | }, 32 | component: Profile 33 | }, 34 | { 35 | path: "/tasks", 36 | name: "tasks", 37 | meta: { 38 | requiresAuth: true, 39 | layout: defaultLayout 40 | }, 41 | component: Tasks 42 | }, 43 | { 44 | path: "/login-form", 45 | name: "login-form", 46 | meta: { 47 | requiresAuth: false, 48 | layout: simpleLayout, 49 | title: "Sign In" 50 | }, 51 | component: loadView("login-form") 52 | }, 53 | { 54 | path: "/reset-password", 55 | name: "reset-password", 56 | meta: { 57 | requiresAuth: false, 58 | layout: simpleLayout, 59 | title: "Reset Password", 60 | description: "Please enter the email address that you used to register, and we will send you a link to reset your password via Email." 61 | }, 62 | component: loadView("reset-password-form") 63 | }, 64 | { 65 | path: "/create-account", 66 | name: "create-account", 67 | meta: { 68 | requiresAuth: false, 69 | layout: simpleLayout, 70 | title: "Sign Up" 71 | }, 72 | component: loadView("create-account-form"), 73 | }, 74 | { 75 | path: "/change-password/:recoveryCode", 76 | name: "change-password", 77 | meta: { 78 | requiresAuth: false, 79 | layout: simpleLayout, 80 | title: "Change Password" 81 | }, 82 | component: loadView("change-password-form") 83 | }, 84 | { 85 | path: "/", 86 | redirect: "/home" 87 | }, 88 | { 89 | path: "/recovery", 90 | redirect: "/home" 91 | }, 92 | { 93 | path: "/:pathMatch(.*)*", 94 | redirect: "/home" 95 | } 96 | ], 97 | history: createWebHashHistory() 98 | }); 99 | 100 | router.beforeEach((to, from, next) => { 101 | 102 | if (to.name === "login-form" && auth.loggedIn()) { 103 | next({ name: "home" }); 104 | } 105 | 106 | if (to.matched.some(record => record.meta.requiresAuth)) { 107 | if (!auth.loggedIn()) { 108 | next({ 109 | name: "login-form", 110 | query: { redirect: to.fullPath } 111 | }); 112 | } else { 113 | next(); 114 | } 115 | } else { 116 | next(); 117 | } 118 | }); 119 | 120 | export default router; 121 | -------------------------------------------------------------------------------- /src/views/change-password-form.vue: -------------------------------------------------------------------------------- 1 | 45 | 46 | 112 | 113 | 116 | -------------------------------------------------------------------------------- /src/views/tasks-page.vue: -------------------------------------------------------------------------------- 1 | 81 | 82 | 135 | 140 | -------------------------------------------------------------------------------- /src/components/header-toolbar.vue: -------------------------------------------------------------------------------- 1 | 47 | 48 | 112 | 113 | 155 | -------------------------------------------------------------------------------- /src/layouts/side-nav-outer-toolbar.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 125 | 126 | 138 | -------------------------------------------------------------------------------- /src/views/login-form.vue: -------------------------------------------------------------------------------- 1 | 57 | 58 | 123 | 124 | 138 | -------------------------------------------------------------------------------- /src/views/create-account-form.vue: -------------------------------------------------------------------------------- 1 | 63 | 64 | 129 | 130 | 151 | -------------------------------------------------------------------------------- /src/components/side-nav-menu.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 120 | 121 | 195 | -------------------------------------------------------------------------------- /src/layouts/side-nav-inner-toolbar.vue: -------------------------------------------------------------------------------- 1 | 53 | 54 | 155 | 156 | 175 | -------------------------------------------------------------------------------- /src/views/home-page.vue: -------------------------------------------------------------------------------- 1 | 168 | 169 | 215 | --------------------------------------------------------------------------------