├── src
├── locales
│ ├── messages.json
│ └── index.js
├── assets
│ └── styles
│ │ ├── scss
│ │ ├── theme-styles.scss
│ │ └── parts
│ │ │ ├── main.scss
│ │ │ ├── variables.scss
│ │ │ ├── texts.scss
│ │ │ └── backgrounds.scss
│ │ └── css
│ │ └── index.css
├── views
│ ├── HomeView.vue
│ └── DashboardView.vue
├── utils
│ ├── index.js
│ ├── time.js
│ └── request.js
├── plugins
│ ├── router.js
│ ├── i18.js
│ ├── index.js
│ └── pinia.js
├── layouts
│ ├── AppLayout.vue
│ └── Default.vue
├── stores
│ ├── all.js
│ ├── index.js
│ ├── message.js
│ ├── user.js
│ └── sales.js
├── App.vue
├── main.js
├── components
│ ├── utils
│ │ ├── Chart.vue
│ │ ├── GetErrorSuccess.vue
│ │ ├── Skeletons
│ │ │ ├── ChartSkeleton.vue
│ │ │ └── TableSkeleton.vue
│ │ └── VueTable.vue
│ ├── home
│ │ └── Login.vue
│ └── dashboard
│ │ └── ComparisonTable.vue
├── router
│ ├── index.js
│ └── middleware
│ │ └── loadLayoutMiddleware.js
└── CONSTANTS.js
├── .env
├── .vscode
└── extensions.json
├── .gitattributes
├── .firebaserc
├── postcss.config.js
├── public
├── favicon.ico
├── favicon-16x16.png
├── favicon-32x32.png
├── apple-touch-icon.png
├── android-chrome-192x192.png
├── android-chrome-512x512.png
└── site.webmanifest
├── jsconfig.json
├── tailwind.config.js
├── firebase.json
├── index.html
├── .gitignore
├── .prettierrc.json
├── .github
└── workflows
│ ├── firebase-hosting-merge.yml
│ ├── firebase-hosting-pull-request.yml
│ └── sonarcloud.yml
├── package.json
├── vite.config.js
├── README.md
├── .eslintrc.js
└── .eslintrc-auto-import.json
/src/locales/messages.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | VITE_OPENAI_API_BASEURL=https://iapitest.eva.guru/
--------------------------------------------------------------------------------
/src/assets/styles/scss/theme-styles.scss:
--------------------------------------------------------------------------------
1 | @import './parts/main.scss';
2 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": ["Vue.volar"]
3 | }
4 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/src/views/HomeView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.firebaserc:
--------------------------------------------------------------------------------
1 | {
2 | "projects": {
3 | "default": "vue3-highcharts-tailwind"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/src/locales/index.js:
--------------------------------------------------------------------------------
1 | import messages from './messages.json';
2 |
3 | export default messages;
4 |
--------------------------------------------------------------------------------
/src/utils/index.js:
--------------------------------------------------------------------------------
1 | export { request } from './request'
2 | export { dateWithDay } from './time'
3 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mustafacagri/vue3-highcharts-tailwind-boilerplate/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/src/plugins/router.js:
--------------------------------------------------------------------------------
1 | import router from '@/router'
2 |
3 | export const initRouter = () => {
4 | return router
5 | }
6 |
--------------------------------------------------------------------------------
/public/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mustafacagri/vue3-highcharts-tailwind-boilerplate/HEAD/public/favicon-16x16.png
--------------------------------------------------------------------------------
/public/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mustafacagri/vue3-highcharts-tailwind-boilerplate/HEAD/public/favicon-32x32.png
--------------------------------------------------------------------------------
/src/assets/styles/scss/parts/main.scss:
--------------------------------------------------------------------------------
1 | @import './variables.scss';
2 | @import './texts.scss';
3 | @import './backgrounds.scss';
4 |
--------------------------------------------------------------------------------
/src/layouts/AppLayout.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mustafacagri/vue3-highcharts-tailwind-boilerplate/HEAD/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mustafacagri/vue3-highcharts-tailwind-boilerplate/HEAD/public/android-chrome-192x192.png
--------------------------------------------------------------------------------
/public/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mustafacagri/vue3-highcharts-tailwind-boilerplate/HEAD/public/android-chrome-512x512.png
--------------------------------------------------------------------------------
/src/stores/all.js:
--------------------------------------------------------------------------------
1 | export { useMessageStore } from './message'
2 | export { useSalesStore } from './sales'
3 | export { useUserStore } from './user'
4 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "paths": {
4 | "@/*": ["./src/*"]
5 | }
6 | },
7 | "exclude": ["node_modules", "dist"]
8 | }
9 |
--------------------------------------------------------------------------------
/src/assets/styles/scss/parts/variables.scss:
--------------------------------------------------------------------------------
1 | /* Colors */
2 | $primary: #0064ff;
3 | $secondary: #100045;
4 | $lightergrey: #f5f5f5;
5 | $lightgrey: #e8e8e8;
6 | $green: #05ad48;
7 | $orange: #f48c11;
8 | $red: #ff005c;
9 |
--------------------------------------------------------------------------------
/src/layouts/Default.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
3 | theme: {
4 | extend: {},
5 | },
6 | variants: {
7 | extend: {},
8 | },
9 | plugins: [],
10 | }
11 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/firebase.json:
--------------------------------------------------------------------------------
1 | {
2 | "hosting": {
3 | "public": "dist",
4 | "ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
5 | "rewrites": [
6 | {
7 | "source": "**",
8 | "destination": "/index.html"
9 | }
10 | ]
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/public/site.webmanifest:
--------------------------------------------------------------------------------
1 | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
--------------------------------------------------------------------------------
/src/plugins/i18.js:
--------------------------------------------------------------------------------
1 | import messages from '@/locales'
2 | import { createI18n } from 'vue-i18n'
3 |
4 | const DEFAULT_LOCALE = 'en'
5 |
6 | export const initI18n = () => {
7 | return createI18n({
8 | legacy: false,
9 | locale: DEFAULT_LOCALE,
10 | messages,
11 | })
12 | }
13 |
--------------------------------------------------------------------------------
/src/stores/index.js:
--------------------------------------------------------------------------------
1 | import { defineStore } from "pinia";
2 | export * from "./all"; // it will be an easy way to use stores like => import { useConfig, useUser } from '@/stores'
3 |
4 | export const useMainStore = defineStore("main", {
5 | state: () => reactive({}),
6 | getters: {},
7 | actions: {},
8 | });
9 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import '@/assets/styles/css/index.css'
2 |
3 | import { initPlugins } from './plugins'
4 | import { createApp } from 'vue/dist/vue.esm-bundler' // https://github.com/fengyuanchen/vue-feather/issues/8
5 |
6 | import App from './App.vue'
7 |
8 | const app = createApp(App)
9 |
10 | initPlugins(app)
11 |
12 | app.mount('#app')
13 |
--------------------------------------------------------------------------------
/src/components/utils/Chart.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
16 |
17 |
--------------------------------------------------------------------------------
/src/utils/time.js:
--------------------------------------------------------------------------------
1 | import { format } from 'date-fns'
2 |
3 | export const dateWithDay = dateString => {
4 | let response = ''
5 |
6 | if (dateString) {
7 | try {
8 | const date = new Date(dateString)
9 | response = format(date, 'EEEE, MM-dd-yyyy')
10 | } catch (error) {
11 | console.log(error)
12 | }
13 | }
14 |
15 | return response
16 | }
17 |
--------------------------------------------------------------------------------
/src/plugins/index.js:
--------------------------------------------------------------------------------
1 | import { initI18n } from "./i18";
2 | import { initPinia } from "./pinia";
3 | import { initRouter } from "./router";
4 |
5 | export const initPlugins = (app) => {
6 | const i18n = initI18n(app);
7 | const router = initRouter(app);
8 | const pinia = initPinia(app, router, i18n);
9 |
10 | app.use(i18n);
11 | app.use(pinia);
12 | app.use(router);
13 | };
14 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Highcharts Tailwind Boilerplate
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/assets/styles/scss/parts/texts.scss:
--------------------------------------------------------------------------------
1 | .text-primary {
2 | color: $primary;
3 | }
4 |
5 | .text-secondary {
6 | color: $secondary;
7 | }
8 |
9 | .text-success {
10 | color: $green;
11 | }
12 |
13 | .text-warninge {
14 | color: $orange;
15 | }
16 |
17 | .text-danger {
18 | color: $red;
19 | }
20 |
21 | .text-lightergrey {
22 | color: $lightergrey;
23 | }
24 |
25 | .text-lightgrey {
26 | color: $lightgrey;
27 | }
28 |
--------------------------------------------------------------------------------
/src/assets/styles/scss/parts/backgrounds.scss:
--------------------------------------------------------------------------------
1 | .bg-primary {
2 | background-color: $primary;
3 | }
4 |
5 | .bg-secondary {
6 | background-color: $secondary;
7 | }
8 |
9 | .bg-success {
10 | background-color: $green;
11 | }
12 |
13 | .bg-warning {
14 | background-color: $orange;
15 | }
16 |
17 | .bg-danger {
18 | background-color: $red;
19 | }
20 |
21 | .bg-lightergrey {
22 | background-color: $lightergrey;
23 | }
24 |
25 | .bg-lightgrey {
26 | background-color: $lightgrey;
27 | }
28 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | .DS_Store
12 | dist
13 | dist-ssr
14 | coverage
15 | *.local
16 |
17 | /cypress/videos/
18 | /cypress/screenshots/
19 |
20 | .npmrc
21 | .env
22 |
23 | # Editor directories and files
24 | .vscode
25 | .vscode/*
26 | !.vscode/extensions.json
27 | .idea
28 | *.suo
29 | *.ntvs*
30 | *.njsproj
31 | *.sln
32 | *.sw?
33 |
34 | *.tsbuildinfo
35 |
--------------------------------------------------------------------------------
/src/plugins/pinia.js:
--------------------------------------------------------------------------------
1 | import router from '@/router'
2 | import { createPinia } from 'pinia'
3 | import { markRaw } from 'vue'
4 |
5 | const pinia = createPinia()
6 |
7 | pinia.use(({ store }) => {
8 | store.router = markRaw(router)
9 | store.i18n = markRaw(i18n)
10 | })
11 |
12 | export const initPinia = (app, router, i18n) => {
13 | const pinia = createPinia()
14 |
15 | pinia.use(({ store }) => {
16 | store.router = markRaw(router)
17 | store.i18n = markRaw(i18n)
18 | })
19 |
20 | return pinia
21 | }
22 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "arrowParens": "avoid",
3 | "bracketSpacing": true,
4 | "htmlWhitespaceSensitivity": "css",
5 | "insertPragma": false,
6 | "jsxBracketSameLine": false,
7 | "jsxSingleQuote": true,
8 | "printWidth": 120,
9 | "proseWrap": "preserve",
10 | "quoteProps": "as-needed",
11 | "requirePragma": false,
12 | "semi": false,
13 | "singleQuote": true,
14 | "tabWidth": 2,
15 | "trailingComma": "all",
16 | "useTabs": false,
17 | "vueIndentScriptAndStyle": false,
18 | "endOfLine": "lf",
19 | "singleAttributePerLine": true
20 | }
--------------------------------------------------------------------------------
/src/router/index.js:
--------------------------------------------------------------------------------
1 | import { createRouter, createWebHistory } from 'vue-router'
2 | import { loadLayoutMiddleware } from '@/router/middleware/loadLayoutMiddleware'
3 |
4 | const router = createRouter({
5 | history: createWebHistory(import.meta.env.BASE_URL),
6 | routes: [
7 | {
8 | path: '/',
9 | name: 'home',
10 | component: () => import('../views/HomeView.vue'),
11 | },
12 | {
13 | path: '/dashboard',
14 | name: 'dashboard',
15 | component: () => import('../views/DashboardView.vue'),
16 | },
17 | ],
18 | })
19 |
20 | router.beforeEach(loadLayoutMiddleware)
21 |
22 | export default router
23 |
--------------------------------------------------------------------------------
/.github/workflows/firebase-hosting-merge.yml:
--------------------------------------------------------------------------------
1 | # This file was auto-generated by the Firebase CLI
2 | # https://github.com/firebase/firebase-tools
3 |
4 | name: Deploy to Firebase Hosting on merge
5 | on:
6 | push:
7 | branches:
8 | - main
9 | jobs:
10 | build_and_deploy:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v4
14 | - run: yarn install && yarn build
15 | - uses: FirebaseExtended/action-hosting-deploy@v0
16 | with:
17 | repoToken: ${{ secrets.GITHUB_TOKEN }}
18 | firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_VUE3_HIGHCHARTS_TAILWIND }}
19 | channelId: live
20 | projectId: vue3-highcharts-tailwind
21 |
--------------------------------------------------------------------------------
/src/assets/styles/css/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | /* WebKit scrollbar styles */
6 | ::-webkit-scrollbar {
7 | width: 10px;
8 | }
9 |
10 | ::-webkit-scrollbar-thumb {
11 | background: #e1e1e1;
12 | border-radius: 5px;
13 | }
14 |
15 | ::-webkit-scrollbar-thumb:hover {
16 | background: #d1d1d1;
17 | }
18 |
19 | /* Firefox scrollbar styles */
20 | body {
21 | scrollbar-width: thin; /* "auto" or "thin" */
22 | scrollbar-color: #e1e1e1 #ffffff; /* thumb and track colors */
23 | }
24 |
25 | body::-webkit-scrollbar {
26 | width: 10px;
27 | }
28 |
29 | body::-webkit-scrollbar-thumb {
30 | background: #e1e1e1;
31 | border-radius: 5px;
32 | }
33 |
34 | body::-webkit-scrollbar-thumb:hover {
35 | background: #d1d1d1;
36 | }
37 |
--------------------------------------------------------------------------------
/src/router/middleware/loadLayoutMiddleware.js:
--------------------------------------------------------------------------------
1 | // this middleware is used to dynamically update the Layouts system.
2 |
3 | // as soon as the route changes, it tries to pull the layout that the users want to display. Then it loads the layout component, and assigns the loaded component to the meta in the layout Component variable.
4 |
5 | // if there is no layout defined, the default layout will be used: default
6 |
7 | export async function loadLayoutMiddleware(route) {
8 | try {
9 | const layout = route.meta.layout
10 | const layoutComponent = await import(`../../layouts/${layout}.vue`)
11 | route.meta.layoutComponent = layoutComponent.default
12 | } catch (_e) {
13 | const layoutComponent = await import(`../../layouts/Default.vue`)
14 | route.meta.layoutComponent = layoutComponent.default
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/.github/workflows/firebase-hosting-pull-request.yml:
--------------------------------------------------------------------------------
1 | # This file was auto-generated by the Firebase CLI
2 | # https://github.com/firebase/firebase-tools
3 |
4 | name: Deploy to Firebase Hosting on PR
5 | on: pull_request
6 | permissions:
7 | checks: write
8 | contents: read
9 | pull-requests: write
10 | jobs:
11 | build_and_preview:
12 | if: ${{ github.event.pull_request.head.repo.full_name == github.repository }}
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: actions/checkout@v4
16 | - run: yarn install && yarn build
17 | - uses: FirebaseExtended/action-hosting-deploy@v0
18 | with:
19 | repoToken: ${{ secrets.GITHUB_TOKEN }}
20 | firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_VUE3_HIGHCHARTS_TAILWIND }}
21 | projectId: vue3-highcharts-tailwind
22 |
--------------------------------------------------------------------------------
/src/components/utils/GetErrorSuccess.vue:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 |
25 | {{ error }}
26 |
27 |
28 |
32 | {{ success }}
33 |
34 |
35 |
--------------------------------------------------------------------------------
/src/CONSTANTS.js:
--------------------------------------------------------------------------------
1 | const config = {
2 | api: {
3 | login: 'oauth/token',
4 | me: 'user/user-information',
5 | sales: {
6 | dailySalesOverview: 'data/daily-sales-overview',
7 | skuData: 'data/daily-sales-sku-list',
8 | skuRefundRates: 'data/get-sku-refund-rate',
9 | },
10 | },
11 | dailySalesOverviewDays: [7, 14, 30, 60],
12 | defaultCurrency: '$',
13 | defaultRecordPerPage: 10,
14 | defaultRecordSize: 30,
15 | errors: {
16 | dashboard: { maxComparisonDays: 'You can not compare more than 2 days' },
17 | noData: 'No data available',
18 | sales: { errorFetching: 'Error fetching sales' },
19 | users: {
20 | loginError: 'Invalid Credentials',
21 | invalidToken: 'Invalid Token',
22 | userData: 'Error fetching user data',
23 | email: 'Invalid Email',
24 | get password() {
25 | return `Password should be at least ${config?.password?.minLength || 8} characters`
26 | },
27 | },
28 | },
29 | maxComparisonDays: 2,
30 | password: { minLength: 8 },
31 | routes: { dashboard: '/dashboard' },
32 | }
33 |
34 | export default config
35 |
--------------------------------------------------------------------------------
/src/components/utils/Skeletons/ChartSkeleton.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "highcharts-tailwind-boilerplate",
3 | "version": "0.0.0",
4 | "private": true,
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "preview": "vite preview",
10 | "sass": "sass --watch src/assets/styles/scss/theme-styles.scss:theme-style.css"
11 | },
12 | "dependencies": {
13 | "axios": "^1.7.2",
14 | "date-fns": "^3.6.0",
15 | "highcharts": "^11.4.3",
16 | "highcharts-vue": "^2.0.1",
17 | "lodash": "^4.17.21",
18 | "pinia": "^2.1.7",
19 | "sass": "^1.77.6",
20 | "validator": "^13.12.0",
21 | "vite-plugin-commonjs": "^0.10.1",
22 | "vite-plugin-node-polyfills": "^0.19.0",
23 | "vue": "^3.4.29",
24 | "vue-i18n": "^9.13.1",
25 | "vue-router": "^4.3.3"
26 | },
27 | "devDependencies": {
28 | "@vitejs/plugin-vue": "^5.0.5",
29 | "autoprefixer": "^10.4.19",
30 | "postcss": "^8.4.38",
31 | "tailwindcss": "^3.4.4",
32 | "unplugin-auto-import": "^0.16.7",
33 | "unplugin-vue-components": "^0.25.2",
34 | "unplugin-vue-define-options": "^1.3.18",
35 | "vite": "^5.3.1",
36 | "vite-plugin-pages": "^0.31.0",
37 | "vite-plugin-vue-devtools": "^7.3.1",
38 | "vite-plugin-vue-layouts": "^0.8.0"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/stores/message.js:
--------------------------------------------------------------------------------
1 | import { defineStore } from 'pinia'
2 |
3 | export const useMessageStore = defineStore('message', {
4 | state: () => ({ error: null, isSuccess: null, errorTime: 5000, successTime: 5000 }),
5 | getters: {
6 | getError() {
7 | return this.error
8 | },
9 | getIsSuccess() {
10 | return this.isSuccess
11 | },
12 | },
13 | actions: {
14 | setErrorClear(payload) {
15 | this.error = payload?.error
16 | let time = payload?.time && payload?.time != undefined ? payload?.time : this.errorTime
17 | setTimeout(() => {
18 | this.error = null
19 | }, time)
20 | },
21 |
22 | setIsSuccessClear(payload) {
23 | try {
24 | this.error = null
25 | this.isSuccess = payload?.message || null
26 |
27 | const time = payload?.time ? payload.time : this.successTime
28 |
29 | setTimeout(() => {
30 | this.isSuccess = null
31 | }, time)
32 | } catch (error) {
33 | this.setErrorClear({ error })
34 | }
35 | },
36 | setIsSuccess(payload) {
37 | try {
38 | this.error = null
39 | this.setIsSuccessClear(payload)
40 | } catch (error) {
41 | this.setErrorClear({ error })
42 | }
43 | },
44 |
45 | setError(payload) {
46 | try {
47 | this.isSuccess = null
48 | this.setErrorClear(payload)
49 | } catch (error) {
50 | this.setErrorClear({ error })
51 | }
52 | },
53 | clearError() {
54 | this.error = null
55 | },
56 | },
57 | })
58 |
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | import vue from '@vitejs/plugin-vue'
2 | import AutoImport from 'unplugin-auto-import/vite'
3 | import Components from 'unplugin-vue-components/vite'
4 | import DefineOptions from 'unplugin-vue-define-options/vite'
5 | import { fileURLToPath } from 'url'
6 | import { defineConfig } from 'vite'
7 | import Pages from 'vite-plugin-pages'
8 | import Layouts from 'vite-plugin-vue-layouts'
9 | import commonjs from 'vite-plugin-commonjs'
10 | import { nodePolyfills } from 'vite-plugin-node-polyfills'
11 |
12 | // https://vitejs.dev/config/
13 | export default defineConfig({
14 | plugins: [
15 | vue({
16 | template: {
17 | compilerOptions: {},
18 | },
19 | }),
20 | commonjs(),
21 | nodePolyfills(),
22 | Pages({}),
23 | Layouts(),
24 | Components({
25 | /* options */
26 | }),
27 | AutoImport({
28 | eslintrc: {
29 | enabled: true,
30 | filepath: './.eslintrc-auto-import.json',
31 | },
32 | imports: ['vue', 'vue-router', 'pinia'],
33 | vueTemplate: true,
34 | }),
35 | DefineOptions(),
36 | ],
37 | css: {
38 | preprocessorOptions: {
39 | scss: {
40 | additionalData: `@import "@/assets/styles/scss/parts/main.scss";`,
41 | },
42 | },
43 | },
44 | define: { 'process.env': {} },
45 | resolve: {
46 | alias: {
47 | vue$: 'vue/dist/vue.esm-bundler.js',
48 | '@': fileURLToPath(new URL('./src', import.meta.url)),
49 | },
50 | },
51 | build: {
52 | chunkSizeWarningLimit: 10000,
53 | rollupOptions: {
54 | output: {
55 | inlineDynamicImports: true,
56 | },
57 | },
58 | },
59 | server: {
60 | port: 5018, // Change this to your desired port number
61 | },
62 | })
63 |
--------------------------------------------------------------------------------
/.github/workflows/sonarcloud.yml:
--------------------------------------------------------------------------------
1 | name: SonarCloud analysis
2 |
3 | on:
4 | push:
5 | branches: ['main']
6 | pull_request:
7 | branches: ['main']
8 | workflow_dispatch:
9 |
10 | permissions:
11 | pull-requests: read # allows SonarCloud to decorate PRs with analysis results
12 |
13 | jobs:
14 | Analysis:
15 | runs-on: ubuntu-latest
16 |
17 | steps:
18 | - name: Analyze with SonarCloud
19 |
20 | # You can pin the exact commit or the version.
21 | # uses: SonarSource/sonarcloud-github-action@v2.2.0
22 | uses: SonarSource/sonarcloud-github-action@4006f663ecaf1f8093e8e4abb9227f6041f52216
23 | env:
24 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} # Generate a token on Sonarcloud.io, add it to the secrets of this repo with the name SONAR_TOKEN (Settings > Secrets > Actions > add new repository secret)
25 | with:
26 | # Additional arguments for the SonarScanner CLI
27 | args:
28 | # Unique keys of your project and organization. You can find them in SonarCloud > Information (bottom-left menu)
29 | # mandatory
30 | -Dsonar.projectKey=mustafacagri_vue3-highcharts-tailwind-boilerplate
31 | -Dsonar.organization=mustafacagri
32 | # Comma-separated paths to directories containing main source files.
33 | #-Dsonar.sources= # optional, default is project base directory
34 | # Comma-separated paths to directories containing test source files.
35 | #-Dsonar.tests= # optional. For more info about Code Coverage, please refer to https://docs.sonarcloud.io/enriching/test-coverage/overview/
36 | # Adds more detail to both client and server-side analysis logs, activating DEBUG mode for the scanner, and adding client-side environment variables and system properties to the server-side log of analysis report processing.
37 | #-Dsonar.verbose= # optional, default is false
38 | # When you need the analysis to take place in a directory other than the one from which it was launched, default is .
39 | projectBaseDir: .
40 |
--------------------------------------------------------------------------------
/src/components/utils/Skeletons/TableSkeleton.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
13 |
20 |
27 |
34 |
41 |
Loading...
42 |
43 |
44 |
--------------------------------------------------------------------------------
/src/components/utils/VueTable.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
17 | {{ CONSTANTS?.errors?.noData }}
18 |
19 |
20 |
21 |
22 | |
26 |
31 | |
32 |
33 |
34 |
35 |
39 | |
44 |
48 |
49 |
50 | |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
94 |
--------------------------------------------------------------------------------
/src/utils/request.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import CONSTANTS from '@/CONSTANTS'
3 | import { useMessageStore } from '@/stores'
4 |
5 | const request = async (type, pureUrl, params = {}, time = null) => {
6 | pureUrl ??= '' // if pureUrl is undefined, set it to an empty string
7 |
8 | const checkResponse = async data => {
9 | return data
10 | .then(res => {
11 | if (!res?.data) {
12 | useMessageStore().setErrorClear({ error: 'Server Error' })
13 | } else {
14 | const { ApiStatusMessage: message } = res.data
15 |
16 | if (res?.data?.ApiStatus !== 200) {
17 | useMessageStore().setErrorClear({ error: message, time })
18 | } else if (res.data) {
19 | useMessageStore().setIsSuccess({ message, time })
20 | } else {
21 | useMessageStore().setErrorClear({ error: message, time })
22 | }
23 |
24 | return res.data
25 | }
26 | })
27 | .catch(error => {
28 | error = error?.response?.data?.ApiStatusMessage || 'Server Error'
29 |
30 | useMessageStore().setError({ error })
31 | })
32 | }
33 |
34 | const baseUrl = import.meta.env.VITE_OPENAI_API_BASEURL || ''
35 | const url = baseUrl.endsWith('/') ? baseUrl + pureUrl : baseUrl + '/' + pureUrl
36 | const token = sessionStorage.getItem('token')
37 | let contentType = 'application/json'
38 |
39 | const potentialFiles = ['image', 'image', 'file', 'files']
40 | const foundPropertyInParams = potentialFiles.find(item => Object.prototype.hasOwnProperty.call(params, item))
41 |
42 | if (foundPropertyInParams && !!params[foundPropertyInParams]) {
43 | contentType = 'multipart/form-data'
44 | }
45 |
46 | const headers = {
47 | Authorization: 'Bearer ' + token,
48 | 'content-type': contentType,
49 | }
50 |
51 | const withCredentials = false
52 |
53 | let options = {
54 | params,
55 | headers,
56 | withCredentials,
57 | }
58 |
59 | const response = ['get', 'delete'].includes(type)
60 | ? axios[type](url, options)
61 | : axios[type](url, params, { headers, withCredentials })
62 |
63 | const checkedResponse = await checkResponse(response)
64 | if (checkedResponse) {
65 | // we do not return anything if the request failed since we already used useMessageStore().setError
66 |
67 | return checkedResponse
68 | }
69 | }
70 |
71 | export { request }
72 |
--------------------------------------------------------------------------------
/src/components/home/Login.vue:
--------------------------------------------------------------------------------
1 |
30 |
31 |
32 |
33 |
34 |
Login
35 |
93 |
94 |
95 |
--------------------------------------------------------------------------------
/src/stores/user.js:
--------------------------------------------------------------------------------
1 | import { defineStore } from 'pinia'
2 | import { request } from '@/utils'
3 | import { useMessageStore, useSalesStore } from '@/stores'
4 | import CONSTANTS from '@/CONSTANTS'
5 | import router from '@/router'
6 | import validator from 'validator'
7 |
8 | export const useUserStore = defineStore('user', () => {
9 | const messageStore = useMessageStore()
10 | const salesStore = useSalesStore()
11 |
12 | const state = reactive({
13 | user: {},
14 | marketplace: null,
15 | sellerId: null,
16 | })
17 |
18 | const login = async payload => {
19 | let response = false
20 |
21 | if (!validator.isEmail(payload?.Email)) {
22 | messageStore.setError({ error: CONSTANTS?.errors?.users?.email })
23 |
24 | return response
25 | } else if (!validator.isLength(payload?.Password, { min: CONSTANTS?.password?.minLength })) {
26 | messageStore.setError({ error: CONSTANTS?.errors?.users?.password })
27 |
28 | return response
29 | }
30 |
31 | const loginPayload = {
32 | ...payload,
33 | GrantType: 'password',
34 | Scope: 'amazon_data',
35 | ClientId: 'C0001',
36 | ClientSecret: 'SECRET0001',
37 | RedirectUri: 'https://api.eva.guru',
38 | }
39 |
40 | try {
41 | const res = await request('post', CONSTANTS?.api?.login, loginPayload)
42 |
43 | if (res?.Data?.AccessToken) {
44 | sessionStorage.setItem('token', res.Data.AccessToken)
45 | sessionStorage.setItem('email', payload.Email)
46 | response = true
47 | } else {
48 | messageStore.setError({ error: CONSTANTS?.errors?.users?.loginError })
49 | }
50 | } catch (error) {
51 | messageStore.setError({ error: CONSTANTS?.errors?.users?.loginError })
52 | }
53 |
54 | return response
55 | }
56 |
57 | const fetchMe = async () => {
58 | salesStore.dailySalesOverview = []
59 | const email = sessionStorage.getItem('email') || ''
60 | let hasError = true
61 |
62 | if (email) {
63 | const payload = { email }
64 |
65 | try {
66 | const res = await request('post', CONSTANTS?.api?.me, payload)
67 | if (res?.Data?.user) {
68 | state.user = res.Data.user
69 |
70 | const store = res.Data.user?.store?.[0]
71 |
72 | if (store?.marketplaceName && store?.storeId) {
73 | state.marketplace = store.marketplaceName
74 | state.sellerId = store.storeId
75 | hasError = false
76 |
77 | await salesStore.fetchDailySalesOverview()
78 | }
79 | }
80 | } catch (error) {}
81 | }
82 |
83 | if (hasError) {
84 | sessionStorage.removeItem('token')
85 | sessionStorage.removeItem('email')
86 | messageStore.setError({ error: CONSTANTS?.errors?.users?.userData })
87 | router.push({ path: '/' })
88 | }
89 | }
90 |
91 | return {
92 | ...toRefs(state), // return all properties in state as refs
93 | fetchMe,
94 | login,
95 | }
96 | })
97 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 🌟 Vue 3 HighCharts Tailwind Boilerplate
2 |
3 | ✨ Powered by Modern Technologies
4 |
5 | - **Vue 3**: The versatile powerhouse that makes building dynamic user interfaces a breeze.
6 | - **Tailwind**: A utility-first CSS framework for rapid UI development.
7 | - **Pinia**: Simplified and intuitive state management for Vue 3 applications.
8 | - **Vite**: Lightning-fast build tool that ensures your development experience is as smooth as silk.
9 | - **i18n**: Ready to go with translations, though not yet implemented.
10 |
11 | ## 🎥 Demo
12 |
13 | https://github.com/mustafacagri/vue3-highcharts-tailwind-boilerplate/assets/7488394/099e6d53-f6c0-4e23-b0d8-d195d8cd231b
14 |
15 | ### 🌐 Live Demo
16 |
17 | - https://vue3-highcharts-tailwind.web.app/
18 |
19 | ### 🔑 Credentials
20 |
21 | - homework@eva.guru
22 | - Homeworkeva1\*\*
23 |
24 | ## Component Structure
25 |
26 | - **Skeletons**
27 |
28 | - **ChartSkeleton**: Placeholder for charts.
29 | - **TableSkeleton**: Placeholder for tables.
30 |
31 | - **Chart**: The component to display our charts.
32 |
33 | - **GetErrorSuccess**
34 | - **VueTable**: Component with pagination and search functionalities.
35 |
36 | ## Layout Structure
37 |
38 | The repository includes a structured layout schema that can be easily utilized. To use a new layout, simply specify its name in the router when creating a new route. All handling is managed in the `loadLayoutMiddleware.js` file.
39 |
40 | ## View Structure
41 |
42 | There are two main views in this repository:
43 |
44 | - **Home (Login screen)**: Entry point of the application.
45 | - **Dashboard**: Displays charts and related tables.
46 |
47 | ## Utility Files
48 |
49 | - **Request**: Centralized handling of HTTP requests.
50 | - **Time**: Utility functions for consistent date formatting across the application.
51 |
52 | ## CONSTANTS!
53 |
54 | The `CONSTANTS.js` file defines crucial constants for the application:
55 |
56 | ```javascript
57 | // CONSTANTS.js
58 | export default {
59 | api: {
60 | login: 'oauth/token',
61 | me: 'user/user-information',
62 | sales: {
63 | dailySalesOverview: 'data/daily-sales-overview',
64 | skuData: 'data/daily-sales-sku-list',
65 | skuRefundRates: 'data/get-sku-refund-rate',
66 | },
67 | },
68 | dailySalesOverviewDays: [7, 14, 30, 60],
69 | defaultCurrency: '$',
70 | defaultRecordPerPage: 10,
71 | defaultRecordSize: 30,
72 | errors: {
73 | dashboard: { maxComparisonDays: 'You can not compare more than 2 days' },
74 | noData: 'No data available',
75 | sales: { errorFetching: 'Error fetching sales' },
76 | users: { loginError: 'Invalid Credentials', invalidToken: 'Invalid Token' },
77 | },
78 | maxComparisonDays: 2,
79 | routes: { dashboard: '/dashboard' },
80 | }
81 | ```
82 |
83 | ## .env
84 |
85 | ```
86 | VITE_OPENAI_API_BASEURL=https://iapitest.eva.guru/
87 | ```
88 |
89 | ## 🚀 Features
90 |
91 | **State Management**
92 |
93 | - **Pinia**: Simplified and intuitive state management for maintaining the application state.
94 |
95 | **Efficient Development Workflow**
96 |
97 | - **Vite**: Fast build tool for an efficient development experience.
98 | - **Modular Code Structure**: Clean and maintainable codebase with Vue 3's Composition API.
99 |
100 | ## 🛠️ Setup Instructions
101 |
102 | ### Cloning and Running Locally
103 |
104 | To get started, clone the repository and run the development server:
105 |
106 | ```bash
107 | git clone https://github.com/mustafacagri/vue3-highcharts-tailwind-boilerplate.git
108 | cd vue3-highcharts-tailwind-boilerplate
109 | yarn install
110 | yarn dev
111 | ```
112 |
113 | The application will be running at http://localhost:5018/. You can redefine the port in the vite.config.js file.
114 |
115 | ## How can I support? 🌟
116 |
117 | - ⭐ Star my GitHub repo.
118 | - 🛠 Create pull requests, submit bugs, suggest new features or updates.
119 |
--------------------------------------------------------------------------------
/src/stores/sales.js:
--------------------------------------------------------------------------------
1 | import { defineStore } from 'pinia'
2 | import { first, isEmpty } from 'lodash'
3 | import { request } from '@/utils'
4 | import { useMessageStore, useUserStore } from '@/stores'
5 | import CONSTANTS from '@/CONSTANTS'
6 |
7 | export const useSalesStore = defineStore('sales', () => {
8 | const messageStore = useMessageStore()
9 | const userStore = useUserStore()
10 |
11 | const state = reactive({
12 | currency: CONSTANTS?.defaultCurrency,
13 | dailySalesOverview: [],
14 | isSkuRefundRatesLoading: false,
15 | skuData: {},
16 | skuRefundRates: [],
17 | selectedDates: [],
18 | })
19 |
20 | const fetchDailySalesOverview = async (day = first(CONSTANTS.dailySalesOverviewDays)) => {
21 | state.dailySalesOverview = []
22 | state.skuData = {}
23 | state.selectedDates = []
24 |
25 | const payload = {
26 | customDateData: null,
27 | day,
28 | excludeYoYData: true,
29 | marketplace: userStore.marketplace,
30 | requestStatus: 0,
31 | sellerId: userStore.sellerId,
32 | }
33 |
34 | try {
35 | const res = await request('post', CONSTANTS?.api?.sales?.dailySalesOverview, payload)
36 |
37 | if (!isEmpty(res?.Data)) {
38 | updateCurrency(res.Data.Currency)
39 | updateDailySalesOverview(res.Data.item)
40 | } else {
41 | messageStore.setError({ error: CONSTANTS?.errors?.sales?.errorFetching })
42 | }
43 | } catch (error) {
44 | messageStore.setError({ error: CONSTANTS?.errors?.sales?.errorFetching })
45 | }
46 | }
47 |
48 | const updateCurrency = currency => {
49 | if (currency) {
50 | state.currency = currency
51 | }
52 | }
53 |
54 | const updateDailySalesOverview = items => {
55 | state.dailySalesOverview = items.map(item => ({
56 | date: item.date,
57 | fbaAmount: item.fbaAmount,
58 | fbmAmount: item.fbmAmount,
59 | fbaShippingAmount: item.fbaShippingAmount,
60 | profit: item.profit,
61 | }))
62 | }
63 |
64 | const fetchSkuData = async payload => {
65 | payload.pageNumber ||= 1
66 |
67 | const { dates, pageNumber } = payload
68 | state.selectedDates = dates
69 |
70 | if (isEmpty(dates) || !Array.isArray(dates)) {
71 | state.skuRefundRates = []
72 | return
73 | }
74 |
75 | state.isSkuRefundRatesLoading = true
76 | const data = buildRequestData(dates, pageNumber)
77 |
78 | try {
79 | const res = await request('post', CONSTANTS?.api?.sales?.skuData, data)
80 | handleSkuResponse(res, pageNumber)
81 | } catch (error) {
82 | messageStore.setError({ error: CONSTANTS?.errors?.sales?.errorFetching })
83 | }
84 | }
85 |
86 | const buildRequestData = (dates, pageNumber) => {
87 | const data = {
88 | isDaysCompare: dates.length > 1 ? 1 : 0,
89 | marketplace: userStore.marketplace,
90 | pageNumber,
91 | pageSize: CONSTANTS.defaultRecordSize,
92 | salesDate: first(dates),
93 | sellerId: userStore.sellerId,
94 | }
95 |
96 | if (dates.length > 1) {
97 | data.salesDate2 = dates[1]
98 | }
99 |
100 | return data
101 | }
102 |
103 | const handleSkuResponse = (res, pageNumber) => {
104 | if (!isEmpty(res?.Data)) {
105 | if (pageNumber === 1) {
106 | state.skuData = res.Data
107 | } else {
108 | state.skuData.item.skuList = [...state.skuData.item.skuList, ...res.Data.item.skuList]
109 | }
110 | fetchSkuRefundRates()
111 | } else {
112 | messageStore.setError({ error: CONSTANTS?.errors?.sales?.errorFetching })
113 | }
114 | }
115 |
116 | const fetchSkuRefundRates = async () => {
117 | const payload = {
118 | marketplace: userStore.marketplace,
119 | sellerId: userStore.sellerId,
120 | skuList: state.skuData?.item?.skuList || [],
121 | }
122 |
123 | try {
124 | const res = await request('post', CONSTANTS?.api?.sales?.skuRefundRates, payload)
125 |
126 | if (!isEmpty(res?.Data)) {
127 | state.skuRefundRates = res.Data
128 | } else {
129 | messageStore.setError({ error: CONSTANTS?.errors?.sales?.errorFetching })
130 | }
131 | } catch (error) {
132 | messageStore.setError({ error: CONSTANTS?.errors?.sales?.errorFetching })
133 | }
134 |
135 | state.isSkuRefundRatesLoading = false
136 | }
137 |
138 | return {
139 | ...toRefs(state),
140 | fetchDailySalesOverview,
141 | fetchSkuData,
142 | }
143 | })
144 |
--------------------------------------------------------------------------------
/src/views/DashboardView.vue:
--------------------------------------------------------------------------------
1 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
118 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
162 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | browser: true,
4 | es2021: true,
5 | },
6 | extends: [
7 | '.eslintrc-auto-import.json',
8 | 'plugin:vue/vue3-recommended',
9 | 'plugin:import/recommended',
10 | 'plugin:promise/recommended',
11 | 'plugin:sonarjs/recommended',
12 | ],
13 | parser: 'vue-eslint-parser',
14 | parserOptions: {
15 | ecmaVersion: 13,
16 | sourceType: 'module',
17 | },
18 | plugins: ['vue'],
19 | ignorePatterns: ['src/@iconify/*.js', 'node_modules', 'dist', '*.d.ts'],
20 | rules: {
21 | 'import/extensions': never,
22 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
23 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
24 |
25 | // indentation (Already present in TypeScript)
26 | indent: ['error', 2],
27 |
28 | // Enforce trailing command (Already present in TypeScript)
29 | 'comma-dangle': ['error', 'always-multiline'],
30 |
31 | // Disable max-len
32 | 'max-len': 'off',
33 |
34 | // we don't want it
35 | semi: ['error', 'never'],
36 |
37 | // add parens ony when required in arrow function
38 | 'arrow-parens': ['error', 'as-needed'],
39 |
40 | // add new line above comment
41 | 'newline-before-return': 'error',
42 |
43 | // add new line above comment
44 | 'lines-around-comment': [
45 | 'error',
46 | {
47 | beforeBlockComment: true,
48 | beforeLineComment: true,
49 | allowBlockStart: true,
50 | allowClassStart: true,
51 | allowObjectStart: true,
52 | allowArrayStart: true,
53 | },
54 | ],
55 |
56 | 'vue/html-self-closing': [
57 | 'error',
58 | {
59 | html: {
60 | void: 'always',
61 | normal: 'always',
62 | component: 'always',
63 | },
64 | svg: 'always',
65 | math: 'always',
66 | },
67 | ],
68 |
69 | 'array-element-newline': ['error', 'consistent'],
70 | 'array-bracket-newline': ['error', 'consistent'],
71 |
72 | 'vue/multi-word-component-names': 'off',
73 |
74 | // Plugin: eslint-plugin-import
75 | 'import/prefer-default-export': 'off',
76 |
77 | // Plugin: eslint-plugin-import
78 | // For omitting extension for ts files
79 | 'import/extensions': [
80 | 'error',
81 | 'ignorePackages',
82 | {
83 | js: 'never',
84 | jsx: 'never',
85 | ts: 'never',
86 | tsx: 'never',
87 | },
88 | ],
89 |
90 | // ignore virtual files
91 | 'import/no-unresolved': [
92 | 2,
93 | {
94 | ignore: [
95 | '~pages$',
96 | 'virtual:generated-layouts',
97 |
98 | // Ignore vite's ?raw imports
99 | '.*?raw',
100 | ],
101 | },
102 | ],
103 |
104 | // Thanks: https://stackoverflow.com/a/63961972/10796681
105 | 'no-shadow': 'off',
106 |
107 | // Plugin: eslint-plugin-promise
108 | 'promise/always-return': 'off',
109 | 'promise/catch-or-return': 'off',
110 |
111 | // ESLint plugin vue
112 | 'vue/block-tag-newline': 'error',
113 | 'vue/component-api-style': 'error',
114 | 'vue/component-name-in-template-casing': ['error', 'PascalCase', { registeredComponentsOnly: false }],
115 | 'vue/custom-event-name-casing': [
116 | 'error',
117 | 'camelCase',
118 | {
119 | ignores: ['/^(click):[a-z]+((d)|([A-Z0-9][a-z0-9]+))*([A-Z])?/'],
120 | },
121 | ],
122 | 'vue/define-macros-order': 'error',
123 | 'vue/html-comment-content-newline': 'error',
124 | 'vue/html-comment-content-spacing': 'error',
125 | 'vue/html-comment-indent': 'error',
126 | 'vue/match-component-file-name': 'error',
127 | 'vue/no-child-content': 'error',
128 |
129 | // NOTE this rule only supported in SFC, Users of the unplugin-vue-define-options should disable that rule: https://github.com/vuejs/eslint-plugin-vue/issues/1886
130 | // 'vue/no-duplicate-attr-inheritance': 'error',
131 | 'vue/no-empty-component-block': 'error',
132 | 'vue/no-multiple-objects-in-class': 'error',
133 | 'vue/no-reserved-component-names': 'error',
134 | 'vue/no-template-target-blank': 'error',
135 | 'vue/no-useless-mustaches': 'error',
136 | 'vue/no-useless-v-bind': 'error',
137 | 'vue/padding-line-between-blocks': 'error',
138 | 'vue/prefer-separate-static-class': 'error',
139 | 'vue/prefer-true-attribute-shorthand': 'error',
140 | 'vue/v-on-function-call': 'error',
141 |
142 | // -- Extension Rules
143 | 'vue/no-irregular-whitespace': 'error',
144 | 'vue/template-curly-spacing': 'error',
145 |
146 | // -- Sonarlint
147 | 'sonarjs/no-duplicate-string': 'off',
148 | 'sonarjs/no-nested-template-literals': 'off',
149 | },
150 | settings: {
151 | 'import/resolver': {
152 | node: {
153 | extensions: ['.ts', '.js', '.tsx', '.jsx', '.mjs', '.vue'],
154 | },
155 | alias: {
156 | extensions: ['.ts', '.js', '.tsx', '.jsx', '.mjs', '.vue'],
157 | map: [
158 | ['@', './src'],
159 | ['@core', './src/@core'],
160 | ['@layouts', './src/@layouts'],
161 | ['@configured-variables', './src/styles/variables/_template.scss'],
162 | ['@axios', './src/plugins/axios'],
163 | ['apexcharts', 'node_modules/apexcharts-clevision'],
164 | ],
165 | },
166 | },
167 | },
168 | }
169 |
--------------------------------------------------------------------------------
/.eslintrc-auto-import.json:
--------------------------------------------------------------------------------
1 | {
2 | "globals": {
3 | "EffectScope": true,
4 | "acceptHMRUpdate": true,
5 | "asyncComputed": true,
6 | "autoResetRef": true,
7 | "computed": true,
8 | "computedAsync": true,
9 | "computedEager": true,
10 | "computedInject": true,
11 | "computedWithControl": true,
12 | "controlledComputed": true,
13 | "controlledRef": true,
14 | "createApp": true,
15 | "createEventHook": true,
16 | "createGlobalState": true,
17 | "createInjectionState": true,
18 | "createPinia": true,
19 | "createReactiveFn": true,
20 | "createSharedComposable": true,
21 | "createUnrefFn": true,
22 | "customRef": true,
23 | "debouncedRef": true,
24 | "debouncedWatch": true,
25 | "defineAsyncComponent": true,
26 | "defineComponent": true,
27 | "defineStore": true,
28 | "eagerComputed": true,
29 | "effectScope": true,
30 | "extendRef": true,
31 | "getActivePinia": true,
32 | "getCurrentInstance": true,
33 | "getCurrentScope": true,
34 | "h": true,
35 | "ignorableWatch": true,
36 | "inject": true,
37 | "isDefined": true,
38 | "isProxy": true,
39 | "isReactive": true,
40 | "isReadonly": true,
41 | "isRef": true,
42 | "logicAnd": true,
43 | "logicNot": true,
44 | "logicOr": true,
45 | "makeDestructurable": true,
46 | "mapActions": true,
47 | "mapGetters": true,
48 | "mapState": true,
49 | "mapStores": true,
50 | "mapWritableState": true,
51 | "markRaw": true,
52 | "nextTick": true,
53 | "onActivated": true,
54 | "onBeforeMount": true,
55 | "onBeforeUnmount": true,
56 | "onBeforeUpdate": true,
57 | "onClickOutside": true,
58 | "onDeactivated": true,
59 | "onErrorCaptured": true,
60 | "onKeyStroke": true,
61 | "onLongPress": true,
62 | "onMounted": true,
63 | "onRenderTracked": true,
64 | "onRenderTriggered": true,
65 | "onScopeDispose": true,
66 | "onServerPrefetch": true,
67 | "onStartTyping": true,
68 | "onUnmounted": true,
69 | "onUpdated": true,
70 | "pausableWatch": true,
71 | "provide": true,
72 | "reactify": true,
73 | "reactifyObject": true,
74 | "reactive": true,
75 | "reactiveComputed": true,
76 | "reactiveOmit": true,
77 | "reactivePick": true,
78 | "readonly": true,
79 | "ref": true,
80 | "refAutoReset": true,
81 | "refDebounced": true,
82 | "refDefault": true,
83 | "refThrottled": true,
84 | "refWithControl": true,
85 | "resolveComponent": true,
86 | "resolveRef": true,
87 | "resolveUnref": true,
88 | "setActivePinia": true,
89 | "setMapStoreSuffix": true,
90 | "shallowReactive": true,
91 | "shallowReadonly": true,
92 | "shallowRef": true,
93 | "storeToRefs": true,
94 | "syncRef": true,
95 | "syncRefs": true,
96 | "templateRef": true,
97 | "throttledRef": true,
98 | "throttledWatch": true,
99 | "toRaw": true,
100 | "toReactive": true,
101 | "toRef": true,
102 | "toRefs": true,
103 | "triggerRef": true,
104 | "tryOnBeforeMount": true,
105 | "tryOnBeforeUnmount": true,
106 | "tryOnMounted": true,
107 | "tryOnScopeDispose": true,
108 | "tryOnUnmounted": true,
109 | "unref": true,
110 | "unrefElement": true,
111 | "until": true,
112 | "useActiveElement": true,
113 | "useAsyncQueue": true,
114 | "useAsyncState": true,
115 | "useAttrs": true,
116 | "useBase64": true,
117 | "useBattery": true,
118 | "useBluetooth": true,
119 | "useBreakpoints": true,
120 | "useBroadcastChannel": true,
121 | "useBrowserLocation": true,
122 | "useCached": true,
123 | "useClamp": true,
124 | "useClipboard": true,
125 | "useColorMode": true,
126 | "useConfirmDialog": true,
127 | "useCounter": true,
128 | "useCssModule": true,
129 | "useCssVar": true,
130 | "useCssVars": true,
131 | "useCurrentElement": true,
132 | "useCycleList": true,
133 | "useDark": true,
134 | "useDateFormat": true,
135 | "useDebounce": true,
136 | "useDebounceFn": true,
137 | "useDebouncedRefHistory": true,
138 | "useDeviceMotion": true,
139 | "useDeviceOrientation": true,
140 | "useDevicePixelRatio": true,
141 | "useDevicesList": true,
142 | "useDisplayMedia": true,
143 | "useDocumentVisibility": true,
144 | "useDraggable": true,
145 | "useDropZone": true,
146 | "useElementBounding": true,
147 | "useElementByPoint": true,
148 | "useElementHover": true,
149 | "useElementSize": true,
150 | "useElementVisibility": true,
151 | "useEventBus": true,
152 | "useEventListener": true,
153 | "useEventSource": true,
154 | "useEyeDropper": true,
155 | "useFavicon": true,
156 | "useFetch": true,
157 | "useFileDialog": true,
158 | "useFileSystemAccess": true,
159 | "useFocus": true,
160 | "useFocusWithin": true,
161 | "useFps": true,
162 | "useFullscreen": true,
163 | "useGamepad": true,
164 | "useGeolocation": true,
165 | "useI18n": true,
166 | "useIdle": true,
167 | "useImage": true,
168 | "useInfiniteScroll": true,
169 | "useIntersectionObserver": true,
170 | "useInterval": true,
171 | "useIntervalFn": true,
172 | "useKeyModifier": true,
173 | "useLastChanged": true,
174 | "useLocalStorage": true,
175 | "useMagicKeys": true,
176 | "useManualRefHistory": true,
177 | "useMediaControls": true,
178 | "useMediaQuery": true,
179 | "useMemoize": true,
180 | "useMemory": true,
181 | "useMounted": true,
182 | "useMouse": true,
183 | "useMouseInElement": true,
184 | "useMousePressed": true,
185 | "useMutationObserver": true,
186 | "useNavigatorLanguage": true,
187 | "useNetwork": true,
188 | "useNow": true,
189 | "useObjectUrl": true,
190 | "useOffsetPagination": true,
191 | "useOnline": true,
192 | "usePageLeave": true,
193 | "useParallax": true,
194 | "usePermission": true,
195 | "usePointer": true,
196 | "usePointerSwipe": true,
197 | "usePreferredColorScheme": true,
198 | "usePreferredDark": true,
199 | "usePreferredLanguages": true,
200 | "useRafFn": true,
201 | "useRefHistory": true,
202 | "useResizeObserver": true,
203 | "useRoute": true,
204 | "useRouter": true,
205 | "useScreenOrientation": true,
206 | "useScreenSafeArea": true,
207 | "useScriptTag": true,
208 | "useScroll": true,
209 | "useScrollLock": true,
210 | "useSessionStorage": true,
211 | "useShare": true,
212 | "useSlots": true,
213 | "useSpeechRecognition": true,
214 | "useSpeechSynthesis": true,
215 | "useStepper": true,
216 | "useStorage": true,
217 | "useStorageAsync": true,
218 | "useStyleTag": true,
219 | "useSwipe": true,
220 | "useTemplateRefsList": true,
221 | "useTextSelection": true,
222 | "useTextareaAutosize": true,
223 | "useThrottle": true,
224 | "useThrottleFn": true,
225 | "useThrottledRefHistory": true,
226 | "useTimeAgo": true,
227 | "useTimeout": true,
228 | "useTimeoutFn": true,
229 | "useTimeoutPoll": true,
230 | "useTimestamp": true,
231 | "useTitle": true,
232 | "useToggle": true,
233 | "useTransition": true,
234 | "useUrlSearchParams": true,
235 | "useUserMedia": true,
236 | "useVModel": true,
237 | "useVModels": true,
238 | "useVibrate": true,
239 | "useVirtualList": true,
240 | "useWakeLock": true,
241 | "useWebNotification": true,
242 | "useWebSocket": true,
243 | "useWebWorker": true,
244 | "useWebWorkerFn": true,
245 | "useWindowFocus": true,
246 | "useWindowScroll": true,
247 | "useWindowSize": true,
248 | "watch": true,
249 | "watchArray": true,
250 | "watchAtMost": true,
251 | "watchDebounced": true,
252 | "watchEffect": true,
253 | "watchIgnorable": true,
254 | "watchOnce": true,
255 | "watchPausable": true,
256 | "watchPostEffect": true,
257 | "watchSyncEffect": true,
258 | "watchThrottled": true,
259 | "watchTriggerable": true,
260 | "watchWithFilter": true,
261 | "whenever": true,
262 | "Component": true,
263 | "ComponentPublicInstance": true,
264 | "ComputedRef": true,
265 | "ExtractDefaultPropTypes": true,
266 | "ExtractPropTypes": true,
267 | "ExtractPublicPropTypes": true,
268 | "InjectionKey": true,
269 | "PropType": true,
270 | "Ref": true,
271 | "VNode": true,
272 | "WritableComputedRef": true,
273 | "onBeforeRouteLeave": true,
274 | "onBeforeRouteUpdate": true,
275 | "toValue": true,
276 | "useLink": true
277 | }
278 | }
279 |
--------------------------------------------------------------------------------
/src/components/dashboard/ComparisonTable.vue:
--------------------------------------------------------------------------------
1 |
128 |
129 |
130 |
131 |
132 |
139 |
156 |
157 |
158 |
162 |
163 |
168 |
169 |
170 |
175 |
176 |
177 |
184 |
185 |
186 |
187 | {{ row?.refundRate }}
188 |
189 |
190 |
191 |
192 |
193 |
218 |
219 |
220 |
221 |
222 |
228 |
--------------------------------------------------------------------------------