├── src ├── api │ └── .gitkeep ├── model │ ├── .gitkeep │ └── Test.model.ts ├── layouts │ └── .gitkeep ├── assets │ ├── icon │ │ └── .gitkeep │ ├── logo │ │ └── .gitkeep │ ├── css │ │ └── index.scss │ └── logo.png ├── helpers │ ├── common │ │ └── .gitkeep │ └── global │ │ └── session-helper.ts ├── components │ ├── charts │ │ └── .gitkeep │ ├── elements │ │ └── .gitkeep │ ├── global │ │ └── .gitkeep │ ├── mixins │ │ └── .gitkeep │ └── HelloWorld.vue ├── plugins │ ├── http.types.ts │ └── http-common.ts ├── store │ ├── counter │ │ ├── state.ts │ │ ├── counter.types.ts │ │ ├── index.ts │ │ ├── getters.ts │ │ ├── mutations.ts │ │ └── actions.ts │ └── index.ts ├── views │ ├── About.vue │ ├── Contact.vue │ ├── Logout.vue │ ├── Login.vue │ ├── Dashboard.vue │ └── Home.vue ├── shims-vue.d.ts ├── main.ts ├── App.vue └── router │ └── index.ts ├── .stylelintignore ├── .browserslistrc ├── .prettierignore ├── .eslintignore ├── public ├── favicon.ico └── index.html ├── .babelrc.js ├── babel.config.js ├── postcss.config.js ├── tailwind.config.js ├── .gitignore ├── .prettierrc.js ├── README.md ├── .gitattributes ├── .vscode ├── tasks.json ├── extensions.json └── settings.json ├── tsconfig.json ├── .editorconfig ├── .eslintrc.js ├── package.json └── stylelint.config.js /src/api/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/model/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/layouts/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icon/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/logo/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/helpers/common/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/charts/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/elements/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/global/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/mixins/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.stylelintignore: -------------------------------------------------------------------------------- 1 | src/assets/css/index.scss 2 | -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /node_modules/** 2 | /target/** 3 | /dist/** 4 | /tests/unit/coverage/** 5 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /dist/ 2 | /tests/unit/coverage/ 3 | /node/ 4 | /node_modules/ 5 | /coverage/ 6 | -------------------------------------------------------------------------------- /src/assets/css/index.scss: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /src/plugins/http.types.ts: -------------------------------------------------------------------------------- 1 | export interface Data { 2 | [key: string]: number | string; 3 | } 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techarshgupta/vue3-boilerplate/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techarshgupta/vue3-boilerplate/HEAD/src/assets/logo.png -------------------------------------------------------------------------------- /src/store/counter/state.ts: -------------------------------------------------------------------------------- 1 | export const state = { 2 | counter: 0, 3 | }; 4 | 5 | export type State = typeof state; 6 | -------------------------------------------------------------------------------- /src/views/About.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /.babelrc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Harsh Gupta 3 | */ 4 | 5 | module.exports = { 6 | presets: ['@vue/cli-plugin-babel/preset'], 7 | }; 8 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Harsh Gupta 3 | */ 4 | 5 | module.exports = { 6 | presets: ['@vue/cli-plugin-babel/preset'], 7 | }; 8 | -------------------------------------------------------------------------------- /src/model/Test.model.ts: -------------------------------------------------------------------------------- 1 | export interface Test { 2 | id: number; 3 | description: string; 4 | status: 'IDLE' | 'RUNNING' | 'SUCCESS' | 'FAILURE'; 5 | } 6 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Harsh Gupta 3 | */ 4 | 5 | module.exports = { 6 | plugins: { 7 | tailwindcss: {}, 8 | autoprefixer: {}, 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /src/store/counter/counter.types.ts: -------------------------------------------------------------------------------- 1 | export enum ActionTypes { 2 | GET_COUNTER = 'GET_COUNTER', 3 | } 4 | 5 | export enum MutationTypes { 6 | SET_COUNTER = 'SET_COUNTER', 7 | } 8 | -------------------------------------------------------------------------------- /src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | declare module '*.vue' { 3 | import type { DefineComponent } from 'vue' 4 | const component: DefineComponent<{}, {}, any> 5 | export default component 6 | } 7 | -------------------------------------------------------------------------------- /src/views/Contact.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | import { createLogger, createStore } from 'vuex'; 2 | 3 | import counter from './counter'; 4 | 5 | export default createStore({ 6 | strict: true, 7 | plugins: process.env.NODE_ENV === 'development' ? [createLogger()] : [], 8 | modules: { 9 | counter, 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Harsh Gupta 3 | */ 4 | 5 | module.exports = { 6 | purge: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'], 7 | darkMode: false, // or 'media' or 'class' 8 | theme: { 9 | extend: {}, 10 | }, 11 | variants: { 12 | extend: {}, 13 | }, 14 | plugins: [], 15 | }; 16 | -------------------------------------------------------------------------------- /src/store/counter/index.ts: -------------------------------------------------------------------------------- 1 | import { state } from './state'; 2 | import { getters } from './getters'; 3 | import { mutations } from './mutations'; 4 | import { actions } from './actions'; 5 | 6 | export default { 7 | namespaced: true, 8 | strict: true, 9 | state, 10 | getters, 11 | mutations, 12 | actions, 13 | }; 14 | -------------------------------------------------------------------------------- /src/store/counter/getters.ts: -------------------------------------------------------------------------------- 1 | import { GetterTree } from 'vuex'; 2 | import { State } from './state'; 3 | 4 | export type Getters = { 5 | doubledCounter(state: State): number; 6 | }; 7 | 8 | export const getters: GetterTree & Getters = { 9 | doubledCounter: (state) => { 10 | return state.counter * 2; 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Harsh Gupta 3 | */ 4 | 5 | import { createApp } from 'vue'; 6 | import App from './App.vue'; 7 | import router from './router'; 8 | import store from './store'; 9 | import '@/assets/css/index.scss'; 10 | 11 | const app = createApp(App); 12 | app.use(store); 13 | app.use(router); 14 | router.isReady().then(() => { 15 | app.mount('#app'); 16 | }); 17 | -------------------------------------------------------------------------------- /.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 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | *.suo 19 | *.ntvs* 20 | *.njsproj 21 | *.sln 22 | *.sw? 23 | 24 | # lock files 25 | package-lock.json 26 | *.lock 27 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Harsh Gupta 3 | */ 4 | 5 | module.exports = { 6 | printWidth: 100, 7 | tabWidth: 2, 8 | useTabs: false, 9 | semi: true, 10 | singleQuote: true, 11 | trailingComma: 'es5', 12 | bracketSpacing: true, 13 | jsxBracketSameLine: false, 14 | arrowParens: 'always', 15 | proseWrap: 'never', 16 | htmlWhitespaceSensitivity: 'css', 17 | }; 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue3-ts-boilerplate 2 | 3 | ## Project setup 4 | ``` 5 | yarn install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | yarn serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | yarn build 16 | ``` 17 | 18 | ### Lints and fixes files 19 | ``` 20 | yarn lint 21 | ``` 22 | 23 | ### Customize configuration 24 | See [Configuration Reference](https://cli.vuejs.org/config/). 25 | -------------------------------------------------------------------------------- /src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 18 | -------------------------------------------------------------------------------- /src/store/counter/mutations.ts: -------------------------------------------------------------------------------- 1 | import { MutationTree } from 'vuex'; 2 | import { MutationTypes } from './counter.types'; 3 | import { State } from './state'; 4 | 5 | export type Mutations = { 6 | [MutationTypes.SET_COUNTER](state: S, payload: number): void; 7 | }; 8 | 9 | export const mutations: MutationTree & Mutations = { 10 | [MutationTypes.SET_COUNTER](state, payload: number) { 11 | state.counter = payload; 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Source code files 2 | 3 | *.css text eol=lf 4 | *.html text eol=lf 5 | *.vue text eol=lf 6 | *.js text eol=lf 7 | *.jsx text eol=lf 8 | *.ts text eol=lf 9 | *.tsx text eol=lf 10 | *.scss text eol=lf 11 | *.sass text eol=lf 12 | *.json text eol=lf 13 | *.md text eol=lf 14 | 15 | # config files 16 | 17 | .browserslistrc text eol=lf 18 | .editorconfig text eol=lf 19 | .eslintignore text eol=lf 20 | .gitattributes 21 | .gitignore 22 | .prettierignore 23 | .stylelintignore 24 | -------------------------------------------------------------------------------- /src/views/Logout.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 22 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "stylelint", 8 | "type": "shell", 9 | "command": "npm", 10 | "args": ["run", "lint:stylelint", "${file}"], 11 | "presentation": { 12 | "echo": true, 13 | "reveal": "never", 14 | "focus": false, 15 | "panel": "shared", 16 | "showReuseMessage": true, 17 | "clear": false 18 | } 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /src/views/Login.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 23 | -------------------------------------------------------------------------------- /src/store/counter/actions.ts: -------------------------------------------------------------------------------- 1 | import { ActionTree, Commit } from 'vuex'; 2 | import { State } from './state'; 3 | import { ActionTypes, MutationTypes } from './counter.types'; 4 | 5 | export interface Actions { 6 | [ActionTypes.GET_COUNTER]({ commit }: { commit: Commit }, payload: number): Promise; 7 | } 8 | 9 | export const actions: ActionTree & Actions = { 10 | [ActionTypes.GET_COUNTER]({ commit }, data) { 11 | return new Promise((resolve) => { 12 | setTimeout(() => { 13 | commit(MutationTypes.SET_COUNTER, data); 14 | resolve(data); 15 | }, 500); 16 | }); 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "skipLibCheck": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "sourceMap": true, 13 | "baseUrl": ".", 14 | "types": ["webpack-env"], 15 | "paths": { 16 | "@/*": ["src/*"] 17 | }, 18 | "lib": ["esnext", "dom", "dom.iterable", "scripthost"] 19 | }, 20 | "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue", "tests/**/*.ts", "tests/**/*.tsx"], 21 | "exclude": ["node_modules"] 22 | } 23 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/views/Dashboard.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 21 | 22 | 33 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 41 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | # Unix-style newlines with a newline ending every file 5 | [*] 6 | end_of_line = lf 7 | insert_final_newline = true 8 | indent_style = space 9 | indent_size = 2 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | 13 | # Matches multiple files with brace expansion notation 14 | # Set default charset 15 | [*.{js,ts,py}] 16 | charset = utf-8 17 | 18 | # Tab indentation (no size specified) 19 | [Makefile] 20 | indent_style = tab 21 | 22 | # Indentation override for all JS under lib directory 23 | [lib/**.{js,ts}] 24 | indent_style = space 25 | indent_size = 2 26 | 27 | # Matches the exact files either package.json or .travis.yml 28 | [{package.json,.travis.yml}] 29 | indent_style = space 30 | indent_size = 2 31 | 32 | # trailing spaces in markdown indicate word wrap 33 | [*.md] 34 | trim_trailing_spaces = false 35 | max_line_length = 80 36 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | // Syntax highlighting and more for .vue files 6 | // https://github.com/vuejs/vetur 7 | "octref.vetur", 8 | 9 | // Peek and go-to-definition for .vue files 10 | // https://github.com/fuzinato/vscode-vue-peek 11 | "dariofuzinato.vue-peek", 12 | 13 | // Lint-on-save with ESLint 14 | // https://github.com/Microsoft/vscode-eslint 15 | "dbaeumer.vscode-eslint", 16 | 17 | // Lint-on-save with Stylelint 18 | // https://github.com/stylelint/vscode-stylelint 19 | "stylelint.vscode-stylelint", 20 | 21 | // SCSS intellisense 22 | // https://github.com/mrmlnc/vscode-scss 23 | "mrmlnc.vscode-scss", 24 | 25 | // Editor config support 26 | "editorconfig.editorconfig", 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 39 | -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createRouter, 3 | createWebHistory, 4 | NavigationGuardNext, 5 | RouteLocationNormalized, 6 | RouteRecordRaw, 7 | } from 'vue-router'; 8 | import { globalStartupGuard, authGuard } from '@/helpers/global/session-helper'; 9 | 10 | const routes: Array = [ 11 | { 12 | path: '/login', 13 | name: 'Login', 14 | component: () => import(/* webpackChunkName: "login" */ '../views/Login.vue'), 15 | }, 16 | { 17 | path: '/signout', 18 | name: 'Logout', 19 | component: () => import(/* webpackChunkName: "logout" */ '@/views/Logout.vue'), 20 | }, 21 | { 22 | path: '/', 23 | name: 'Dashboard', 24 | component: () => import(/* webpackChunkName: "dashboard" */ '../views/Dashboard.vue'), 25 | children: [ 26 | { 27 | path: '/', 28 | name: 'Home', 29 | component: () => import(/* webpackChunkName: "home" */ '../views/Home.vue'), 30 | }, 31 | { 32 | path: '/about', 33 | name: 'About', 34 | component: () => import(/* webpackChunkName: "about" */ '../views/About.vue'), 35 | }, 36 | ], 37 | }, 38 | { 39 | path: '/contact', 40 | name: 'Contact', 41 | component: () => import(/* webpackChunkName: "contact" */ '@/views/Contact.vue'), 42 | meta: { 43 | guest: true, 44 | }, 45 | }, 46 | ]; 47 | 48 | const router = createRouter({ 49 | history: createWebHistory(process.env.BASE_URL), 50 | routes, 51 | }); 52 | 53 | router.beforeEach( 54 | (to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) => { 55 | globalStartupGuard().then(() => { 56 | authGuard(to, from, next); 57 | }); 58 | } 59 | ); 60 | 61 | export default router; 62 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Harsh Gupta 3 | */ 4 | 5 | module.exports = { 6 | root: true, 7 | env: { 8 | node: true, 9 | }, 10 | extends: [ 11 | "plugin:vue/vue3-essential", 12 | "eslint:recommended", 13 | "@vue/typescript/recommended", 14 | "@vue/prettier", 15 | "@vue/prettier/@typescript-eslint", 16 | ], 17 | globals: { 18 | Atomics: "readonly", 19 | SharedArrayBuffer: "readonly", 20 | }, 21 | parserOptions: { 22 | ecmaVersion: 2020, 23 | }, 24 | rules: { 25 | "no-console": process.env.NODE_ENV === "production" ? "warn" : "off", 26 | "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off", 27 | eqeqeq: [ 28 | "error", 29 | "always", 30 | { 31 | null: "always", 32 | }, 33 | ], 34 | "prefer-const": ["error", { 35 | "destructuring": "any", 36 | "ignoreReadBeforeAssign": false 37 | }], 38 | quotes: ["error", "single", { avoidEscape: true }], 39 | "comma-dangle": ["error", "only-multiline"], 40 | 41 | "max-len": [ 42 | "error", 43 | { 44 | code: 100, 45 | tabWidth: 2, 46 | ignoreComments: true, 47 | ignoreUrls: true, 48 | ignoreTemplateLiterals: true, 49 | ignoreRegExpLiterals: true, 50 | } 51 | ], 52 | 53 | "lines-between-class-members": [ 54 | "error", 55 | "always", 56 | { 57 | exceptAfterSingleLine: true, 58 | }, 59 | ], 60 | 61 | // vue rules 62 | "vue/custom-event-name-casing": "off", 63 | "vue/valid-v-slot": "warn", 64 | "vue/no-mutating-props": "warn", 65 | 66 | "no-plusplus": "off", 67 | "no-var": "error", 68 | "no-use-before-define": ["error", {"functions": true, "classes": true}] 69 | }, 70 | }; 71 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // === 3 | // Spacing 4 | // === 5 | 6 | "editor.insertSpaces": true, 7 | "editor.tabSize": 2, 8 | "editor.trimAutoWhitespace": true, 9 | "files.trimTrailingWhitespace": true, 10 | "files.eol": "\n", 11 | "files.insertFinalNewline": true, 12 | "files.trimFinalNewlines": true, 13 | 14 | // === 15 | // Files 16 | // === 17 | 18 | "files.exclude": { 19 | "**/*.log": true, 20 | "**/*.log*": true, 21 | "**/coverage": true 22 | }, 23 | "files.associations": { 24 | ".markdownlintrc": "jsonc" 25 | }, 26 | 27 | // === 28 | // Event Triggers 29 | // === 30 | 31 | "editor.formatOnSave": true, 32 | "editor.formatOnPaste": true, 33 | "editor.codeActionsOnSave": { 34 | "source.fixAll": true, 35 | "source.fixAll.eslint": true, 36 | "source.fixAll.stylelint": true 37 | }, 38 | "eslint.run": "onSave", 39 | "eslint.options": { 40 | "extensions": [".html", ".js", ".vue", ".ts"] 41 | }, 42 | "eslint.validate": ["javascript", "javascriptreact", "vue", "vue-html", "html", "typescript"], 43 | "vetur.format.enable": true, 44 | 45 | // === 46 | // HTML 47 | // === 48 | 49 | "html.format.enable": true, 50 | "vetur.validation.template": false, 51 | "emmet.triggerExpansionOnTab": true, 52 | "emmet.includeLanguages": { 53 | "vue-html": "html", 54 | "javascript": "javascriptreact" 55 | }, 56 | "vetur.completion.tagCasing": "initial", 57 | 58 | // === 59 | // JS(ON) 60 | // === 61 | 62 | "[javascript]": { 63 | "editor.formatOnSave": false 64 | }, 65 | "eslint.packageManager": "npm", 66 | "javascript.format.enable": false, 67 | "json.format.enable": false, 68 | "vetur.validation.script": false, 69 | 70 | // === 71 | // CSS 72 | // === 73 | 74 | "stylelint.enable": true, 75 | "stylelint.packageManager": "npm", 76 | "css.validate": false, 77 | "scss.validate": false, 78 | "vetur.validation.style": false, 79 | 80 | // === 81 | // MARKDOWN 82 | // === 83 | 84 | "[markdown]": { 85 | "editor.wordWrap": "wordWrapColumn", 86 | "editor.wordWrapColumn": 80 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue3-boilerplate", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint", 9 | "stylelint": "stylelint \"src/**/*.{vue,scss}\"", 10 | "stylelint:fix": "stylelint \"src/**/*.{vue,scss}\" --fix" 11 | }, 12 | "author": { 13 | "name": "Harsh Gupta", 14 | "email": "techarshgupta@gmail.com" 15 | }, 16 | "dependencies": { 17 | "axios": "^0.21.1", 18 | "core-js": "^3.6.5", 19 | "vue": "^3.0.0", 20 | "vue-router": "^4.0.0-0", 21 | "vuex": "^4.0.0-0" 22 | }, 23 | "devDependencies": { 24 | "@tailwindcss/postcss7-compat": "^2.0.4", 25 | "@typescript-eslint/eslint-plugin": "^4.18.0", 26 | "@typescript-eslint/parser": "^4.18.0", 27 | "@vue/cli-plugin-babel": "~4.5.0", 28 | "@vue/cli-plugin-eslint": "~4.5.0", 29 | "@vue/cli-plugin-router": "~4.5.0", 30 | "@vue/cli-plugin-typescript": "~4.5.0", 31 | "@vue/cli-plugin-vuex": "~4.5.0", 32 | "@vue/cli-service": "~4.5.0", 33 | "@vue/compiler-sfc": "^3.0.0", 34 | "@vue/eslint-config-prettier": "^6.0.0", 35 | "@vue/eslint-config-typescript": "^7.0.0", 36 | "autoprefixer": "^9.8.6", 37 | "eslint": "^6.7.2", 38 | "eslint-plugin-prettier": "^3.3.1", 39 | "eslint-plugin-vue": "^7.0.0", 40 | "lint-staged": "^9.5.0", 41 | "node-sass": "^4.12.0", 42 | "postcss": "^7.0.35", 43 | "prettier": "^2.2.1", 44 | "sass-loader": "^8.0.2", 45 | "stylelint": "^13.12.0", 46 | "stylelint-config-css-modules": "^2.2.0", 47 | "stylelint-config-prettier": "^8.0.2", 48 | "stylelint-config-recess-order": "^2.3.0", 49 | "stylelint-config-standard": "^21.0.0", 50 | "stylelint-scss": "^3.19.0", 51 | "tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.0.4", 52 | "typescript": "~4.1.5" 53 | }, 54 | "gitHooks": { 55 | "pre-commit": "lint-staged" 56 | }, 57 | "lint-staged": { 58 | "*.{js,jsx,vue,ts,tsx}": [ 59 | "vue-cli-service lint", 60 | "stylelint", 61 | "git add" 62 | ] 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/helpers/global/session-helper.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Harsh Gupta 3 | */ 4 | 5 | import { NavigationGuardNext, RouteLocationNormalized } from 'vue-router'; 6 | 7 | // caches the JWT status 8 | let hasToken = false; 9 | 10 | // var to ensure that we run global startup guard once and only once 11 | let init: boolean; 12 | 13 | /** 14 | * Load user session using auth 15 | * @returns {Boolean} 16 | */ 17 | 18 | function loadSession(): boolean { 19 | const token = localStorage.getItem('token'); 20 | if (!token) { 21 | return false; 22 | } 23 | return true; 24 | } 25 | 26 | /** 27 | * Handle guest router navigation 28 | * @param to 29 | * @param from 30 | * @param next 31 | * 32 | */ 33 | 34 | function handleGuestPages( 35 | to: RouteLocationNormalized, 36 | from: RouteLocationNormalized, 37 | next: NavigationGuardNext 38 | ): void { 39 | if (to.path === '/') { 40 | if (hasToken) { 41 | next({ name: 'Home', query: from.query }); 42 | } else { 43 | next({ name: 'Login' }); 44 | } 45 | } else if (hasToken) { 46 | next({ name: 'Home', query: from.query }); 47 | } else { 48 | next(); 49 | } 50 | } 51 | 52 | /** 53 | * Router guard which validates that the user is properly 54 | * authenticated to acess the given 'to' route 55 | * 56 | * @param to 57 | * @param from 58 | * @param next 59 | */ 60 | 61 | export function authGuard( 62 | to: RouteLocationNormalized, 63 | from: RouteLocationNormalized, 64 | next: NavigationGuardNext 65 | ): void { 66 | if (to.path === '/login' && !hasToken) { 67 | next(); 68 | } else if (to.meta.guest === true) { 69 | handleGuestPages(to, from, next); 70 | } else if (!hasToken) { 71 | next({ name: 'Login' }); 72 | } else if ((to.name = 'Home' && to.hash && to.fullPath)) { 73 | to.fullPath = ''; 74 | to.hash = ''; 75 | next('/'); 76 | } else { 77 | next(); 78 | } 79 | } 80 | 81 | /** 82 | * Global router guard which runs exactly *once*, when the application is first 83 | * loaded. That is, when processing the very first route. Anything which needs 84 | * to run at the earliest possible time, before loading any Vue components 85 | * should happen here. 86 | * 87 | * Currently it: 88 | * - Loads session 89 | * - Handles the auth check for secure routes 90 | */ 91 | 92 | export async function globalStartupGuard(): Promise { 93 | if (hasToken === false) { 94 | hasToken = loadSession(); 95 | } 96 | 97 | if (init !== undefined) { 98 | return init; 99 | } 100 | 101 | if (!hasToken) { 102 | init = false; 103 | return init; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /stylelint.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Harsh Gupta 3 | */ 4 | 5 | module.exports = { 6 | extends: [ 7 | // Use the Standard config as the base 8 | // https://github.com/stylelint/stylelint-config-standard 9 | 'stylelint-config-standard', 10 | // Enforce a standard order for CSS properties 11 | // https://github.com/stormwarning/stylelint-config-recess-order 12 | 'stylelint-config-recess-order', 13 | // Override rules that would interfere with Prettier 14 | // https://github.com/shannonmoeller/stylelint-config-prettier 15 | 'stylelint-config-prettier', 16 | // Override rules to allow linting of CSS modules 17 | // https://github.com/pascalduez/stylelint-config-css-modules 18 | 'stylelint-config-css-modules', 19 | ], 20 | 21 | plugins: [ 22 | // Bring in some extra rules for SCSS 23 | 'stylelint-scss', 24 | ], 25 | 26 | // Rule lists: 27 | // - https://stylelint.io/user-guide/rules/ 28 | // - https://github.com/kristerkari/stylelint-scss#list-of-rules 29 | rules: { 30 | // Allow newlines inside class attribute values 31 | 'string-no-newline': null, 32 | // Enforce camelCase for classes and ids, to work better 33 | // with CSS modules 34 | 'selector-class-pattern': /^[a-z][a-zA-Z0-9-]*(-(enter|leave)(-(active|to))?)?$/, 35 | 'selector-id-pattern': /^[a-z][a-zA-Z]*$/, 36 | // Limit the number of universal selectors in a selector, 37 | // to avoid very slow selectors 38 | 'selector-max-universal': 1, 39 | // Disallow allow global element/type selectors in scoped modules 40 | 'selector-max-type': [ 41 | 0, 42 | { 43 | ignore: ['child', 'descendant', 'compounded'], 44 | }, 45 | ], 46 | 'max-nesting-depth': 5, 47 | 'declaration-block-no-duplicate-properties': true, 48 | 49 | // disable the border: none blacklist 50 | 'declaration-property-value-blacklist': null, 51 | 52 | // === 53 | // PRETTIER 54 | // === 55 | // HACK: to compensate for https://github.com/shannonmoeller/stylelint-config-prettier/issues/4 56 | // Modifying setting from Standard: https://github.com/stylelint/stylelint-config-standard/blob/7b76d7d0060f2e13a331806a09c2096c7536b0a6/index.js#L6 57 | 'at-rule-empty-line-before': [ 58 | 'always', 59 | { 60 | except: ['blockless-after-same-name-blockless', 'first-nested'], 61 | ignore: ['after-comment'], 62 | ignoreAtRules: ['else'], 63 | }, 64 | ], 65 | 66 | // === 67 | // SCSS 68 | // === 69 | 'scss/dollar-variable-colon-space-after': 'always', 70 | 'scss/dollar-variable-colon-space-before': 'never', 71 | 'scss/dollar-variable-no-missing-interpolation': true, 72 | 'scss/dollar-variable-pattern': /^[a-z-_]+$/, 73 | 'scss/double-slash-comment-whitespace-inside': 'always', 74 | 'scss/operator-no-newline-before': true, 75 | 'scss/operator-no-unspaced': true, 76 | 'scss/selector-no-redundant-nesting-selector': true, 77 | // Allow SCSS and CSS module keywords beginning with `@` 78 | 'at-rule-no-unknown': null, 79 | 'scss/at-rule-no-unknown': true, 80 | }, 81 | }; 82 | -------------------------------------------------------------------------------- /src/plugins/http-common.ts: -------------------------------------------------------------------------------- 1 | import router from '@/router'; 2 | import _axios, { 3 | AxiosInstance, 4 | AxiosRequestConfig, 5 | AxiosResponse, 6 | AxiosError, 7 | Method, 8 | } from 'axios'; 9 | import { Data } from './http.types'; 10 | 11 | // var is responsible for the base URL of the axios 12 | let baseURL: string; 13 | 14 | /** 15 | * switch case to choose the base URL for axios request config 16 | */ 17 | switch (process.env.VUE_APP_BUILD) { 18 | case 'production': 19 | baseURL = 'https://www.google.com/'; 20 | break; 21 | case 'uat': 22 | baseURL = 'https://www.google.com/'; 23 | break; 24 | case 'development': 25 | baseURL = 'https://www.google.com/'; 26 | break; 27 | default: 28 | baseURL = 'https://www.google.com/'; 29 | break; 30 | } 31 | 32 | export class Axios { 33 | protected readonly instance: AxiosInstance; 34 | protected setAuthHeader = false; 35 | public constructor() { 36 | this.instance = _axios.create({ 37 | baseURL, 38 | timeout: 3000, // indicates, 3000ms ie. 3 second 39 | headers: { 40 | 'Content-Type': 'application/json', 41 | }, 42 | }); 43 | this._initializeRequestInterceptor(); 44 | this._initializeResponseInterceptor(); 45 | } 46 | 47 | /** 48 | * Initialize request interceptor is used to takeover the request 49 | * to customize the headers of the axios request. 50 | */ 51 | private _initializeRequestInterceptor = () => { 52 | this.instance.interceptors.request.use(this._handleRequest, this._handleErrors); 53 | }; 54 | 55 | /** 56 | * Handle request is used to overwrite the headers 57 | * of axios request config 58 | * @param config 59 | * @returns {AxiosRequestConfig} 60 | */ 61 | private _handleRequest = (config: AxiosRequestConfig) => { 62 | if (this.setAuthHeader) { 63 | // config.headers['Authorization'] = this._getAuthToken(); 64 | config.headers['Authorization'] = 'Bearer ...'; 65 | return config; 66 | } 67 | return config; 68 | }; 69 | 70 | /** 71 | * Initialize response interceptor is used to takeover the response 72 | * to customize the response and body of the axios response. 73 | */ 74 | private _initializeResponseInterceptor = () => { 75 | this.instance.interceptors.response.use(this._handleResponse); 76 | }; 77 | 78 | private _handleResponse = (response: AxiosResponse) => { 79 | switch (response.status) { 80 | case 200: 81 | // yay! 82 | break; 83 | // any other cases 84 | default: 85 | // default case 86 | } 87 | return response; 88 | }; 89 | 90 | // private _getAuthToken = () => localStorage.getItem('token'); 91 | /** 92 | * Handle errors is used to handle all the axios request errors 93 | * @param {AxiosError} error 94 | */ 95 | private _handleErrors = (error: AxiosError) => { 96 | // all the error responses 97 | const status: number = error.response && error.response ? error.response.status : 400; 98 | switch (status) { 99 | case 400: 100 | console.error(status, error.message); 101 | console.error('Nothing to display', 'Data Not Found'); 102 | break; 103 | case 401: // authentication error, logout the user 104 | console.error(status, error.message); 105 | console.error('Please login again', 'Session Expired'); 106 | router.replace('/login'); 107 | break; 108 | case 404: 109 | console.error(status, error.message); 110 | console.error('Nothing to display', 'Page Not Found'); 111 | break; 112 | case 500: 113 | console.error(status, error.message); 114 | console.error('Please try again after sometime!'); 115 | break; 116 | default: 117 | console.error(status, error.message); 118 | break; 119 | } 120 | }; 121 | 122 | /** 123 | * Request method to call axios. 124 | * 125 | * @access public 126 | * @param {Method} type - request type. 127 | * @param {string} url - endpoint you want to reach. 128 | * @param {boolean} isAuth - request with authorization header or not 129 | * @param {object} data - request payload. 130 | * @returns {Promise} - HTTP [axios] response payload. 131 | * @memberof Axios 132 | * 133 | */ 134 | 135 | async request(type: Method, url: string, isAuth: boolean, data?: Data): Promise { 136 | this.setAuthHeader = isAuth; 137 | try { 138 | let response: AxiosResponse; 139 | switch (type) { 140 | case 'get': 141 | response = await this.instance.get(url, { params: data }); 142 | break; 143 | case 'post': 144 | response = await this.instance.post(url, data); 145 | break; 146 | case 'put': 147 | response = await this.instance.put(url, { params: data }); 148 | break; 149 | case 'patch': 150 | response = await this.instance.patch(url, { params: data }); 151 | break; 152 | case 'delete': 153 | response = await this.instance.delete(url, { params: data }); 154 | break; 155 | default: 156 | response = await this.instance.get(url, { params: data }); 157 | break; 158 | } 159 | return response && response.data ? response.data : response; 160 | } catch (error) { 161 | this._handleErrors(error); 162 | return Promise.reject(error); 163 | } 164 | } 165 | } 166 | --------------------------------------------------------------------------------